среда, 19 августа 2009 г.

Запуск JSTL в Eclipse Dynamic Web Project

Оказывается не все так просто, если запускать Tomcat под Eclipse.
Если в Eclipse создать Dynamic Web Project, то простое добавление
jstl.jar и standard.jar в WEB_INF/lib может не помочь,
даже если в Java Build Path они есть.
При этом, если экпортировать .war, то в Tomcat запустится нормально.
Не запускается именно из-под Eclipse.

Помогло следующее:
при вызове контекстного меню в Run As -> Run configuration
выбирает нужный сервер из списка Apache Tomcat (перед этим нужно остановить)
справа выбираем сервер и в закладке Classpath и добавляем (Add jars):
jstl.jar, standard.jsr, servlet-api.jar, jsp-api.jar, el-api.jar

вторник, 18 августа 2009 г.

Hibernate: ошибка связанная с SLF4J

Проблема:
При запуске нового проекта на Hibernate 3.3.2 скопированы с архива (папка lib) все необходимые библиотеки, в том числе и slf4j-api-1.5.8.jar.
Но при старте теста появляется ошибка:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. Exception in thread "main" java.lang.ExceptionInInitializerError

Решение:
На сайте http://www.slf4j.org/codes.html#StaticLoggerBinder эту ошибку описывают так:
"This error is reported when the org.slf4j.impl.StaticLoggerBinder class could not be loaded into memory. This happens when no appropriate SLF4J binding could be found on the class path. Placing one (and only one) of slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar on the class path should solve the problem."
То есть нужно в пути к библиотекам добавить один из (и только один slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar)

Скачать необходимую библиотеку можно с сайта http://www.slf4j.org/dist/ (берем ту же версию, что и slf4j-api).
В Jar архиве есть необходимые библиотеки.
Я взял slf4j-jdk14-1.5.8.jar.

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

Как правильно закрывать сессию. session.invalidate() - не достаточно

Описание проблемы:
Есть сайт, с такими условными частями:
1) login.jsp - в котором вводятся логин и пароль
2) dispatcher - сервлет, который обрабатывает данные формы из login.jsp, проверяя из БД логин и пароль и перенаправляет (или назад в login.jsp (если проверка неудачна) или в welcome.jsp)
3) welcome.jsp (или home.jsp), на этой странице приветствия, профайл пользователя, домашняя страница пользователя, или любая другая страница с конфиденциальными данными и ссылками.
Пользователь нажимает по ссылке "выйти", dispatcher закрывает сессию и перенаправляет на страницу регистрации login.jsp.
4) вспомогательный объект, в котором находятся дополнительные/вспомогательные данные сессии.

Задача:
Правильно сделать так, чтобы пользователь, нажав на кнопку браузера "Назад" ("Back") не смог просматривать конфиденциальные данные предыдущего пользователя. Браузер кеширует данные.

Обсуждение:
Все оказалось не так просто.
session.invalidate() - проблему не решил.
session.removeAttribute("myObject")
- тоже может воскреснуть.
session.removeParameter("login")
- не существует
запретить кеширование: с одной стороны на это надеяться нельзя, поскольку сервер не может полностью контролировать работу браузеров всех версий и под все платформы, это клиентская часть. С другой стороны, и это во многих случая не помогает.
Отменять кеширование можно либо программным способом:
response.setContentType("text/html");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
response.setHeader("Pragma","no-cache")
либо (по сути это одно и тоже, к тому же скриптлеты уже считаются плохой манерой):
META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE">
META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
META HTTP-EQUIV="Expires" CONTENT="-1">

и даже response.
sendRedirect() вместо
RequestDispatcher rd =
this.getServletConfig()
.getServletContext()
.getRequestDispatcher("login.jsp");
rd.forward(request, response);
не помогает.

Все дело в том, что когда пользователь, нажал кнопку назад в welcome.jsp объект, то объект, в котором мы храним данные сессии практически потерян, НО если нажать кнопку обновить (F5), он снова восстанавливается, поскольку браузер все-равно сохранил параметры этой страницы (request.getParameter("login") все-еще срабатывает со старыми данными).
Дополнительной мерой защиты есть создание Ajax-запроса на сервер для проверки наличия объекта сессии, который хранит результаты регистрации.
Пример сервлета:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html; charset=UTF-8");
request.setCharacterEncoding("UTF-8");
HttpSession session = request.getSession();
Helper helper = (Helper) session.getAttribute("helper");
PrintWriter out = response.getWriter();
if (helper.isLoged()) {
out.println("1"); // valid
} else {
out.println("0"); // invalid
}
}


Пример клиента (с помощью JQuery):
function validate(){
$.ajax({
url: "checkSession",
type: "POST",
data: {0: 0}, //any minimal data
error: function(){ alert("виникла помилка Ajax"); } ,
success: function (data){
if (data != 1){
location.href="login.jsp";
}
}
}); // ajax
}


И welcome.jsp вызываем эту функцию.

Такой метод хорош. Но все-таки можно успеть остановить перенаправление (мне это удалось сделать) и опять же обновить страницу...

Хорошим трюком есть использование токенов.
В сервлете создаем генерацию токенов:
public static int TOKEN ;
public static int getNextToken(){
TOKEN++;
return TOKEN;
}

на странице с формой логина создаем дополнительный скрытый параметр:
input type="hidden" name="token" value="<%= Dispatcher.getNextToken() %>"
и в том же (предыдущем) сервлете делаем проверку:
if (new Long(reaquest.getParameter("token")) {
forward(request, response, "/login.jsp");
}

И срабатывает! Дело в том, что когда пользователь нажал на кнопку "выйти", его перенаплавило на login.jsp, в котором счетчик TOKEN увеличился. Но в закешированной странице это значение осталось старым, поэтому оно будет меньше текущего


И еще дополнительным способом (наверное даже самым лучшим) это использовать в login.jsp AJAX, который будет проверять в базе логин и пароль и перенаправит (в случае успешной аутентификации) на нужную страницу, которая НЕ ХРАНИТ И НЕ КЕШИРУЕТ результаты. В таком случае, параметр ACTION вместо сервлета перенаправления нужно установить action="#"

воскресенье, 5 июля 2009 г.

Установка Subversion на OpenSuse 11

Кратко, просто и с примером:

Установка Subversion возможна как на отдельный компьютер, так и на сервер.
В данном примере обсуждается установка на сервер на базе Apache2.

1. Первым делом нужно установить следующие пакеты:
apache2, apache2-prefork, libapr1, libapr-util1, neon, subversion, subversion-server.

2. Активируем модули dav, dav_svn (под рутом)
a2enmod dav
a2enmod dav_svn

3. на сервере должен появиться файл с примерами настроек конфигурации Subversion.
По умолчанию этот файл не запустится, так как конфигурацию нужно настроить вручную.
В нем есть примеры настроек. Если веб-сервер Apache использует виртуальные хосты (Virtual Hosts),
то, если просто включить этот файл в общий файл конфигурации Apache, то будет предоставлен доступ к
системе управления версиями на всех виртуальных хостах. В большинстве случаев такого нужно избегать.
Для решения этого вопроса можно либо включить ссылку на файл конфигурации (Include /путь/к/файлу) или
добавить эти строки непосредственно в настройку виртуального хоста.
Я выбрал второй вариант, поскольку удобней сразу видеть настройки конкретного виртуального хоста
без "пробежки" по файловой системе.

Итак, предположим, что я хочу видеть по адресу svn.mydomain.com все свои проекты (репозитарии).
Вот пример конфигурации виртуального хоста:


Замечания:
  • замените svn.mydomain.com на ваш домен
  • не нужно добавлять DocumentRoot - могут быть конфликты
  • по умолчанию в примере после установки стоит параметр - SVNParent. Корректно - SVNParentPath
  • конфигурация настроена так, чтобы все могли видеть список проектов, но для проекта
  • myproject система запросит логин и пароль
  • если не указать SVNListParentPath on, вместо списка проектов, будет вываливаться:
Access forbidden!
You don't have permission to access the requested directory. There is either no index document or the directory is read-protected.
If you think this is a server error, please contact the webmaster.
Error 403
4. Дальнейшие настройки (под рутом)
mkdir -p /srv/svn/myproject # создаем каталон для нашего проекта
mkdir /srv/svn/auth # папка для хранения файла пользователей
touch /srv/svn/auth/svn.mydomain.com.htpasswd # сам файл, в котором хранятся пользователи для доступа к SVN
chown root:www /srv/svn/auth/svn.mydomain.com.htpasswd # права доступа
chmod 640 /srv/svn/auth/svn.mydomain.com.htpasswd # права доступа
htpasswd2 /srv/svn/auth/svn.mydomain.com.htpasswd my_user # добавляем пользователя
rcapache2 restart # перезапуск Apache2

После удачного перезапуска по адресу http://svn.mydomain.com можно просмотреть наш репозитарий, который пока пустой

5. Настраиваем проект myproject (под рутом)
svnadmin create /srv/svn/repos/myproject
-- команда создаст структуру каталогов для этого проекта
(Если нужно удалить, просто удаляем с помощью команды rm )
chown -R wwwrun:www /srv/svn/repos/myproject/
-- права доступа
svn import /path/to/project/files http://svn.mydomain.com -m "initial import"
-- команда импортирует необходимую структуру файлов с Subversion
(Если не указать путь к /path/to/project/files, то будет импортироваться текущая директория)


6. Теперь можно на удаленных компьютерах делать checkout
svn checkout http://svn.mydomain.com

Все.
Некоторая информация была переведена с этого сайта
Более подробную информацию о Subversion можно прочитать/скачать тут.
На этом сайте можно найти и на русском, но кто знает английский, лучше на английском -
точнее и новее информация.

PS. Современные IDE типа NetBeans и Eclipse имеют модули для работы с SVN.
Для Eclipse я выбрал Subclipse. об установке можно прочитать по этой ссылке (с рисунками ;) )

четверг, 18 июня 2009 г.

Перенос Joomla на другой сервер (+ меняем кодировку с cp1251 на utf8)

Перенос Joomla будет состоять из 4-х этапов:

  1. настройка доменного имени
  2. копирование файлов сайта
  3. экспорт/импорт базы данных (еще рассмотрим конвертацию кодировки с cp1251 в utf8)
  4. настройка параметров на новом сервере.

Итак, 1) настройка доменного имени
При покупке доменного имени продавец должен выслать имя и пароль доступа настройки. Заходим в настройку доменного имени и создаем/редактируем записи:
подзона - @
A (тип записи)
приоритет - 0 (ноль) (если требуется)
адрес хоста - адрес сервера
Тоже самое нужно сделать с подзоной www
На сервере создаем VirtualHost
например, так:
ServerAdmin webmaster@localhost
ServerName mysite.com
ServerAlias www.mysite.com
DocumentRoot /srv/www/htdocs/mysite.com
ErrorLog /var/log/apache2/mysite.com-error_log
CustomLog /var/log/apache2/mysite.com_log combined


2) копирование файлов сайта
Тут вопросов не должно быть. Используем любой доступный способ копирования на сервер (ftp, ssh, webDav, флешка ...)
Не забываем про права доступа к файлам

3) экспорт/импорт базы данных (еще рассмотрим конвертацию кодировки с cp1251 в utf8)

а) экcпорт
mysqldump -u user -p mypassword databasename > backup.sql
скорее всего user - это root
К сожалению, у Денвера нету утилиты mysqldump. Поэтому можно скачать с сайта mysql файл (without installer, без установки) и копируем от туда mysqldump и вставляем в bin директорию, где установлен mysql

б) копируем файл на сервер

в)
(на сервере)
заходим в оболочку mysql (mysql --password для входа с паролем )

create user 'myusername'@'localhost' identified by 'mypassword' ;

create database mydbname character set utf8 collate utf8_general_ci;

grant all on mydbname.* to myusername@localhost ;

создали пользователя
создали базу (обратите внимание на кодировку)
дали все права для новому пользователю на новую базу

просмотреть кодировку созданной бд - show create database mydbname ;

г) конвертируем базу с cp1251 в utf8
iconv -f cp1251 -t utf8 backup.sql > backup-utf8.sql

в файлике backup-utf8.sql в строке
/*!40101 SET NAMES cp1251 */;
cp1251 меняем на utf8

д) импортируем базу
mysql -h localhost -u myusername -D mydbname --password=mypassword < backup-utf8.sql

для проверки импорта заходим в mysql под новым пользователем
mysql -u myusername -p mypassword mydbname

show tables ;


для установки кодировки в консоли mysql
SET character_set_client='utf8';

SET character_set_results='utf8';

SET character_set_connection='utf8';

(для справки: Переменная character_set_client устанавливает кодировку данных отправляемых от клиента, переменная character_set_results устанавливает кодировку данных отправляемых клиенту, переменная character_set_connection устанавливает кодировку, в которую преобразуется информация пришедшая от клиента, перед выполнением запроса на сервере.)
или просто
SET NAMES 'utf8' (который заменяет эти 3 команды)
и смотрим одну из таблиц
select * from mytablename ;

4) настройка параметров на новом сервере.
открываем для редактирования файл configuration.php (желательно предварительно сохраните оригинал)
редактируем поля
var $user = 'myusername';

var $password = 'mypassword';

var $db = 'mydbname';

var $log_path = '/новый/путь/к/папке/logs';

var $tmp_path = '/новый/путь/к/папке/tmp';


Заходим на сайт, проверяем, радуемся (или исправляем ошибки, а потом радуемя)

PS:
Есть Еще очень удобный компонент JoomlaPack, корорый может сильно облегчить работу.


вторник, 2 июня 2009 г.

Скрипт для автоматического запуска/останова Tomcat

(пример установки Tomcat 6 на CentOS 5)

1. скачиваем Tomcat 6
2. копируем на локальный раздел (например, /opt/tomcat )
3. проверяем запуск:
  • а) /opt/tomcat/bin/startup.sh
  • б) в браузере проверяем localhost:8080 (это порт по умолчанию)
  • в) останавливаем /opt/tomcat/bin/shutdown.sh
4. создаем файлик для скрита:
  • touch /etc/init.d/tomcat
  • chmod 755 /etc/init.d/tomcat (делаем его исполняемым)
5. Копируем в этот файл следующий текст (с помощью vi, mcedit, gedit... )
#!/bin/bash

# chkconfig: 35 20 80

# description: Tomcat Server basic start/shutdown script

# processname: tomcat

START_TOMCAT=/opt/tomcat/bin/startup.sh


STOP_TOMCAT=/opt/tomcat/bin/shutdown.sh

start() {
echo -n "Starting tomcat: "
${START_TOMCAT}
echo "done."
}
stop() {
echo -n "Shutting down tomcat: "
${STOP_TOMCAT}
echo "done."
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
stop
sleep 10
start
;;
*)

echo "Usage: $0 {start|stop|restart}"
esac

exit 0

(Примечание: в некоторых случаях указывают TOMCAT_HOME. В данном случае для простоты можно и без TOMCAT_HOME)

6. /sbin/chkconfig --add tomcat (добавляем скрипт в системные службы)
7. /sbin/chkconfig --level 35 tomcat on (будет запускаться на уровнях 3 и 5)
8. /sbin/chkconfig --list tomcat (проверяем)
9. /sbin/service tomcat start (запускаем)

четверг, 12 февраля 2009 г.

java ConcurrentModificationException

Эта ошибка может возникнуть в случае, когда происходит попытка изменить коллекцию внутри итерации этой же коллекции. Например:
List originalList = new ArrayList();
for (Iterator it =
originalList.iterator(); it.hasNext(); ) {
originalList.add(someObject);
}

Решение: создать копию коллекции и изменять нужную коллекцию внутри итерации по копии. Пример:
List copy = new ArrayList(originalList);
for (Iterator it = copy.iterator(); it.hasNext(); ) {
originalList.add(someObject);
}

Заметка: можно также использовать специальные классы колекций (что-то типа
CopyOnWriteArrayList), но в большинстве случаев лучше использовать копии.
Для дополнительной информации:

Class ConcurrentModificationException (англ.)

описание решения проблемы на форуме Sun (англ.)