понедельник, 17 августа 2009 г.

Управление версиями в Maven

После очередного релиза проекта приходится менять его версию, проходясь при этом по всем pom-файлам. Процедура эта довольно утомительная и избавить от нее поможет versions-maven-plugin.

Достаточно выставить в головном pom-файле новую версию проекта и затем выполнить команду:

mvn versions:update-child-modules

Как вы наверное догадались, команда выполнит обновление всех версий проекта в pom-файлах дочерних модулей. Если же, по какой-то причине, вы остались недовольны проделанной работой плагина, то содержимое pom-файлов можно легко вернуть в исходное состояние командой:

mvn versions:revert

Чтобы удалить backup-ы pom-файлов выполните:

mvn versions:commit

Помимо выполнения обновления версии проекта, есть еще несколько полезных возможностей. Вывод списка новых версий зависимостей проекта, доступных из репозитария:

mvn versions:display-dependency-updates

Список новых версий плагинов используемых в проекте:

mvn versions:display-plugin-updates

суббота, 8 августа 2009 г.

Параметры производительности nginx и tapestry 5

Думаю, что вам известно о HTTP-сервере nginx, а также возможности его использования в качестве front-end сервера для передачи разных ресурсов клиенту. Использование nginx позволяет разгрузить ваш сервер Java-приложений от лишней работы со стататическими ресурсами.

Отмечу лишь несколько основных, на мой взгляд параметров, влияющих на производительность системы использующей Nginx и веб-фреймворка Tapestry 5.

Параметры для nginx, выставляемые в nginx.conf:

  • worker_processes - кол-во процессов, обеспечивающих загрузку ресурсов с диска. Значение зависит от объема ресурсов и скорости носителя с которого производится чтение. Также играет роль число ядер/процессоров в вашем сервере, думаю, что значение должно быть не меньше чем их кол-во;

  • worker_connections - максимальное кол-во одновременных соединений с сервером. Следует выбрать оптимальное число, которое сможет держать сам сервер-приложений. Именно верный порог соединений предотвратит DDOS-атаки на ваш сервер и даст уверенность в том, что ваш сервер будет устойчиво работать.

Настройки производительности у Tapestry 5 сводятся к верной настройке пула страниц. Напомню, что при каждом обращении клиента фреймворк вытаскивает из пула экземпляр определенной страницы. Параметры:

  • tapestry.page-pool.hard-limit - максимальное значение страниц в пуле. Следует учесть, количество локалей поддерживаемых вашим приложением, т.к. для каждой локали создается отдельный экземпляр страницы (по-умолчанию значение 20);

  • tapestry.page-pool.soft-limit - кол-во страниц в пуле, после которого tapestry перейдет в ожидание освободившихся страниц. Пока число страниц в пуле не достигнет этого значения и свободных экземпляров не будет, они будут создаваться (по-умолчанию значение 5).

понедельник, 3 августа 2009 г.

Использование LiquiBase с Hibernate и Maven. Часть 2

Теперь о том, что касается изменений которые вносит hibernate в структуру БД, при добавлении/изменении сущностей, если в настройках указан параметр hibernate.hbm2ddl.auto=update. Как продолжить пользоваться этой возможностью, если теперь для всех изменений структуры БД мы должны использовать только liquibase? Было бы не плохо получать sql генерируемый hibernate, который можно легко поместить в changeset liquibase. Для этого необходимо подключить hibernate3-maven-plugin к модулю, в котором используется hibernate:

Необоходимым условием для работы плагина является наличие файла hibernate.hbm.xml, в нем должны содержаться следующие настройки:

Не забудьте также и о том, что теперь все маппинги классов тоже нужно указывать в этом файле, а не в каком-нибудь SessionFactoryBean, если используете Spring. Настройка SessionFactoryBean будет выглядеть примерно так:

Стоит отметить, что параметру hibernate.hbm2ddl.auto, в вашем проекте, нужно присвоить значение validate. Теперь при каждом выполнении команды:

mvn hibernate3:hbm2ddl -Dhibernate.create=true

вам будет выводится sql генерируемый hibernate, который можно вставить в changeset. Параметр -Dhibernate.create=true следует указывать, в том случае, когда вам требуется SQL всей схемы БД. В случае, когда требуется получить SQL-команды, выполняемые hibernate для обновления схемы БД, то вместо первого параметра используйте -Dhibernate.update=true.

И помните, что в случае обновления БД вам нужно будет следить SQL, генерируемым hibernate, т.к. не все изменения параметров объектов входят в SQL (к примеру, hibernate не будет создавать некоторые constraint-ы).

четверг, 9 июля 2009 г.

Использование LiquiBase с Hibernate и Maven. Часть 1

Когда-то я представлял вашему вниманию инструмент для управления изменениями в БД - liquibase. Теперь я решил описать как его можно использовать в проектах с maven-сборкой, и hibernate.

Для начала необходимо создать отдельный maven-артефакт, который будет содержать только ресурсы - xml-файлы с changeSet-ами от liquibase. Структура файлов в папке resources нашего артефакта следующая:

    /changelogs/версия_проекта/*-changelog.xml
/changelogs/версия_проекта/release-changelog.xml
/project-changelogs.xml

Файл project-changesets.xml включает в себя набор файлов release-changelog.xml всех версий проекта, вот пример его содержимого:

Таким же образом файлы release-changelog.xml включают в себя список файлов с измененями, находящихся в той же папке и касающихся лишь данной версии проекта. Сами изменения я предпочитаю описывать не пользуясь при это тэгами liquibase, а использую привычный SQL.

Благодаря наличию в liquibase такого понятия как "контекст", можно разделить все операции над БД на три контекста:

  • schemedata - контекст в котором будут отражены изменения структруры базы;

  • initdata - контекст определяющий данные для инициализации БД, например добавление новых пользовательских ролей;

  • migratedata - контекст используемый при изменении "живых" данных, т.е данных появляющихся в системе при ее эксплуатации.

Такая концепция позволит вам четко разделять виды изменений в БД. А также "накатывать" изменения лишь нужных контекстов. Пример:

Если в проекте предполагается использовать две базы - БД разработчика/продакшн и БД для тестов, то pom-файл, созданного нами артефакта, будет содержать следующие строки:

Параметры app.database.xxx и test.database.xxx можно определить в головном pom-файле проекта. Теперь накатывать изменения на обе БД можно одной командой:

mvn process-resources либо mvn clean install

Чтобы сделать rollback обеих баз на несколько changeset-ов назад к команде следует добавить параметры:

-Dliquibase.goal=rollback -Dliquibase.rollbackCount=1

liquibase.rollbackCount - определят кол-во changeset-ов которые следует откатить.

Порой может возникнуть ситуация, когда требуется внести изменение в существующий changeset и заново накатить все changeset-и. Здесь поможет параметр:

-Dliquibase.dropFirst=true

Если потребуется работать с каждой БД отдельно, то необходимо использовать уже команды самого плагина liquibase:

mvn liquibase:update либо mvn liquibase:rollback

Для тестовой БД добавляем параметр -Ptestdb в командной строке.

суббота, 2 мая 2009 г.

Javascript AOP

Наверное каждому известно что такое АОП или по-просту аспектно-ориентированное программирование. В java есть целый ряд фреймворков реализующих определенные возможности данного подхода.

Но если смотреть в сторону javascript-языка, то возможности АОП в нем реализовать довольно просто:

var AOPUtils = {
addBefore: function(object, methodName, invokedObject, invokedMethodName) {
var oldMethod = object[methodName];
object[methodName] = function() {
var args = $A(arguments);
try {
if (Object.isFunction(invokedObject)) {
invokedObject.call(invokedObject, args);
} else {
invokedObject[invokedMethodName].call(invokedObject, args);
}
} catch (e) {
alert("An exception occurred in method '" + methodName + "' Error: " + e.message);
}
try {
var result = oldMethod.apply(object, args);
} catch (e) {
alert("An exception occurred in method '" + methodName + "' Error: " + e.message);
}
return result;
};
}
};

Представленная функция AOPUtils.addBefore позволяет вам выполнить свою функцию перед функцией какого-либо объекта. Для реализации использовались стандартные javascript-функции, смысл которых состоит в следующем: apply - вызов функции с параметрами в виде массива, call - вызов функции с перечислением параметров через запятую. Первым аргументом обеих функций будет объект который в вызываемой функции будет представлять ссылку на this.

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

var SomeClass = Class.create({
simplemethod: function(url) {
alert(url);
}
});

var sobject = new SomeClass();
AOPUtils.addBefore(sobject, "simplemethod", function(args) {
var url = args[0];
url += "?param=test";
args[0] = url;
});

sobject.simplemethod("http://test.com");

Отмечу, что для использования данного механизма необоходма библиотека Prototype

четверг, 2 апреля 2009 г.

Переключение из HTTPS-соединения в HTTP (в Tomcat)

Для наиболее безопасной передачи конфиденциальных данных по сети, в веб-приложениях, используется HTTPS-соединение. HTTPS-соединение лучше использовать не на весь пользовательский сеанс, а лишь на определенных этапах где защищенность канала действительно очень важна.

К примеру, если сделать на HTTPS-соединении только логин пользователя, а после его входа в систему снова использовать обычный HTTP, то на web-сервере Tomcat придется реализовать поддержку корректного переключения с HTTPS на HTTP.

Конфигурация Spring Security для такого случая:

<security:http auto-config="true" >
<security:intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY"
requires-channel="https"/>
<security:intercept-url pattern="/billing.html" access="ROLE_USER"
requires-channel="http"/>
</security:http>

Возможны два варианта переключения соединений:


1.    2.

В первом случае все будет работать, т.к. Tomcat создаст cookie сперва для HTTP сессии, которая будет действительна и для HTTPS, а следовательно может быть аутентифицирована Spring Security и использована дальше в работе с обычным HTTP.

Во втором случае Tomcat создаст cookie для HTTPS с параметром secure, а это значит что аутентификация такой сессии не позволит использовать в дальнейшем обычную HTTP сессию. Сookie с параметром secure привязана лишь к HTTPS-сессиям. Для пользователя же это обернется редиректом Spring Security на страницу логина, но с уже созданной cookie, и только второй логин уже пустит пользователя в систему. Чтобы решить эту проблему необходимо, находясь в HTTPS сессии, создать cookie для обычной HTTP сессии без параметра secure. С помощью фильтра это реализовать довольно просто:

package com.blogspot.nkoksharov.filter;

import java.io.IOException;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.commons.lang.StringUtils;

public class HttpsCookieFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
final HttpServletResponse httpResponse = (HttpServletResponse) response;
final HttpSession session = httpRequest.getSession(false);

if (request.isSecure() && session != null) {
final Cookie sessionCookie = new Cookie("JSESSIONID", session.getId());
sessionCookie.setMaxAge(-1);
sessionCookie.setSecure(false);
String contextPath = httpRequest.getContextPath();
if (StringUtils.isNotBlank(contextPath)) {
sessionCookie.setPath(contextPath);
} else {
sessionCookie.setPath("/");
}
httpResponse.addCookie(sessionCookie);
}

chain.doFilter(request, response);
}

...

}

Теперь переход с https-соединения будет выглядеть так:

Фильтр лучше всего поместить в самом начале цепочки фильтров, важно чтобы он был перед springSecurityFilterChain. Следует отметить, что при таком решении http-cookie будет приходить пользователю на каждый https-запрос. Т.к. сложно опеределить при https-соединении создана ли cookie для http-канала.

понедельник, 30 марта 2009 г.

Замена механизма отображения ошибок при валидации в Tapestry 5.1

При унифицированном подходе к отображению ошибок на форме, обычно для каждого поля ставится соответствующий html-элемент, либо элементы в которых пользователю сообщается об ошибке в конкретном поле. К примеру, в Spring-mvc в таком случае вам придется ставить тэг form:error для каждого поля.

Механизм отображения ошибок в Tapestry 5 реализован с помощью специального декоратора, который призван избавить разработчика от расставления подобных элементов на своей странице. Реализация этого механизма находится в классе org.apache.tapestry5.internal.DefaultValidationDecorator. Результат его работы выглядит так:

Согласитесь, не очень красиво вставлять красный крест после каждого поля с ошибкой да еще и подсвечивать его содержимое. Во всяком случае меня это не устроило. Хотелось бы, чтобы ошибки выглядели примерно так:

Для этого, в модуле tapestry вашего приложения необходимо подменить markup-фильтр DefaultValidationDecorator совоей реализацией:

package com.blogspot.nkoksharov.tapestry;

import org.apache.tapestry5.*;
import org.apache.tapestry5.ioc.*;
import org.apache.tapestry5.services.*;

public class AppModule {

public void contributeMarkupRenderer(OrderedConfiguration configuration,
final Environment environment) {

MarkupRendererFilter defaultValidationDecorator = new MarkupRendererFilter() {
public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) {
ValidationDecorator decorator = new ExValidationDecorator(environment, writer);
environment.push(ValidationDecorator.class, decorator);
renderer.renderMarkup(writer);
environment.pop(ValidationDecorator.class);
}
};

configuration.override("DefaultValidationDecorator", defaultValidationDecorator);
}
}

Логика декоратора выглядит так:

package com.blogspot.nkoksharov.tapestry;

import org.apache.tapestry5.*;
import org.apache.tapestry5.services.Environment;

public class ExValidationDecorator extends BaseValidationDecorator {

private static final String ERROR_ELEMENT_CLASS = "error_element";

private final Environment environment;
private final MarkupWriter markupWriter;

public ExValidationDecorator(Environment environment, MarkupWriter markupWriter) {
this.environment = environment;
this.markupWriter = markupWriter;
}

@Override
public void afterField(Field field) {
if (inError(field)) {
String errorId = field.getClientId() + "Error";
markupWriter.element("div",
"class", ERROR_ELEMENT_CLASS,
"id", errorId);
markupWriter.write(getErrorMsg(field));
markupWriter.end();
}
}

private String getErrorMsg(Field field) {
ValidationTracker tracker = environment.peekRequired(ValidationTracker.class);
return tracker.getError(field);
}

private boolean inError(Field field) {
ValidationTracker tracker = environment.peekRequired(ValidationTracker.class);
return tracker.inError(field);
}
}

Данный декоратор после каждого поля ставит такой вот элемент:

<div class="error_element" id="someElementError">
Сообщение об ошибке для данного поля
</div>

Стоит отметить, что возможность переопределения элементов в списке конфигурации сервисов, через метод override стала доступна лишь начиная с версии Tapestry 5.1.0.0