Показаны сообщения с ярлыком java. Показать все сообщения
Показаны сообщения с ярлыком java. Показать все сообщения

Неожиданно

При использовании в контроллере конструкции redirect:/some-page результатом было добавление всех параметров в строку запроса, например: localhost/success?param1=value1&param2=value2&param3=value3.
А такого быть не должно. Поэтому, после небольших поисков нашлось следующее решение. Для Spring 3.1 необходимо включить опцию ignoreDefaultModelOnRedirect:

<mvc:annotation-driven ignoreDefaultModelOnRedirect="true"/>

А вот для версии, скажем 3.0, такого тэга нет. Но можно использовать небольшую хитрость, просто добавить переменную RedirectAttributes (даже можно не использовать ее) в метод, где вызывается редирект:

@RequestMapping("save")
public String doSave(RedirectAttributes ra)
{
   return "redirect:/success";
}

Как запустить OSGi консоль

По умолчанию, для созданного через визард Eclipse RCP «Hello World» приложения, OSGi консоль не запускается. Так вот, чтобы ее запустить надо сделать следующее:

  1. Добавить следующие бандлы/плагины в настройках запуска (Run Configurations):

  2. В параметры запуска добавить -console;
  3. Все, запускаем приложение и консоль должна запуститься автоматически.

Редактируем колонку таблицы

Ни для кого не секрет, что vaadin таблицу можно сделать редактируемой, если вызвать метод table.setEditable(true) и наоборот. В этом случае редактировать можно все ячейки.

Но что, если необходимо редактировать ячейки только одной колонки? Например так:

Форумы и «Book of Vaadin» подсказывают, что надо использовать TableFieldFactory. Например: https://vaadin.com/book/-/page/components.table.html, параграф «Navigation in Editable Mode».

Собственно, примерно так должен выглядеть код:

// создаем таблицу и определяем ее колонки
Table table = new Table();
table.addContainerProperty("num", Integer.class, null);
table.addContainerProperty("name", String.class, null);
table.addContainerProperty("age", String.class, null);

// переопределяя метод создания редактируемых полей, мы просто 
// указываем конкретную колонку или колонки, для которых надо 
// создавать эти самые поля
table.setTableFieldFactory(new TableFieldFactory() {
   @Override
   public Field createField(Container c, Object itemId, 
         Object propertyId, Component uiContext) {
      TextField field = null;

      if ("age".equals(propertyId)) {
         field = new TextField((String) propertyId);
         field.setWidth("100%");
      }

      return field;
   }
});

// а это позволит проверить работоспособность примера по 
// двойному нажатию мышки на табличке
table.addListener(new ItemClickEvent.ItemClickListener() {
   @Override
   public void itemClick(ItemClickEvent event) {
      if (event.isDoubleClick()) {
         table.setEditable(!table.isEditable());
      }
   }
});

// осталось заполнить таблицу данными и запустить приложение
int index = 1;
table.addItem(new Object[]{index, "Nathan Fielding", 41}, index++);
table.addItem(new Object[]{index, "John Smith", 26}, index++);
table.addItem(new Object[]{index, "Jane Hill", 88}, index++);
table.addItem(new Object[]{index, "John Sheridan", 32}, index++);
table.addItem(new Object[]{index, "Marc Torvalds", 40}, index++);
table.addItem(new Object[]{index, "Sarah Richards", 34}, index++);
table.addItem(new Object[]{index, "Peter Fielding", 85}, index++);
table.addItem(new Object[]{index, "Lenny Sheridan", 32}, index++);
table.addItem(new Object[]{index, "Lenny McGoff", 70}, index++);
table.addItem(new Object[]{index, "John Halas", 72}, index++);

Selenium проблема с Vaadin ComboBox

При написании selenium теста я столкнулся с ситуацией, когда стандартный метод type(locator, text) никак не взаемодействовал с vaadin элементом ComboBox. Ну и не только с ним.

Для решения проблемы, selenium позволяет генерировать HTML DOM Events при помощи метода fireEvent(locator, eventName), например:

public void typeInVaadinComboBox(String locator, String text) {
    fireEvent(locator, "keydown");
    type(locator, text);
    fireEvent(locator, "keypress");
    fireEvent(locator, "keyup");
}

Это решает данную проблему, а также позволяет взаимодействовать с другими недружелюбными элементами, например строками таблиц.

Доп. материалы:
http://www.w3schools.com/jsref/dom_obj_event.asp
http://www.quirksmode.org/dom/events/index.html
http://java.dzone.com/articles/testing-gwt-apps-selenium-or
http://blog.softwaretestingclub.com/2011/04/selenium-commands-locators-explained

Mysql и конвертация времени

Что если необходимо хранить время в базе данных в GMT, а клиенту отдавать в GMT+1. Как обеспечить корректную конвертацию времени и оставить код прозрачным? Этот вариант решения заключаться в том, чтобы правильно настроить java mysql connector для конвертации времени.

Что необходимо добиться:
  1. В базе время хранится в GMT; 
  2. При запросах время получаем в GMT+1 (+01:00); 
  3. При сохранении время из GMT+1 конвертируется в GMT.

Что для этого необходимо настроить:
  1. Часовой пояс GMT для mysql сервера:
    mysql> set global time_zone = '+00:00';
    mysql> set time_zone = '+00:00';
    
    Проверить настройки можно так:
    mysql> select now();
    mysql> select @@global.time_zone, @@session.time_zone, @@system_time_zone;
    
  2. Задать параметр -Duser.timezone=GMT+1 в файле setenv.sh и setenv.bat. Например:
    # file setenv.sh
    JAVA_OPTS="$JAVA_OPTS -Duser.timezone=GMT ..."
    
    Это надо сделать только если необходимо вручную задать часовой пояс в котором работает приложение, иначе пропустить этот шаг;
  3. Перейти к строке с настройкой jdbc.default.url и добавить параметр useTimezone=true. Например:
    jdbc.default.url=jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false&useTimezone=true
    
  4. Готово, запускаем и проверяем.
Эти настройки mysql базы будут работать до ее перезапуска. После перезапуска время снова будет системным. Этого можно избежать вручную указав параметр --default-time-zone='+00:00' при запуске mysql сервера. Либо снова выполнить шаг 1 и 2.

Официальная документация:
http://dev.mysql.com/doc/refman/5.1/en/time-zone-support.html
http://dev.mysql.com/doc/refman/5.5/en/connector-j-reference-configuration-properties.html

Обновление базы данных

Liferay сервисы поддерживают версионность базы данных. Другими словами, в зависиммости от описанных в service.xml сущностей, будут созданы соответствующие таблицы в базе данных и будет сохранена версия этих таблиц на момент создания. При последующем изменениии сущностей, пересборке и развертывании сервисов произойдет автоматическое обновления соответствующих таблиц. Это удобный и легкий способ создания и поддержки слоя для работы с базой данных, но также существуют и некоторые недостатки.

Предмет обновления зависит от того, что было изменено от версии к версии. Добавление новых колонок, удаление или модификация существующих происходит просто и почти без ошибок. А вот переименование колонки влечет за собой ее удаление и создание новой, при этом данные теряются навсегда.

Есть проблема обновления таблиц с большим количеством данных. Этот процесс выполняется средствами java, поэтому банально может не хватить памяти. В этом случае можно вручную переименовывать необходимую таблицу и запускать обновление, затем удалять сгенерированную таблицу и возвращать старой прежнее имя.

Также могут возникнуть проблемы, если структуру базы надо обновить на этапе сборки проекта. В этом случае будут недоступны изменения, которые автоматически появятся после развертывания. Как выход можно отключить автообновление структуры. Для этого надо создать файл service-ext.properties в той же директории что и service.properties файл, и переопределить следующий параметр:
build.auto.upgrade=false

Java 5 или 1.5?

Версия «5.0» является версией продукта, в то время как «1.5.0» — версией разработки. В остальном разницы нет.

Проверка ссылок на неравенство null

Порой приходится конструировать длинные условия, которые проверяют сразу нескольких ссылок на неравенство null, например:
if (object1 != null && object2 != null && object3 != null) {
    // и если условие верно, то выполняется код
}

Для упрощения записи таких условий можно использовать Varargs, которые появились в Java 5. Теперь создаем функцию, которая будет проверять переменное число ссылок на неравенство null:
// принцип сохранился, если хотя бы одна ссылка равна null, 
// тогда метод вернет false, иначе — true
public boolean notNull(Object... objects) {
    for (Object object : objects) {
        if (object == null) {
            return false;
        }
    }
    return true;
}

Если переписать первое выражение с учетом использования этой функции, то получится следующее:
if (notNull(object1, object2, object3)) {
    // и если условие верно, то выполняется код
}

Подсказали другой вариант реализации функции:
public boolean notNull(Object... objects) {
    return !Arrays.asList(objects).contains(null);
}

Mocking Liferay Services

Если вы пишете под Liferay, используете Service Builder и не знаете как протестировать свою логику, тогда этот пост для вас.

В сети можно найти различные варианты решения данной задачи. Многие из них направлены на специфическую конфигурацию зависимостей, которые позволят сервисам работать с реальной базой данных. Такие подходы сложны и противоречат принципам юнит тестирования. В этом случае надо либо поднимать базу в памяти и настраивать на ее использования сервиса, либо создавать заглушки, они же «моки», самих сервисов. Поэтому бизнес логику лучше вынести в отдельный слой.

Пока нет ничего сложного. С подходом определились, теперь необходимо найти инструмент, который позволит создавать заглушки на любой сгенерированный сервис. Такой инструмент есть — Mockito. В качестве льтернативы могу предложить Easymock, но примеры будут на Mockito.

Для примера возьмем сущность Status и опишем ее в service.xml:
<entity name="Status" table="status" local-service="true" remote-service="false">
    <column name="id" db-name="status_id" type="long" primary="true" id-type="increment"/>
    <column name="name" db-name="name" type="String"/>
</entity>

Соберем сервиса, после чего получим ряд классов, который все вместе образуют DAO слой для этой сущности. Обычно в бизнес логике используются классы *LocalServiceUtil, в нашем случае это класс StatusLocalServiceUtil. Допустим что у нас есть некий класс Controller, который вызывает метод getStatus класса StatusLocalServiceUtil и выполняет некую логику с результатом:
public class Controller {
    public void doAction(long statusId) throws SystemException, PortalException {
        // Получаем статус по его id
        Status status = StatusLocalServiceUtil.getStatus(statusId);
        // и выводим его в консоль
        System.out.println("Status = " + status);
    }
}

Целью является создание заглушки, которая будет эмулировать работу метода getStatus. А сложность в том, что этот метод статический и подменить его сложно, но возможно. Далее следует пример юнит теста, в котором происходит создание заглушки:
public class ControllerTest {
    private StatusLocalService service;
    private Controller controller;

    @BeforeMethod
    public void beforeMethod() {
        // Создаем мок сервиса используя библиотеку mockito
        service = mock(StatusLocalService.class);
        // После этой строчки класс StatusLocalServiceUtil 
        // будет использовать зуглушку или мок сервиса
        new StatusLocalServiceUtil().setService(service);
        // А это контроллер, который работает со
        // сгенерированными сервисами и ничего не знает
        // ни о каких моках
        controller = new Controller();
    }

    @Test
    public void testGetStatus()
            throws ServletException, SystemException, PortalException {
        // Конфигурируем сервис таким образом, что при вызове
        // метода с параметром 7 вернется уже другой мок —
        // мок объекта Status
        when(service.getStatus(eq(7L))).thenReturn(mock(Status.class));

        // Вот он самый, вызов метода, который тестируется
        controller.doAction(7);

        // А здесь проверяем был ли вызов метода getStatus
        // именно с параметром 7
        verify(service).getStatus(eq(7L));
    }
}

На самом деле дела обстоят сложнее и требуют более глубоких объяснений иерархии генерируемых сервисов, но для тестирования этого достаточно.

Валидация непустых полей

Оказывается, в Vaadin 6.7.8 невозможно валидировать текстовое поле на непустое значение. Можно пометить его как обязательное и тогда, вызывая метод commit этого поля, возникает исключение — com.vaadin.data.Validator.EmptyValueException. При этом, если поле размещается вручную, то визуально ошибку не будет видно. Имеется ввиду, что поле не является частью какой-нибудь стандартной формы фреймворка, типа com.vaadin.ui.Form.

Может это дефект, а может задумка, но как выход можно показать сообщение об ошибке в заранее определенном месте, например так: http://demo.vaadin.com/sampler#FormBasic. Для вывода сообщения можно использовать компонент com.vaadin.ui.Label. Вызов метода commit нашего поля надо поместить в блок try-catch. При возникновении исключения EmptyValueException вручную задать сообщение об ошибке используя метод setComponentError компонента label. Можно также добавить логику, которая покажет label с сообщением, когда произойдет ошибка, а в любом другом случае — скроет его.

Небольшой пример:
public class UserForm extends VerticalLayout {
    private TextField name;
    private Button apply;
    private Label error;

    public UserForm() {
        setSizeFull();
        setSpacing(true);

        name = new TextField("Name");
        name.setRequired(true);
        name.setRequiredError("Please enter a valid name");
        addComponent(name);

        error = new Label();
        error.setVisible(false);
        addComponent(error);

        apply = new Button("Apply");
        addComponent(apply);

        apply.addListener(new Button.ClickListener() {
            @Override
            public void buttonClick(Button.ClickEvent event) {
                try {
                    name.commit();
                } catch (Validator.InvalidValueException e) {
                    error.setCaption(e.getMessage());
                    error.setComponentError(e);
                    error.setVisible(true);
                }
                error.setVisible(false);
            }
        });
    }
}
Henri Sara объясняет причину: https://vaadin.com/forum/-/message_boards/view_message/365283

Транзакции в Liferay

Те, кто активно используют Service Builder понимают, что поддержка транзакций в сгенерированных сервисах просто необходима. Более того, это даже предусмотрено разработчиками портала. Но есть загвоздка — это не работает. По крайней мере с версией 6.0.5.

На странице http://issues.liferay.com/browse/LPS-15904 описана причина этого дефекта.

Как правило, портлеты создают новую SessionFactory для загрузки собственных hbm файлов. Также известно, что портал и портлеты используют один и тот же менеджер для создания транзакций, который в свою очередь использует SessionFactory портала. Однако портлеты должны использовать свои собственные SessionFactory, иначе портлетные сессии не будут использоваться в портальных транзакциях. Результат — транзакции в портлетах вообще не работают.

Как вариант решения можно использовать JTA transaction manager. Eсть две библиотеки на выбор: JOTM или Atomikos. На сайте Liferay есть даже статья, которая описывает как настроить портал для каждой из библитек — http://www.liferay.com/community/wiki/-/wiki/Main/JTA-XA+on+Tomcat. Но к сожалению статья не помогла. Только после продолжительного поиска удалось найти работающее решение.

Настройка портала с библиотекой JOTM:
  1. Скачиваем библиотеку с сайта http://forge.ow2.org/projects/jotm, отдельно обновляем библиотеку xapool до последней версии, даже если это бета-версия;
  2. Добавляем необходимые jar-файлы в {tomcat}/lib:
  3. Удаляем commons-logging.jar из {tomcat}/lib/ext и log4j.jar из {tomcat}/webapps/ROOT/WEB-INF/lib а также из каждого портлета;
  4. Находим файл {tomcat}/conf/context.xml и добавляем следующее в конец секции Context:
  5. <Resource 
       auth="Container" 
       name="UserTransaction" 
       type="javax.transaction.UserTransaction" />
        
    <Resource 
       auth="Container" 
       driverclassname="com.mysql.jdbc.Driver" 
       factory="org.objectweb.jotm.datasource.DataSourceFactory" 
       max-connections="100" 
       maxactive="20" 
       name="jdbc/LiferayPool" 
       username="root" 
       password="" 
       type="javax.sql.DataSource" 
       url="jdbc:mysql://localhost/lportal?useUnicode=true&amp;characterEncoding=UTF-8
          &amp;useFastDateParsing=false" /> 
        
    <Transaction 
      factory="org.objectweb.jotm.UserTransactionFactory" 
      jotm.timeout="60" />
    
    
  6. Комментируем все jdbc.default.* свойства в portal-ext.properties, а затем добавляем следующее:
  7. transaction.manager.impl=org.springframework.transaction.jta.JtaTransactionManager
    transaction.manager.property.allowCustomIsolationLevels=true
    transaction.manager.property.globalRollbackOnParticipationFailure=true
    jdbc.default.jndi.name=jdbc/LiferayPool
    
Это собственно и все. Гарантий никаких, в моем случае это заработало, в любом другом может и не заработать — это Liferay.

Обновляем Vaadin в Liferay

Все началось с того, что очередной функционал потребовал новых возможностей. Пришлось обновлять Vaadin. И что же делать, если на руках Liferay 6.0.5 и Vaadin, который шел в комплекте?

А вот что:
  1. Идем на страницу https://vaadin.com/releases;
  2. В правой колонке выбираем желаемую версию;
  3. Загружаем «Jar-only for all platforms» jar-файл и «Liferay update package» zip-архив;
  4. Удаляем существующую библиотеку {liferayWebapp}/WEB-INF/lib/vaadin.jar;
  5. Перемещаем и переименовываем новую библиотеку на место старой (см. пункт 4) — это обновление самой библиотеки;
  6. Распаковываем скачанный zip-архив, копируем с заменой его содержимое в папку {liferayWebapp}/html/VAADIN — это обновление наборов виджетов;
  7. Если используются сторонние дополнения, то набор виджетов необходимо пересобрать локально.
После запуска портала очищаем кэш браузера и радуемся новым возможностям Vaadin. 

Размер кучи в Eclipse

Те, кто исполюзует Intellij IDEA постоянно видят индикатор, отображающий размер кучи, в правом нижнем углу. Он выгладит так:




Такой индикатор есть и в Eclipse, но он выключен по умолчанию. Включить его можно поставив галочку напротив Preferences > General > Show heap status. И как результат получим:


Share plugins services

Представьте два портлета: «CLP» и «CLP Consumer». Первый содержит сервис-проект (service-plugin), сущность MyObject и соответственно набор сгенерированных классов, а второй нуждается в доступе к этому слою сервисов. Для обеспечения такого доступа необходимо следующее:
  1. Выполнить сборку сервиса в «CLP» портлете и в результате появится файл CLP-portlet-service.jar в директории {clp-portlet}/docroot/WEB-INF/lib;
  2. Сделать CLP-portlet-service.jar доступным для «CLP Consumer» портлета. Это можно сделать двумя способами:
    1. Скопировать jar файл в {clp-consumer-portlet}/docroot/WEB-INF/lib;
    2. Скопировать jar файл в глобальную директорию библиотек сервера приложений — {tomcatHome}/lib/ext но при этом удалить из всех других мест;
  3. Теперь можно вызвать метод сервиса (который находится в «CLP» портлете) из «CLP Consumer» портлета. Например:
  4. com.liferay.clp.service.MyObjectServiceUtil.testMethod();
    
Используя Tomcat 6.0 с этим были проблемы, но они вроде как решены в Liferay Portal 6.0.1 RC. Первоисточник

JSP tips

1. Параметр trimDirectiveWhitespaces поможет убрать пустый строки, появляющиеся на месте JSP-директив в коде результирующей страницы;

some.jsp:
<%@ page trimDirectiveWhitespaces="true" %>
web.xml:
<jsp-config>
    <jsp-property-group>
        <url-pattern>*.jsp</url-pattern>
        <trim-directive-whitespaces>true</trim-directive-whitespaces>
    </jsp-property-group>
</jsp-config>
2. Версия JSP:
JspFactory.getDefaultFactory().getEngineInfo().getSpecificationVersion();

Service Builder, размер поля

Liferay Service Builder, по умолчанию, задает размер поля типа String равным 75 символов. Свой размер поля можно задать в файле portlet-model-hints.xml.

Пример:
  1. Описываем произвольную сущность в service.xml:
  2. <entity local-service="true" name="Note" remote-service="false" table="note">
        <column id-type="increment" name="id" primary="true" type="long"/>
        <column name="noteText" type="String"/>
    </entity>
  3. Собираем сервис;
  4. Открываем файл /docroot/WEB-INF/src/META-INF/portlet-model-hints.xml;
  5. Задаем размер поля noteText = 512:
  6. <model name="mypackage.model.Note">
        <field name="id" type="long"/>
        <field name="noteText" type="String">
            <hint name="max-length">512</hint>
        </field>
    </model>
  7. Еще раз собираем сервис.
Результат можно посмотреть в файле /docroot/WEB-INF/sql/tables.sql, в котором длина поля noteText поменялась с 75 на 512:
create table note (id LONG not null primary key, noteText VARCHAR(512) null);

Источник: http://issues.liferay.com/browse/LEP-7406

JVM и Proxy

JVM параметры для настройки прокси:
-Dhttp.proxyHost=proxyhostURL
-Dhttp.proxyPort=proxyPortNumber
-Dhttp.proxyUser=someUserName
-Dhttp.proxyPassword=somePassword

Из командной строки:
$> java -Dhttp.proxyHost=proxyhostURL -Dhttp.proxyPort=proxyPortNumber \
-Dhttp.proxyUser=someUserName -Dhttp.proxyPassword=somePassword HelloWorldClass

В коде:
System.getProperties().put("http.proxyHost", "someProxyURL");
System.getProperties().put("http.proxyPort", "someProxyPort");
System.getProperties().put("http.proxyUser", "someUserName");
System.getProperties().put("http.proxyPassword", "somePassword");

Liferay properties

Для получения значения свойства из файлов portal.properties и portal-ext.properties можно использовать класс PropsUtil.
String p = PropsUtil.get("example.property.name");

Также можно использовать класс GetterUtil для преобразования значения в нужный тип или получение значения по-умолчанию.
// если свойство существует, по GetterUtil постарается
// преобразовать его в тип long иначе вернется значение
// по-умолчанию, которое указано вторым параметром - 111
long p = GetterUtil.getLong(PropsUtil.get("example.property.id"), 111);