суббота, 25 декабря 2010 г.

Использование Redis в Java WEB applcation

В жизни каждого достаточно большого web-проекта наступает момент, когда требования к производительности заставляют поставить между кодом и данными в базе что-то достаточно быстрое и нетребовательное... Например Memcached или Redis. Первый инструмент достаточно известен, и обладает серьёзным недостатком: всё что не в памяти - удаляется. Второй - практически полноценная noSQL БД. И благодаря этой своей природе может быть крайне полезна, в первую очередь как дополнение к традиционной SQL СУБД. Действительно, так ли редко мы складываем в таблицы что-то совсем не "табличное"? Например, недавно мы вынесли в базу хранилище сессий пользователей (очень удобно для горизонтального масштабирования web-фронтэндов). Возникли проблемы с нагрузкой, потребовалась "тонкая" настройка и т.п. А ведь всё дело в том, что хранение таких данных в реляционной базе вообще-то "притянуто за уши". Что мы пишем в сесию? Правильно, пары ключ-значение. При чём тут SQL?. Короче, Redis с этой задачей справился идеально, что позволило нашему SQL-серверу снова "расправить крылья".

Вспомню, как я с этим разбирался...
После недолгого изучения разных источников нашёл вот эту wiki. Это перевод официальной документации. Пользовался почти исключительно этим сайтом. Но прежде чем начать изучение, установим себе эту систему.
На официальном сайте скачиваем версию (на данный момент последняя стабильная - 2.0). Это архив с исходниками, собираем сами: после распаковки в каталоге делаем make. После сборки запускаем сервер с помощью:

$ ./redis-server

По умолчанию он поднимается на порту 6379 и дальше с ним можно взаимодействовать хотя бы даже и обычным telnet-ом. Но мы не будем так суровы :)
Для начала сделаем минимальные правки в конфиге: redis.conf в каталоге приложения - поставим daemonize yes, это позволит нам запускать сервер не отдавая ему консоль. Более тонкую настройку оставим на потом.
Запускать сервер теперь будем с явным указанием конфига:

$ ./redis-server redis.conf 

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

$ ./redis-cli <команда> <параметры ...>

Например, чтобы сохранить пару ключ-значение в Redis:

$ ./redis-cli set mykey myval
$ OK

... а чтобы получить значение по ключу:

$ ./redis-cli get mykey

$ "myval"

Redis поддерживает несколько типов данных для значений, которые мы "вкладываем" в ключи. Это строки, списки, множества и хэши. Детальнее посмотрите в руководстве, на пока понадобятся лишь имена команд, для каждого типа данных "геттеры" и "сеттеры" свои. Например для хэшей это:
hget, hset, hmset, hgetall... пока достаточно... (установить значение поля хэша, получить его, установить все поля-значения и получить все). Детально разбираться не будем, всё равно работать нам не в консоли. Достаточно просто понимать, какие есть инструменты.

Теперь переходим к Java.
Для работы с Redis есть несколько библиотек. Самая "живая" из них - Jedis, поддерживает версию 2.0, что нам и требуется.
Скачиваем её отсюда. Подключаем jedis-1.5.2.jar к проекту. Пишем класс для взаимодействия с redis:

  1. /*
  2. * Redis get|set implementation
  3. */
  4.  
  5. package net.multipi.connector.redis;
  6.  
  7. import java.io.IOException;
  8. import java.util.Map;
  9. import java.util.Set;
  10. import redis.clients.jedis.Jedis;
  11.  
  12. public class RedisConnector {
  13.     private Jedis cli;
  14.     
  15.     /**
  16.      * constructor: connect to Redis server and authorization
  17.      * @param host
  18.      * @param port
  19.      * @param password
  20.      */
  21.     public RedisConnector(String host, int port, String password) {
  22.         cli = new Jedis(host, port, 5000);
  23.         cli.auth(password);
  24.         try {
  25.             cli.connect();
  26.         } catch (Exception ex) {
  27.             ex.printStackTrace(System.err);
  28.         }
  29.     }
  30.  
  31.     /**
  32.      * calculate keys count (eg count of active sessions)
  33.      * @return
  34.      */
  35.     public long getKeysCount() {
  36.         return cli.dbSize();
  37.     }
  38.  
  39.     /**
  40.      * get all keys that begin with "begin" string
  41.      * @param begin
  42.      * @return
  43.      */
  44.     public Set<String> getAllKeys(String begin) {
  45.         return cli.keys(begin+"*");
  46.     }
  47.  
  48.     /**
  49.      * set session values as map
  50.      * @param sess
  51.      * @param m
  52.      * @return
  53.      */
  54.     public boolean setAll(String sess, Map<String, String> m) {
  55.         String r = cli.hmset(sess, m);
  56.         return (r.equals("OK"));
  57.     }
  58.  
  59.     /**
  60.      * set session values as map and set expire the session
  61.      * @param sess
  62.      * @param m
  63.      * @param expire
  64.      * @return
  65.      */
  66.     public boolean setAll(String sess, Map<String, String> m, int expire) {
  67.         boolean rez = false;
  68.         String r = cli.hmset(sess, m);
  69.         long er = cli.expire(sess, expire);
  70.         rez = (r.equals("OK") && er>0);
  71.         return rez;
  72.     }
  73.  
  74.     /**
  75.      * set one of session values
  76.      * @param sess
  77.      * @param key
  78.      * @param value
  79.      * @return
  80.      */
  81.     public boolean set(String sess, String key, String value) {
  82.         boolean rez = false;
  83.         Map m = getAll(sess);
  84.         if (m!=null) {
  85.             m.put(key, value);
  86.             rez = setAll(sess, m);
  87.         }
  88.         return rez;
  89.     }
  90.     
  91.     /**
  92.      * set or update session values and set expire
  93.      * @param sess
  94.      * @param key
  95.      * @param value
  96.      * @param expire
  97.      * @return
  98.      */
  99.     public boolean set(String sess, String key, String value, int expire) {
  100.         boolean rez = false;
  101.         Map m = getAll(sess);
  102.         if (m!=null) {
  103.             m.put(key, value==null ? "" : value);
  104.             rez = setAll(sess, m, expire);
  105.         }
  106.         return rez;
  107.     }
  108.     /**
  109.      * check if session exists
  110.      * @param sess
  111.      * @return
  112.      */
  113.     public boolean isExists(String sess) {
  114.         return cli.exists(sess);
  115.     }
  116.     /**
  117.      * get session values
  118.      * @param sess
  119.      * @return
  120.      */
  121.     public Map<String, String> getAll(String sess) {
  122.         return cli.hgetAll(sess);
  123.     }
  124.  
  125.     /**
  126.      * det session values and prolongs session
  127.      * @param sess
  128.      * @param expire
  129.      * @return
  130.      */
  131.     public Map<String, String> getAll(String sess, int expire) {
  132.         Map m = cli.hgetAll(sess);
  133.         cli.hmset(sess, m);
  134.         cli.expire(sess, expire);
  135.         return m;
  136.     }
  137.  
  138.     /**
  139.      * get one of session values
  140.      * @param sess
  141.      * @param key
  142.      * @return
  143.      */
  144.     public String get(String sess, String key) {
  145.         return cli.hget(sess, key);
  146.     }
  147.  
  148.     /**
  149.      * delete session with all session data
  150.      * @param sess
  151.      * @return
  152.      */
  153.     public boolean del(String sess) {
  154.         Long del = cli.del(sess);
  155.         return del>0;
  156.     }
  157.  
  158.     /**
  159.      * close connection
  160.      */
  161.     public void close() {
  162.       if (cli.isConnected()) {
  163.             try {
  164.                 cli.disconnect();
  165.             } catch (IOException ie) {}
  166.         }
  167.     }
  168. }
Как видно из кода все методы библиотеки есть простые java-обёртки над командами Redis. А все наши методы - вызовы методов библиотеки. В конструктор нужно передать ip-адрес машины на которой запущен Redis-сервер, порт и пароль доступа к серверу. Третьим параметром передаём время ожидания ответа сервера в миллисекундах. Вообще, если есть такая возможность Redis лучше всего поднимать на той же машине, где работает наше приложение. Скорость обработки запросов у Redis на базе из миллиона записей составляет 1-2 миллисекунды. В сравнении с этим потери 20-50 ms на соединение (в лучшем случае) выглядят неприятно. Требования к памяти у Redis весьма скромные.
Использование Redis в качестве сессионного хранилища приятно ещё и тем, что в нём реализован механизм "устаревания" ключей. Достаточно установить срок жизни ключу и сервер сам "приберёт" его через указанное время. В текущей версии была неприятная особенность: после обновления значения ключ становился "вечным". Поэтому мы "напоминаем" ключам время жизни после апдейта, там где это нужно.
Ещё одна особенность едва не испортила впечатление от Jedis: при использовании внутреннего механизма пула соединений в этой библиотеке спустя 5-10 минут работы под нагрузкой система "засыпала", без видимой причины и без каких-либо ошибок прекращая реагировать на запросы. От пула пришлось отказаться, благо высокая скорость обработки запросов делала контроль за числом открытых коннектов задачей чисто "академической" важности.
В целом: инструмент весьма порадовал. После изнурительной борьбы за производительность запросов на "основной" БД скорость ответов от Redis создаёт прямо-таки фантастическое впечатление. А возможность работать с сервером "чем угодно", просто посылая команды на порт так и провоцирует написать свою реализацию клиента.

пятница, 24 декабря 2010 г.

связка Apache+Tomcat

На днях пришлось решать задачу по настройке связки Apache 2+Tomcat 6.02 на Debian 5 сервере. Пришлось в очередной раз порыться в инете в поисках инструкций. Дабы не повторять этот процесс в следующий раз решил выложить тут алгоритм, который получил в итоге.

Итак, дано: VDS с Debian 5, Apache 2 уже установлен, на нём настроены несколько виртуальных хостов. Требуется: в поддиректории одного из сайтов исполнять jsp - страницы средствами Tomcat.

1. Устанавливаем JDK. 

apt-get install python-software-properties 
add-apt-repository ppa:sun-java-community-team/sun-java6 
apt-get update 
apt-get install sun-java6-jdk

Желательно после установки в /etc/profile добавить строки:

JAVA_HOME="/usr/lib/jvm/java-6-sun/"
export JAVA_HOME

2. Устанавливаем Tomcat:

Берём с официального сайта ссылку на последнюю версию и wget-ом вытягиваем её к себе на сервер. Распаковываем и переносим "на место постоянного проживания":

tar -xzvf apache-tomcat-6.0.29.tar.gz
mv apache-tomcat-6.0.29 /opt/tomcat

3. Делаем init-скрипт для запуска/останова/перезапуска Tomcat-а:

в /etc/init.d/tomcat
пишем:

#!/bin/sh
# Tomcat Init-Script
case $1 in
start)
sh /opt/tomcat/bin/startup.sh
;;
stop)
sh /opt/tomcat/bin/shutdown.sh
;;
restart)
sh /opt/tomcat/bin/shutdown.sh
sh /opt/tomcat/bin/startup.sh
;;
esac
exit 0

Ставим на него права 0755 и делаем:
update-rc.d tomcat defaults

4. Настраиваем логин в Tomcat Manager:

редактируем /opt/tomcat/conf/tomcat-users.xml

<tomcat-users>
<role rolename="manager"/>
<role rolename="admin"/>
<user username="USERNAME" password="PASSWORD" roles="admin,manager"/>
</tomcat-users>


5. Проверяем работу "свежеустановленного" сервера:

/etc/init.d/tomcat start
по адресу http://ваш домен:8080/manager/html будет работать админка Tomcat-а.

6. Настраиваем модуль для связи Tomcat и Apache

Устанавливаем модуль для Apache:
aptitude install libapache2-mod-jk

взаимодействие между серверами реализуется с помощью worker-а. Описываем его конфигурацию в файле /etc/apache2/workers.properties:

workers.tomcat_home=/opt/tomcat
workers.java_home=/usr/lib/jvm/java-6-sun
ps=/
worker.list=default
worker.default.port=8009
worker.default.host=localhost
worker.default.type=ajp13
worker.default.lbfactor=1

Тут, как видим, устанавливается имя нашего worker-а: default
Описываем конфигурацию модуля в файле /etc/apache2/conf.d/jk.conf

JkWorkersFile /etc/apache2/workers.properties
JkLogFile /var/log/apache2/mod_jk.log
JkLogLevel error
Перезагружаем сервера:
/etc/init.d/apache2 stop
/etc/init.d/tomcat restart
/etc/init.d/apache2 start

7. Создаём виртуальный хост на Tomcat и привязываем его к выбранному виртуальному хосту на Apache

добавляем в /opt/tomcat/conf/server.xml
<host name="www.testsrv.local" appBase="/var/www/vhost1"
unpackWARs="true" autoDeploy="true">
<context path="" docBase="htdocs" debug="0" reloadable="true"/>
<valve className="org.apache.catalina.valves.AccessLogValve"
directory="/var/www/vhost1/logs" prefix="tomcat_access_" suffix=".log"
pattern="common" resolveHosts="false"/>
</host>

Пути к директориям указываем соответственно те, где будут лежать наши .jsp

В секцию выбранного витуального хоста Apache в /etc/apache2/apache2.conf добавляем строку:

JkMount /j/*.jsp default

где /j/ - путь к директории, где будут лежать .jsp - страницы относительно корня сайта в Apache.

8. Проверяем:

Создаём файл test.jsp в выбранной директории:

<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello World</h1>
Today is: <%= new java.util.Date().toString() %>
</body>
</html>

и заходим по адресу http://ваш домен/j/test.jsp
наблюдаем работающую .jsp страницу, которую выполнил Tomcat по "поручению" Apache :)

суббота, 2 октября 2010 г.

Swing: мелочи..

Писать десктопные приложения на java приходится редко, поэтому постоянно забываю пару мелочей. Запишу их тут, чтобы потом не искать снова.
Во-первых скины: в 21 веке мало кого устраивает стандартный вид swing-интерфейса. Можно, конечно сделать UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) и окошко приобретает привычный вид для нашей операционки. Но можно и поэлегантнее...
Например Substance: скачиваем библиотеку substance.jar, подключаем к проекту и устанавливаем UIManager.setLookAndFeel(new SubstanceGraphiteLookAndFeel()) или любой другой скин из довольно приличного набора. В рантайме потребуется ещё библиотека trident.jar, я нашёл её в исходниках substanse, которые можно скачать там же, на сайте проекта.
Во вторых, позиционирование окна:

  1.     private static void setCenterPosition(JFrame frame) {
  2.         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
  3.         Dimension frameSize = frame.getPreferredSize();
  4.         if (frameSize.height > screenSize.height) {
  5.             frameSize.height = screenSize.height;
  6.         }
  7.         if (frameSize.width > screenSize.width) {
  8.             frameSize.width = screenSize.width;
  9.         }
  10.         frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);
  11.     }

четверг, 1 июля 2010 г.

Освоение криптографии - 3: SHA-1

Симметричное шифрование, которое я "освоил" в двух предыдущих постах иногда является излишним. Например, между приложениями есть https-канал и перехват данных нам не грозит. Но нам грозит другое: данные могут быть подделаны. Обычная схема защиты в таком случае сгенерировать сигнатуру и послать её вместе с данными. Сигнатура при этом получается как результат необратимого шифрования строки, составленной из передаваемых данных и пароля, известного получателю и отправителю. Получатель не расшифровывает сигнатуру (это невозможно), а создаёт её заново: запрос он получил, а пароль он знает. Совпадение сигнатуры с полученной подтверждает что данные не изменились при передаче, а отправитель знает верный пароль.
Ключевой элемент этой схемы - необратимое шифрование. Одним из самых популярных алгоритмов тут является SHA-1.
Итак, задача прежняя: на серверной стороне java. Код тут такой:
  1.     /**
  2.      * Хэширование sha1
  3.      * @param Param строка для хеширования
  4.      * @param Encode
  5.      * @return hex-представление хэш-строки
  6.      * @throws NoSuchAlgorithmException
  7.      * @throws UnsupportedEncodingException
  8.      */
  9.     public static String sha1(String Param) throws NoSuchAlgorithmException, UnsupportedEncodingException {
  10.         MessageDigest SHA = MessageDigest.getInstance("SHA-1");
  11.         SHA.reset();
  12.         SHA.update(Param.getBytes("UTF-8"), 0, Param.length());
  13.         byte[] sha1hash = SHA.digest();
  14.         return bytesToHexStr(sha1hash);
  15.     }
Тут полученный хэш мы сразу представляем в виде hex-строки. Для этого используем метод:
  1.     /**
  2.      * Преобразование байтового массива в hex-строку
  3.      * @param raw
  4.      * @return String
  5.      */
  6.     public static String bytesToHexStr(byte[] raw) {
  7.         char[] kDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
  8.         int length = raw.length;
  9.         char[] hex = new char[length * 2];
  10.         for (int i = 0; i < length; i++) {
  11.             int value = (raw[i] + 256) % 256;
  12.             int highIndex = value >> 4;
  13.             int lowIndex = value & 0x0f;
  14.             hex[i * 2 + 0] = kDigits[highIndex];
  15.             hex[i * 2 + 1] = kDigits[lowIndex];
  16.         }
  17.         return new String(hex);
  18.     }

Теперь аналогичное действие делаем во flex:
  1. var h:IHash = Crypto.getHash("sha1");
  2. var sidn:String = Hex.fromArray(h.hash(Hex.toArray(Hex.fromString("your_secret_string"))));
Тут, как видите, всё совсем просто. Конечно, если использовать правильные библиотеки :)
Я, как и в предыдущих случаях использовал библиотеку as3crypto

вторник, 29 июня 2010 г.

Освоение криптографии - 2: алгоритм Blowfish

Использование DES-шифрования, описанного в предыдущем посте весьма удобно, но оправдано далеко не всегда. Например, если передаваемые данные нужно защитить "с гарантией" или ключ меняется достаточно редко, использовать алгоритм такой стойкости уже нежелательно. Тем более, что смена алгоритма при нынешнем уровне развития платформ не требует больших усилий. Альтернативой устаревшему уже в начале века DES выбираем Blowfish. Он достаточно быстрый и, также являясь симметричным алгоритмом, не требует принципиальной престройки взаимодействия клиента с сервером.
Для реализации шифрования используем те же библиотеки, что и раньше. В случае flex-части вообще изменится только одна строчка кода:
var cipher:ICipher = Crypto.getCipher("blowfish-ecb", key);
В случае java всё немного сложнее...
Во-первых
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("Blowfish");
уже не сработает. SecretKeyFactory не может порождать ключи такого типа. Это решается достаточно просто, ключи делаем так:
key = new SecretKeySpec(keystr.getBytes(), "Blowfish");
Тут keystr - наш секретный пароль. Остальной код оставляем как в прошлом посте. Компилируем, запускаем, и... получаем Exception: Illegal key size. Очевидный путь наращивания длины ключа тут не поможет. Не в моём случае, по крайней мере. Помогло другое: Скачиваем отсюда так называемые "Unlimited Strength Jurisdiction Policy Files". Это 2 jar-ника, которыми можно заменить аналогичные файлы в java-машине, "ослабив" при этом политику безопасности до нужного нам уровня. Файлы, которые следует заменить в моём случае лежат тут: /usr/lib/jvm/java-6-sun/jre/lib/security. Старые, после окончания экспериментов лучше вернуть на место ;)

Освоение криптографии - 1: алгоритм DES

До сих пор как-то не приходилось серьёзно знакомиться с шифрованием данных. Https защищал мои приложения не только от хакеров, но и от необходимости о них думать :). Но вот возникла задача обеспечить защиту трафика "вручную". На сервере Java, клиент - flex. Первый алгоритм с которым я познакомился (практически случайно) - DES. Алгоритм старый, имеет хорошие реализации практически на всех языках. Криптостойкость не абсолютная, как говорит wikipedia, ломается прямым перебором за 2^55 итераций. Правда для этого нужна пара открытой и закриптованной строк. Впрочем, в нашем случае его криптостойкость достаточна. 
Для серверной части используем классы javax.crypto.*. Код примерно такой:


  1. public class DesCrypter {
  2.  
  3.     Cipher ecipher;
  4.     Cipher dcipher;
  5.  
  6.     /**
  7.      * Конструктор
  8.      * @param key секретный ключ алгоритма DES
  9.      * @throws NoSuchAlgorithmException
  10.      * @throws NoSuchPaddingException
  11.      * @throws InvalidKeyException
  12.      */
  13.     public DesCrypter(String keystr) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException {
  14.         DESKeySpec desKeySpec = new DESKeySpec(keystr.getBytes());
  15.         SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
  16.         SecretKey key = keyFactory.generateSecret(desKeySpec);
  17.         ecipher = Cipher.getInstance("DES");
  18.         dcipher = Cipher.getInstance("DES");
  19.         ecipher.init(Cipher.ENCRYPT_MODE, key);
  20.         dcipher.init(Cipher.DECRYPT_MODE, key);
  21.     }
  22.  
  23.     /**
  24.      * Функция шифровнаия
  25.      * @param str строка открытого текста
  26.      * @return зашифрованная строка в формате Base64
  27.      */
  28.     public String encrypt(String str) throws UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException {
  29.         byte[] utf8 = str.getBytes("UTF8");
  30.         byte[] enc = ecipher.doFinal(utf8);
  31.         return new sun.misc.BASE64Encoder().encode(enc);
  32.     }
  33.  
  34.     /**
  35.      * Функция расшифрования
  36.      * @param str зашифрованная строка в формате Base64
  37.      * @return расшифрованная строка
  38.      */
  39.     public String decrypt(String str) throws IOException, IllegalBlockSizeException, BadPaddingException {
  40.         byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
  41.         byte[] utf8 = dcipher.doFinal(dec);
  42.         return new String(utf8, "UTF8");
  43.     }
  44.  
  45. }

На клиентской стороне используем библиотеку as3crypto. С её помощью делаем реализацию шифрования на ActionScript 3.5:


  1. /**
  2. * @param string Cтрока для зашифровки
  3. * @param passString Пароль
  4. */
  5. public static function encryptString(string:String, passString:String):String {
  6.     var key:ByteArray        = Hex.toArray(Hex.fromString(passString+postfix));
  7.     var plainText:ByteArray    = Hex.toArray(Hex.fromString(string));
  8.     var cipher:ICipher = Crypto.getCipher("simple-des-ecb", key);
  9.     cipher.encrypt(plainText);
  10.     return Base64.encodeByteArray(plainText);
  11. }
  12.  
  13. /**
  14. * @param string Cтрока для расшифровки
  15. * @param passString Пароль
  16. */
  17. public static function decryptString(string:String, passString:String):String {
  18.     var key:ByteArray        = Hex.toArray(Hex.fromString(passString+postfix));
  19.     var plainText:ByteArray    = Hex.toArray(string);
  20.     var cipher:ICipher = Crypto.getCipher("simple-des-ecb", key);
  21.     cipher.decrypt(plainText);
  22.     return(Hex.toString(Hex.fromArray(plainText)));
  23. }

Никаких неожиданностей не было, всё заработало сразу. Единственная особенность: функции шифрования работают с битовыми массивами, а передаём мы строки. При создании строки играет роль (часто губительную) кодировка. Чтобы избежать неприятностей с ней, везде используем Base64-кодирование результата.

воскресенье, 27 июня 2010 г.

SmartGWT + Netbeans

После двухнедельного знакомства с flex я получил массу положительных эмоций, но начальство любит apple а apple не любит flash :). Вывод - надо искать другую платформу для Rich Internet Applications. Сегодня разбираюсь со SmartGWT.
Что понадобится для работы:
  • Netbeans - он у нас уже есть, Tomcat ставится вместе с ним, тут тоже порядок.
  • GWT - Google Web Toolkit. Это фреймворк, который позволяет нам писать клиентский код на java, при компиляции превращая его в javascript.
  • Gwt4nb - плагин к Netbeans, который позволяет создавать шаблоны web-приложений с использованием GWT. Ставится прямо из Netbeans (сервис->плагины->доступные плагины)
  • SmartGWT - библиотека виджетов, расширяющая GWT. 
Как это всё собрать вместе и подключить к проекту? Очень просто.

После установки плагина создаём java web application, на последнем этапе мастера отмечаем фреймворк GWT. Нужно будет указать путь к каталогу, где лежат распакованные файлы фреймворка.
Создастся каркас приложения (такой себе helloworld), в который нужно добавить библиотеку smartgwt.jar из каталога, куда распаковали SmartGWT.
Единственная неочевидность процесса - приложение будет корректно компилироваться только если ручками подправить после этого файл Main.gwt.xml в вашем проекте. Нужно добавить строку
<inherits name="com.smartgwt.SmartGwt" />
в блок module.

суббота, 26 июня 2010 г.

Ubuntu 10.04 на ноутбуке Samsung R58plus

Сегодня решил обновить свою Ubuntu 9.4 до версии 10.04 и столкнулся с проблемой. Посте перезагрузки - чёрный экран. При этом, если выбрать при загрузке предыдущее ядро 2.6.31-21, то загрузка, хоть и с ошибками, проходит. Згрузившись, идём в интернет и читаем. В итоге, проблема решается одной командой в консоли:
echo "options radeon modeset=0" > /etc/modprobe.d/ati.conf
После перезагрузки ядро 22 запускается как часы :)

четверг, 10 июня 2010 г.

Flex-разработка: настройка инструментов под Linux

На днях возникло желание освоить Flex... До сих пор писал интерфейсы для web на HTML+JavaScript (c красивостями jQuery) и как-то справлялся. Но тут вдруг надоело... Да и задачи всё время какие-то "интерфейсные" попадаются. Короче - время пришло. И, поскольку мы стойкие сторонники Open Source, правильный выбор свободных инструментов - задача №1.
Условия из которых исходим: Ubuntu Linux 9.10 и стойкая привязанность к IDE для Java: NetBeans, Eclipse. Не люблю пересаживаться на новые окошки под новую задачу.
Ищем плагины под NetBeans. Первое, что обнадёживает, есть Flexbean. Описание установки и возможностей есть на Хабре. Там сразу напугало упоминание проблем с версиями NetBeans выше 6.5, но совершенно напрасно. Плагин стал на 6.9 совершенно спокойно и начал работать "как родной". Общий алгоритм установки:
  • Скачиваем SDK с сайта Adobe и распаковываем (например во ~/flex).
  • Скачиваем плагин и устанавливаем его в NetBeans.
  • Указываем Flexbean, где лежит Flex SDK (Tools -> Flex Platforms -> Add Platform… -> ~/flex).
Теперь можно создавать новые проекты типа Flex project и Flex application и запускать и компилировать их прямо из Netbeans.
Но это было только начало... Плагин этот не реализует подсветку кода mxml-файлов. Про автокомплит, понятно, речь вообще не идёт. И если в PHP я бы с этим смирился, то изучать новый язык постоянно глядя в справочник... нет уж, увольте. Ищем дальше.
Если не обращать внимание на Windows-решения, осталось не так много вариантов. Flash Builder от Adobe не свободный продукт, но альфа-версии под Linux можно использовать бесплатно. Он скачивается в виде бинарного инсталлятора, содержит в себе SDK и flash player, требует наличия Eclipse, т.к. является его плагином. Проблем в работе версии 3.5 не заметил, но есть особенность: плагин спокойно ставится на все версии Eclipse (заявлена поддержка начиная с 3.3), но работать удалось заставить только с версией 3.3.2. Пришлось откатиться на пару версий назад, что поделаешь...
На этом проблемы не закончилсь. Flash Builder устанавливает player 9 версии для Firefox, в котором есть крайне неприятный баг: кириллица при вводе в текстовые поля отображается в битой кодировке. Этому багу уже не один год, и написаны даже несколько ActionScript - заплаток для его исправления. Я решил проблему, заменив player на версию 10.1 (она пока ещё RC, но уже с бэты работает стабильно). Замена player-а описана тут. Обязательно перед установкой нужно снести старую версию.
Итак на выходе получили: можно удобно писать mxml - разметку и Action Script - классы (правда пока только 3 версии, но этого в принципе хватает для начала). Есть все фичи Eclipse, а чего нет, всегда можно добавить. Есть нормальная среда для тестирования и отладки. Осталось освоить эту технологию. Об этом - в следующих постах.

О чём?

Этот блог о решённых проблемах, найденных выходах и тому подобном. Я по роду деятельности часто изучаю новые технологии, ищу решения для тех или иных задач программирования. Всемирная паутина почти всегда выручает меня, подбрасывая идеи, уроки, готовые исходники. Пора, по-моему вернуть ей часть долга. Может быть в этом блоге кто-то найдёт решение или идею и для себя.
Если материала соберётся достаточно и он будет интересен - перенесу на отдельный сайт, а пока - добро пожаловать :)