четверг, 15 декабря 2011 г.

Quartz + Spring

В больших проекта часто бывает необходимо выполнять какой-то участок кода по расписанию. Для этого подойдет популярная библиотека Quarz.
Рассмотрим, как можно интегрировать Quartz (1.x) в Spring.

Для начала подхватим зависимость в maven:

    org.quartz-scheduler
    quartz
    1.8.5   


Создадим бин, отвечающий за выполнение задачи:
@Component
public class SomeWorkBean implements InitializingBean, DisposableBean {
    @Override
    public void afterPropertiesSet() {
    }

    public void doWork() {
        System.out.println("doing some work");
    }

    @Override
    public void destroy() throws Exception {
    }
}

теперь можно конфигурировать Spring:
   





    
    



    
       
      



    
        
            
        
    
    
        
            
        
    


где workJob - обьявление JobDetailBean,
simpleTrigger - триггер, обьявляющий критерии выполнения кода в SomeWorkBean,
schedulerFactoryBean - фабрика для создания шедьюлера

Все, теперь метод SomeWorkBean.doWork() будет выполняться каждую минуту. Для более гибкого задание расписания, можно использовать другие триггеры, например CronTrigger

четверг, 1 декабря 2011 г.

Алгоритмы сортировки

Сортировки сравнением (Comparison sorts)


Сортировки сравнением имеют минимальную сложность O(n*lg n).

Сортировка вставками (Insert sort)


Сложность: O(n2)

На каждом шаге алгоритма выбираем очередной элемент входных данных и сдвигаем его влево до тех пор, пока слева от него не останутся элементы, меньшие чем он. Метод выбора очередного элемента из исходного массива произволен; может использоваться практически любой алгоритм выбора. Обычно (и с целью получения устойчивого алгоритма сортировки), элементы выбираются по порядку их появления во входном массиве.

Преимущества: практически не требует выделения памяти (всего O(1)); алгоритм эффективен при малом размере входного массива.

Недостатки: неэффективен на большом размере входного массива (более 100).

Алгоритм:
Вход: массив A, состоящий из элементов A[1], A[2], ..., A[n]

for i = 2, 3, ..., n:  
    key = A[i]
    j = i - 1
    while j > 0 and A[j] > key:
        A[j + 1] = A[j]
        j = j - 1
    A[j + 1] = key

Реализация на Java:
//insertion sort
public static void insertionSort(int[] arr) {
    int i, j, tmp;
    //двигаемся вперед по массиву
    for (i = 1; i < arr.length; i++) {
        j = i;
        //двигаем элемент назад, пока он больше предыдущих
        while (j > 0 && arr[j - 1] > arr[j]) {
            tmp = arr[j];
            arr[j] = arr[j - 1];
            arr[j - 1] = tmp;
            j--;
        }
    }
}

суббота, 12 ноября 2011 г.

Установка Etherpad на Ubuntu со Scala 2.8.1

Для работы Etherpad 1.x требуется Sun JDK, Scala 2.8.1

Сборку Etherpad под scala 2.8.1 можно скачать здесь

Установка Sun JDK
sudo add-apt-repository "deb http://archive.canonical.com/ lucid partner"
sudo apt-get update   
sudo apt-get install sun-java6-jdk
sudo update-alternatives --config java

установка mysql
mysql -u root -p
create database etherpad;
grant all privileges on etherpad.* to 'etherpad'@'localhost' identified by 'passwd';
quit

установка scala
sudo apt-get install scala
wget http://www.scala-lang.org/downloads/distrib/files/scala-2.8.1.final.tgz
sudo tar -C /opt/ -xvzf scala-2.8.1.final.tgz
PATH="$PATH:/opt/scala-2.8.1.final/bin"

Скачанный Эзер кладем в /usr/local/etherpad

не забудем добавить переменные среды
export JAVA_HOME="/usr/lib/jvm/java-6-sun"
export SCALA_HOME="/opt/scala-2.8.1.final"
export SCALA_LIBRARY_JAR="/opt/scala-2.8.1.final/lib/scala-library.jar"
export MYSQL_CONNECTOR_JAR="/usr/share/java/mysql-connector-java-5.1.10.jar"
export JAVA="$JAVA_HOME/bin/java"
export SCALA="/opt/scala-2.8.1.final/bin/scala"
export PATH="$JAVA_HOME/bin:$PATH"

после всех приготовлений можно собирать Эзер
bin/rebuildjar.sh clearcache

перед запуском подправить etheprad/etherpad/etc/etherpad.localdev-default.properties
мои настройки:
alwaysHttps = false
ajstdlibHome = ../infrastructure/framework-src/modules
appjetHome = ./data/appjet
devMode = true
#etherpad.SSOScript = /usr/local/structures/bin/etherpad.validator
etherpad.adminPass = ethe4RiveriBUYTDEFbzriutb7BIoxboyd7U
etherpad.fakeProduction = false
etherpad.isProduction = false
etherpad.proAccounts = true
etherpad.SQL_JDBC_DRIVER = com.mysql.jdbc.Driver
etherpad.SQL_JDBC_URL = jdbc:mysql://localhost:3306/etherpad
etherpad.SQL_PASSWORD = passwd
etherpad.SQL_USERNAME = etherpad
hidePorts = true
listen = 9000
#listenSecure = 9001
#sslKeyPassword = KEYPASS_CHANGEME
#sslKeyStore = ./data/appjet/keystore
#sslStorePassword = KEYPASS_CHANGEME
logDir = ./data/logs
modulePath = ./src
motdPage = /ep/pad/view/ro.3PfHCD0ApLc/latest?fullScreen=1&slider=0&sidebar=0
topdomains = mydomain.ru, localhost
transportPrefix = /comet
transportUseWildcardSubdomains = true
useHttpsUrls = false
useVirtualFileRoot = ./src
theme = default
etherpad.soffice = /usr/bin/soffice
customBrandingName = etherpad
customEmailAddress = etherpad@etherpad.com
showLinkandLicense = false
smtpServer = 192.168.0.1:25

запуск
bin/run-local.sh&

в дальнейшем можно добавить скрипт в init.d для автоматического запуска Эзера и экспорта переменых среды

пятница, 11 ноября 2011 г.

Чтение и запись Tab Separated Values файлов

Что же представляют из себя файлы в формате TSV? Это текстовые файлы, представляющие собой набор строк. В каждой строке одинаковое количество "полей" определенного формата, разделенные символом табуляции ("\t"). Похоже на некое подобие таблицы.

Итак, приступим к написанию reader-а:
/**
 * @author vie
 *
 * TSVReader.java
 * Class for read tab separated values files
 */
public class TSVReader<T> {

    private final String filePath;

    public TSVReader(String filePath) {
        this.filePath = filePath;
    }

    /**
     * This method reads tab separated values file
     * into object of the arbitrary class
     *
     * @param clazz type of transmitted class
     * @return List
     * @throws com.site.tsv.exceptions.TSVException
     */
    public List<T> read(Class<T> clazz) throws TSVException {
        List<T> result = new LinkedList<T>();

        try {
            BufferedReader bReader = new BufferedReader(new FileReader(filePath));
            String line;

            //recieving all lines
            int i = 1;
            while ((line = bReader.readLine()) != null) {
                //recieving all line members
                String datavalue[] = line.split("\t");

                //create instance of line entity
                Constructor constructor = clazz.getConstructor(null);
                Object somethingInstance = constructor.newInstance(null);

                //parsing line entity members
                Field[] fields = clazz.getDeclaredFields();

                //recieve map of line entity member types
                Map<Integer, Class<?>> mapFields = defineFields(clazz);

                //for each line entity field
                if (fields != null && datavalue != null && fields.length == datavalue.length) {
                    for (int j = 0; j < fields.length; j++) {
                        //for each line entity field
                        String s = fields[j].getName();
                        s = s.substring(0, 1).toUpperCase() + s.substring(1, s.length());

                        //exec line entity setter methods
                        Method method = clazz.getMethod("set" + s, mapFields.get(j));
                        method.invoke(somethingInstance, getDeterminatedValue(datavalue[j], fields[j].getType()));
                    }
                } else if (fields != null && datavalue != null && fields.length != datavalue.length) {
                    throw new TSVInvalidFileException("Count of formatted file fields on line " + i + " not equal count of entity fields");
                }

                i++;
                result.add((T) somethingInstance);
            }
            bReader.close();
        } catch (FileNotFoundException e) {
            throw new TSVException(e);
        } catch (IOException e) {
            throw new TSVException(e);
        } catch (ParseException e) {
            throw new TSVInvalidFileException(e);
        } catch (NumberFormatException e) {
            throw new TSVInvalidFileException(e);
        } catch (NoSuchMethodException e) { //reflect
            throw new TSVException(e);
        } catch (InstantiationException e) {
            throw new TSVException(e);
        } catch (IllegalAccessException e) {
            throw new TSVException(e);
        } catch (InvocationTargetException e) {
            throw new TSVException(e);
        }
        return result;
    }

    /**
     * Returns field names and types
     *
     * @param clazz type of transmitted class
     * @return Map<string,integer>.
     * @throws com.site.tsv.exceptions.TSVIncorrectAnnotatedFieldsException
     */
    private Map<Integer, Class<?>> defineFields(Class<?> clazz) throws TSVIncorrectAnnotatedFieldsException {
        Map<Integer, Class<?>> map = new HashMap<Integer, Class<?>>();

        Field[] fields = clazz.getDeclaredFields();
        int i = 0;
        for (Field field : fields) {
            if (field.isAnnotationPresent(TSVInteger.class)) {
                map.put(i++, Integer.class);
            } else if (field.isAnnotationPresent(TSVDouble.class)) {
                map.put(i++, Double.class);
            } else if (field.isAnnotationPresent(TSVString.class)) {
                map.put(i++, String.class);
            } else if (field.isAnnotationPresent(TSVDate.class)) {
                map.put(i++, Date.class);
            }
        }

        if (fields.length == 0 || fields.length != map.size()) {
            throw new TSVIncorrectAnnotatedFieldsException("Some field not annotated or count of fields = 0!");
        }

        return map;
    }

    /**
     * Returns converted to the native type field
     *
     * @param original value of obtained field
     * @param needType type of transmitted class
     * @return Object
     * @throws java.text.ParseException
     */
    private Object getDeterminatedValue(String original, Class<?> needType) throws ParseException {
        if (needType.getName().equals("java.lang.Integer")) {
            return Integer.parseInt(original);
        } else if (needType.getName().equals("java.lang.Double")) {
            return Double.parseDouble(original);
        } else if (needType.getName().equals("java.lang.String")) {
            return original;
        } else if (needType.getName().equals("java.util.Date")) {
            SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy");
            return formatter.parse(original);
        } else {
            return null;
        }
    }
}
В классе я использовал generics, чтобы проверять заданного типы entity на этапе компиляции. В преобразовании строк с различными типами данных в entity объект мне помог reflection. Для разбора с его помощью entity, написаны приведенные ниже аннотации. Также я создал несколько исключений, чтобы в при выпадении ошибок можно было быстро понять что свалилось, будь то reflection, IO или преобразование чисел и дат.

среда, 9 ноября 2011 г.

Разделение выполнения задачи на n потоков

Бывает при решении некоторых задач, таких как загрузка данных с FTP приходится разделять ход выполнения задачи на несколько потоков.
Казалось бы, все просто. Создаем n-е количество потоков (каждый из которых делает часть общей работы) и запускаем их:
public class MainClass {
  public static void main(String[] args) {
    WorkClass work = new WorkClass();
    work.doSomeWork();
  }
}

public class WorkClass {

  public void doSomeWork() {             
    for (int i = 0; i < 5; i++) { 
      Worker worker = new Worker("directory" + i);
      Thread thread = new Thread(worker);
      thread.start();
    }
    
    //here we must wait for execution of all threads

    //doing work on    
  }

  public class Worker implements Runnable {

    private String dir;

    public Worker(String dir) {
      this.dir = dir;
    }
  
    @Override
    public void run() {
      doPieceOfWork();
    }

    private void doPieceOfWork() {
      //doing loading from FTP, depending on the name of directory ...
    }
  }
}
Здесь в качестве примера разделения выполняемой работы на 5 потоков приводится загрузка по FTP. Каждый поток грузит свою папку. Но может возникнуть проблема. Надо подождать, пока выполнятся все запущенные потоки, и только после этого продолжать работу основного потока. Для этого обернем вызов потоков в блок синхронизации.
public void doSomeWork() {
  synchronized (this) {             
    for (int i = 0; i < 5; i++) { 
      Worker worker = new Worker("directory" + i);
      Thread thread = new Thread(worker);
      thread.start();
    }
  }
}
Далее остановим главный поток после запуска второстепенных потоков до тех пор, пока не получим ответ о завершении работы каждого из них. Для этого сделаем цикл, в котором получаем уведомление от одного из потоков и, если количество завершенных второстепенных потоков меньше количества запущенных, продолжаем ожидать ответа от оставшихся:
public void doSomeWork() {
  synchronized (this) { 
    Vector events = new Vector();
            
    for (int i = 0; i < 5; i++) {       
      Worker worker = new Worker(this, "directory" + i, events);
      Thread thread = new Thread(worker);
      thread.start();
    }

    //if all threads are dead, go ahead
    while (true) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        errorLogger.error(e.getMessage(), e);
      }
      if (events.size() == 5) {
        break;
      }
    }        
  }

  //doing work on
}
теперь надо добавить во второстепенные потоки уведомления, которые будет получать основной поток:
public class Worker implements Runnable {

  private String dir;

  private Vector events;

  public Worker(WorkClass monitor, String dir, Vector events) {
    this.dir = dir;
    this.events = events;
  }
  
  @Override
  public void run() {
    doPieceOfWork();
  }

  private void doPieceOfWork() {
    //doing loading from FTP, depending on the name of directory ...
    
    synchronized (monitor) {
      events.add(new Object());
      monitor.notify();      
    }
  }
}
Cледует обратить внимание, что блоки синхронизации во второстепенных потоках и в главном создаются по монитору work класса WorkClass. Также в класс Worker добавлена коллекция events, которая выполняет роль счетчика завершенных потоков. Класс Vector выбран не случайно, тк его методы синхронизированы. В качестве его альтернативы можно воспользоваться Collections.synchronizedList(new ArrayList()). Хотя в данном случае синхронизация методов коллекции и не будет играть никакой роли, тк работа с ней происходит в блоке синхронизации. Все, теперь метод doSomeWork() разделит загрузку по FTP на 5 потоков и продолжит свою работу после завершения всех запущенных потоков. Общий результат выглядит так:
public class MainClass {
  public static void main(String[] args) {
    WorkClass work = new WorkClass();
    work.doSomeWork();
  }
}

public class WorkClass {
  protected static final Logger errorLogger = LoggerFactory.getLogger("errorLogger");

  public void doSomeWork() {
    synchronized (this) {     
      Vector events = new Vector();
        
      for (int i = 0; i < 5; i++) { 
        Worker worker = new Worker(this, "directory" + i, events);
        Thread thread = new Thread(worker);
        thread.start();
      }
      
      //if all threads are dead, go ahead
      while (true) {
        try {
          this.wait();
        } catch (InterruptedException e) {
          errorLogger.error(e.getMessage(), e);
        }
        if (events.size() == 5) {
          break;
        }
      }
    }
    
    //doing work on 
  }

  public class Worker implements Runnable {

    private String dir;

    private Vector events;

    public Worker(WorkClass monitor, String dir, Vector events) {
      this.dir = dir;
      this.events = events;
    }
  
    @Override
    public void run() {
      doPieceOfWork();
    }

    private void doPieceOfWork() {
      //doing loading from FTP, depending on the name of directory ...
    
      synchronized (monitor) {
        events.add(new Object());
        monitor.notify();        
      }
    }
  }
}

понедельник, 31 октября 2011 г.

Maven Shade Plugin

Shade плагин как альтернатива Assembly.

На днях используя Assembly, столкнулся со следующей проблемой. Приложение со Spring собрал в jar. При запуске валилась ошибка:
Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException:
Configuration problem: Unable to locate Spring NamespaceHandler
for XML schema namespace [http://www.springframework.org/schema/context]
at org.springframework.beans.factory.parsing.FailFastProblemReporter.
error(FailFastProblemReporter.java:68)
at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)
...

Это означает что при копировании в META-INF спринговых файлов spring.schemas и spring.handlers, они перезатираются для каждой spring библиотеки.

Assebly плагин выглядел так:

    maven-assembly-plugin
    2.2.1
    
        false
        
            jar-with-dependencies
        
        
            
                com.mypackage.MainClass
            
        
    


Немного погуглив, нашел вариант с обьявлением своего assembly дескриптора, или со скачиванем all-in-one Spring библиотеки. Но на stackoverflow посоветовали использовать плагин Shade.

Обьявление Shade:

    org.apache.maven.plugins
    maven-shade-plugin
    
        
            package
            
                shade
            
            
                
                    
                        com.mypackage.MainClass
                    
                    
                        META-INF/spring.handlers
                    
                    
                        META-INF/spring.schemas
                    
                
                
                    
                        *:*
                        
                            META-INF/*.SF
                            META-INF/*.DSA
                            META-INF/*.RSA
                        
                    
                
            
        
    


Все. После этого спокойно собираем jar командой package и никаких проблем.

среда, 28 сентября 2011 г.

RESTful веб-сервис на Jersey + Spring 3

Пишем простой REST веб-сервис.

Как известно, начиная с третьей версии у Spring появилась возможность легко создавать REST веб сервис с помощью Spring MVC (на аннотациях @RequestMapping). Но я хочу рассмотреть классический Jax-RS с помощью Jersey. Итак, начнем.

Для начала определим maven зависимости:




    com.sun.jersey
    jersey-server
    1.9.1




    com.sun.jersey.contribs
    jersey-spring
    1.9.1
    
        
            org.springframework
            spring
        
        
            org.springframework
            spring-core
        
        
    



    com.sun.xml.bind
    jaxb-impl
    2.1.9

exclusions в зависимости jersey+spring необходимы потому, что библиотека требует зависимости спринга версий 2 или 2.5. А мы ей их дать не можем, тк используем третью версию. В исключениях требуется указать все библиотеки Spring, которые у нас есть в проекте.

Теперь можно писать сам сервис. Будем доставать продукты по их id:
@Path("/product/{id}")
@Component
@Scope("prototype")
public class ProductRSController {

    @Autowired
    private ProductService srv;

    @GET
    @Produces(MediaType.APPLICATION_XML)
    public Product getDescription(@PathParam("id") String productId) {
        Product product = srv.getProduct(productId);
        return product;
    }
}
Пометив метод с помощью аннотаций @GET и @Produces(MediaType.APPLICATION_XML), мы указали, что url вида http://myserver.com/myapp/rs/product/123 (где myserver.com - наш сервер, myapp - имя развернутого приложения), будет возвращать в ответ сериализованный обьект Product в xml.

Класс ProductService представляет собой сервис бин, с методом Product getProduct(String productId) {...}

Не забудем настроить web.xml:

    contextConfigLocation/WEB-INF/spring-context.xml


    org.springframework.web.context.ContextLoaderListener



    Jersey Web Application
    com.sun.jersey.spi.spring.container.servlet.SpringServlet
    1



    Jersey Web Application
    /rs/*

и spring-context.xml:




Теперь можно разворачивать приложение и зайти по URL http://myserver.com/myapp/rs/product/123. В случае успеха мы увидим полученный XML, если конечно у нас есть Product с id = 123.

Теперь можно создавать клиент веб-сервиса. Для удобства работы, возьмем клиент от Jersey.

зависимости:

    com.sun.xml.bind
    jaxb-impl
    2.1.9



    com.sun.jersey
    jersey-client
    1.9.1


сам клиент будет выглядеть так:
public class RESTClient {

    public Product recieveObject() {
        Client c = Client.create();
        WebResource r = client.resource("http://myserver.com/myapp/rs/");
        Product product = response = r.path("product/123").
                accept(MediaType.APPLICATION_XML_TYPE).
                get(Product.class);

        return product;
    }
    
    public String recieveXML() {
        Client c = Client.create();
        WebResource r = client.resource("http://myserver.com/myapp/rs/");
        String product = response = r.path("product/123").
                accept(MediaType.APPLICATION_XML_TYPE).
                get(String.class);

        return product;
    }
}
метод recieveObject() возвращает десериализованный обьект Product, а метод recieveXML() возвращает XML.

С помощью Generics можно сделать клиент универсальным:
public class RESTClient {

    private static final Client client;

    private String resource = "http://myserver.com/myapp/rs/";

    static {
        client = Client.create();
    }

    public RESTClient() {
    }

    public RESTClient(String resource) {
        this.resource = resource;
    }

    public void recieve() {

        //object
        Product product = recieve(Product.class, "product/", "123");
        System.out.println("response.getProductId: " + product.getProductId());
        
        //string 
        String xml = recieve(String.class, "product/", "123");
        System.out.println("response: " + xml);
    }

    private <T> T recieve(Class<T> type, String URI, String id) {
        WebResource r = client.resource(resource);

        T response = r.path(URI + id).
                accept(MediaType.APPLICATION_XML_TYPE).
                get(type);

        return response;
    }
}


Для настройки вывода данных в формате JSON необходимо проделать следующее:

Добавить зависимость:

            com.sun.jersey
            jersey-json
            1.10
        

Создать класс JAXBContextResolver в том же package, где лежит контроллер
@Provider
public final class JAXBContextResolver implements ContextResolver<jaxbcontext> {

    private final JAXBContext context;

    private final Set<class> types;

    private final Class[] cTypes = {MyEntity1.class, MyEntity1.class};

    public JAXBContextResolver() throws Exception {
        this.types = new HashSet(Arrays.asList(cTypes));
        this.context = new JSONJAXBContext(JSONConfiguration.natural().build(), cTypes);
    }

    @Override
    public JAXBContext getContext(Class<?> objectType) {
        return (types.contains(objectType)) ? context : null;
    }
}
где MyEntity1, MyEntity2 - отображаемые в JSON обьекты


и в контроллере указывать формат данных @Produces("application/json; charset=utf-8")

среда, 7 сентября 2011 г.

Scala под windows 7. ошибка при запуске компилятора.

После установки Scala 2.9.0 на win 7 столкнулся с проблемой. (С предыдущими версиям scala все нормально)

при запуске команды scalac -version неожиданно вываливается Эксепшн:

Exception in thread "main" java.lang.NoClassDefFoundError: scala/tools/nsc/MainGenericRunner
Caused by: java.lang.ClassNotFoundException: scala.tools.nsc.MainGenericRunner
        at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Could not find the main class: scala.tools.nsc.MainGenericRunner.  Program will exit.

Начал разбираться в чем проблема. Оказалось, что при запуске scala.bat тоже самое.

Путем трейса файлов scalac.bat и scala.bat установил переменная _SCALA_HOME
в них неверная:
_SCALA_HOME c:\Program Files\Files\scala\bin\..

Хотя Переменная среды проставлена правильно:
c:\Program Files\scala\bin\..

В итоге пришлось в файлах scala.bat и scala.sh исправить строчки:

:set_home
  set _BIN_DIR=
  rem for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi
  rem set _SCALA_HOME=%_BIN_DIR%..
  set _SCALA_HOME=%~dps0..
goto :eof

здесь заккоментировал:
for %%i in (%~sf0) do set _BIN_DIR=%_BIN_DIR%%%~dpsi
set _SCALA_HOME=%_BIN_DIR%..

и добавил
set _SCALA_HOME=%~dps0..

суббота, 27 августа 2011 г.

AspectJ + Maven + Spring + IDEA

Как заставить работать AspectJ с Maven и Spring.

Подцепим зависимости maven:

    org.springframework
    spring-aop
    ${spring.version}
    
        
            org.aspectj
            aspectjweaver
        
    



    org.springframework
    spring-aspects
    ${spring.version}
    
        
            org.aspectj
            aspectjweaver
        
    





    org.aspectj
    aspectjrt
    ${aspectj.version}


Для выполнения aspectj компиляции добавим плагин в maven:

    org.codehaus.mojo
    aspectj-maven-plugin
    1.1
    
        1.6
        1.6
        true
        ignore
        
            
                org.springframework
                spring-aspects
            
        
        false
    
    
        
            
                compile
                test-compile
            
        
    


Все, проект можно собирать.
Для выполнения приложения прямо из IDEA надо поставить галку "Run Maven Goal" в Run -> Edit Configurations -> Before Launch и выбрать в ней плагин aspectj:compile

суббота, 9 июля 2011 г.

Singleton и Prototype scope в Spring

Singleton scope означает что экземпляр бина создается один раз, при инициализации контекста. Не стоит сравнивать этот scope со stateless bean в EJB. EJB контейнер при каждом обращении выдает новый экземпляр бина. Поэтому в stateless бине состояние не сохраняется. Таким образом Singleton scope в Spring - тоже statefull. В случае со scope Prototype, можно создавать его новые экземпляры, когда это надо, с помощью фабрики. (statefull)

В обьявлении этих бинов нет ничего сложного:



Но проблема появляется, когда нужна зависимость SingletonBean от PrototypeBean. В этом случае можно поступить несколькими способами:
ApplicationContextAware или BeanNameAware method injection; Lookup method injection; Arbitrary method replacement.
Расскажу о самом простом (по крайней мере для меня) из них - аналоге BeanNameAware.

PrototypeBean может представлять из себя что угодно. В данном контексте нам это не важно, так что будем рассматривать его просто как POJO класс:
public class PrototypeBean {
...
}

а в SingletonBean используем фабрику org.springframework.beans.factory.ObjectFactory:
public class SingletonBean {

  private ObjectFactory factory;

  public void createPrototypeBean() {
     //здесь можем получить сколько угодно экземпляров PrototypeBean
     PrototypeBean bean = factory.getObject();
  }

  ...

  public void setFactory(ObjectFactory beanFactory) throws BeansException {
     this.factory = beanFactory;
  }
}

Чтобы явно указать ObjectFactory экземпляр какого бина создавать, в конфигурации пропишем ObjectFactory как property Singleton-бина:



  
    
      
        
      
    
  

суббота, 25 июня 2011 г.

Установка Tomcat под Ubuntu

Для начала установливаем openjdk вместе с пакетом ubuntu-restricted-extras:
sudo apt-get install ubuntu-restricted-extras
установленная версия: 1.06_22

качаем томкат:
wget http://apache.cyberuse.com/tomcat/tomcat-7/v7.0.16/bin/apache-tomcat-7.0.16.tar.gz

распаковываем:
tar xvzf apache-tomcat-7.0.16.tar.gz

копируем в нужную папку:
sudo mv apache-tomcat-7.0.16 /usr/local/tomcat

также необходимо прописать переменные окружения JAVA_HOME и JDK_HOME. Для этого добавляем в файле /etc/environment:

JDK_HOME="/usr/lib/jvm/java-6-openjdk"
JAVA_HOME="/usr/lib/jvm/java-6-openjdk"

запускаем томкат:
sudo /usr/local/tomcat/bin/catalina.sh start

редактируем server.xml:
sudo nano /usr/local/tomcat/conf/server.xml

ищем следующий текст:

меняем порт на стандартный для http - 80, и указываем кодировку для URI:

создаем файл автозагрузки
sudo nano /etc/init.d/tomcat

и вставляем следующее:
# Tomcat auto-start
#
# description: Auto-starts tomcat
# processname: tomcat
# pidfile: /var/run/tomcat.pid

export JAVA_HOME=/usr/lib/jvm/java-6-openjdk

case $1 in
start)
       sh /usr/local/tomcat/bin/startup.sh
       ;;
stop)  
       sh /usr/local/tomcat/bin/shutdown.sh
       ;;
restart)
       sh /usr/local/tomcat/bin/shutdown.sh
       sh /usr/local/tomcat/bin/startup.sh
       ;;
esac   
exit 0

далее сделаем этот скрипт запускаемым:
sudo chmod 755 /etc/init.d/tomcat

для проверки работы запустим tomcat:
sudo /etc/init.d/tomcat start

сервер должен запуститься на 80 порту. Для остановки используем следующую команду:
sudo /etc/init.d/tomcat stop

создадим ссылки на скрипт для автоматического запуска и останова:
sudo update-rc.d tomcat defaults

перезагружаем сервер:
sudo shutdown -r now

в конце надо не забыть создать пользователя tomcat с ролями admin-gui и manager-gui в conf\tomcat-users.xml:



четверг, 9 июня 2011 г.

Double checked locking

Шаблон проектирования "Блокировка с двойной проверкой" используется в многопоточном программировании. Это один из излюбленных вопросов на собеседованиях. Звучит он обычно как просьба написать синглетон, корректно работающий в многопоточном режиме.

Рассмотрим принцип паттерна на примере Синглетона.
// Не работает в Java 1.4 и более ранних версиях из-за семантики volatile
class Foo {
  private volatile Helper helper = null;  

  public Helper getHelper() {
    if (helper == null) {          //блок проверки, для ускорения производительности
      synchronized(this) {         //блок синхронизации
        if (helper == null)        //если обьект еще не создан, то создаем его
          helper = new Helper();
      }
    }
    return helper;
  }
}

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

Модификатор volatile появился начиная с версии Java 1.5. Он позволяет корректно обработать запись в переменную в многопоточном режиме.

среда, 11 мая 2011 г.

Typesafe Enum pattern или как обойтись без Enum-ов.

Часто нужда в использовании Enum возникает в случае, если необходимо передать в метод одно из значений, например "green", "red" или "blue". Первое что приходит в голову - это принимать String:
public void method(String param) {...}

Но в таком случае в качестве параметра может быть принята любая строка. В соответствии с принципом, что все потенциальные ошибки по возможности надо переносить на момент компиляции, такой вариант неприемлем. Тут и приходит на помощь Enum:

public enum TypesafeEnum {GREEN, RED, BLUE};

А можно ли обойтись без него? Конечно можно, ведь Enum появились только с java 1.5. Тут нам может помочь паттерн Typesafe Enum:
public class TypesafeEnum {

  private final String name;

  private TypesafeEnum(String name) {
    this.name = name;
  }

  public static final TypesafeEnum GREEN = new TypesafeEnum("green");
  public static final TypesafeEnum RED = new TypesafeEnum("red");
  public static final TypesafeEnum BLUE = new TypesafeEnum("blue");

  public String toString() {
    return name;
  }
}
Теперь можно обьявить наш метод таким образом:
public void method(TypesafeEnum param) {...}
соответственно его вызов:
someObject.method(TypesafeEnum.RED);


Подробнее о паттерне здесь

пятница, 15 апреля 2011 г.

Паттерн Entity-Attribute-Value (EAV)

Паттерн "Entity-Attribute-Value" может пригодиться при проектировании базы данных товаров. Рассмотрим пример на СУБД Oracle.

В простейшем случае структура базы данных будет выглядеть следующим образом:
-- Create table
create table entity
(
  entity_id number,
  name      varchar2(50)
);
-- Create/Recreate primary, unique and foreign key constraints 
alter table entity
  add constraint xpk_entity_id primary key (ENTITY_ID);
Таблица Entity хранит сами сущности - товары. В качестве полей указываем общие свойства сущностей, таких как Название товара
-- Create table
create table attribute
(
  attribute_id   number,
  attribute_name varchar2(50),
  attribute_type varchar2(10),
  decriptions    clob
);
-- Create/Recreate primary, unique and foreign key constraints 
alter table attribute
  add constraint xpk_attribute_id primary key (ATTRIBUTE_ID);
Таблица Attribute хранит различные свойства товаров - атрибуты. В качестве полей указываем название атрибута, тип.
-- Create table
create table value
(
  attribute_id   number,
  entity_id      number,
  string_value   varchar2(100),
  number_value   number
);
таблица value - для хранения значений атрибутов, а также для связи многие ко многим между товарами и их атрибутами.

Реализация разных типов данных может быть реализована различными способами. Например вместо одного поля value можно создать разные поля под разные типы данных: string_value, number_value и использовать нужное исходя из типа атрибута из таблицы attribute.

Более подробная информация по паттерну

суббота, 9 апреля 2011 г.

Simple ORM

Наверное многие начинающие java программисты задумывались над тем, как неудобно писать слой взаимодейстия с базой данных на чистом JDBC. Ctrl+c, Ctrl+v и изменение названия таблиц и параметров. Все это приводит к излишнему дублированию кода. Конечно можно сразу взять тот же Hibernate или любую другую реализацию JPA. Но для небольших и простых проектов, имхо это как "из пушки по воробьям". К тому же считаю что надо один раз написать ORM самому, чтобы понять основы его работы.

В этой статье рассмотрим создание своего простого ORM на примере работы с СУБД Oracle.

Создадим интерфейс Persistence и реализующий его класс PersistenceOraImpl - собственно сам персистенс Api для работы с БД.

Список методов в Persistence:

1. selectAll - выбирает все записи из таблицы;
2. select - выбирает записи по заданным параметрам;
3. selectRow - выбирает одну запись по заданным параметрам;
4. insert - вставляет запись;
5. update - обновляет запись;
6. delete - удаляет запись.

Сам интерфейс выглядит так:
public interface Persistence {

 public List selectAll(Class<?> clazz);

 public List select(Class<?> clazz, Map<String, Object> params);

 public Object selectRow(Class<?> clazz, Map<String, Object> params);

 public void insert(Object obj);

 public void update(Object obj);

 public void delete(Class<?> clazz, Map<String, Object> params);

 public Connection getConnection();

 public void setConnection(Connection conn);
}

вторник, 5 апреля 2011 г.

Сериализация обьекта

отправка обьекта:
Message message = new Message();
 ByteArrayOutputStream fos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(fos);
 oos.writeObject(message);
 oos.flush();
 oos.close();

 byte[] messageBodyBytes = fos.toByteArray();
где Message - POJO обьект.

Далее обьект можно записать в файл или отправить в сообщении.

прием обьекта:
ByteArrayInputStream fis = new ByteArrayInputStream(messageBodyBytes);
 ObjectInputStream oin = new ObjectInputStream(fis);
 Message mess = (Message) oin.readObject();

среда, 30 марта 2011 г.

Настраиваем поддержку русского языка в Linux Ubuntu

Открываем шелл: ALT+F2.

Обновляем репозиторий:
sudo apt-get update
Качаем саппорт языка, заодно всякие медиа и эмулятор windows:
sudo apt-get install language-support-ru ubuntu-restricted-extras wine

При установке шрифтов windows выскакивает лицензионное соглашение. Соглашаемся с ним TAB+ENTER.

После установки, для настройки переключения раскладки на клавиатуре редактируем файл:
sudo nano /etc/default/console-setup
Для переключения по CTRL+SHIFT зменяем строки:
XKBLAYOUT="us,ru"
XKBVARIANT=",winkeys"
XKBOPTIONS="grp:ctrl_shift_toggle"
Для переключения по ALT+SHIFT, устанавливаем параметр
XKBOPTIONS="grp:alt_shift_toggle"
перезагружаемся.

пятница, 18 марта 2011 г.

Огромные шрифты в темах PrimeFaces 2.2.x

Чтобы избавиться от огромных шрифтов в PrimeFaces 2.2 надо переопределить стиль ui-widget.

Для этого создаем css следующего вида:
.ui-widget {
   font-size: 12px !important;
}

и подтягиваем ее на странице:

   <link rel="stylesheet" type="text/css" href="mystyle.css"/>

четверг, 10 февраля 2011 г.

Реализуем обмен сообщениями AMQP с помощью RabbitMQ

Для начала установим и настроим RabbitMQ.

Тут все просто, скачиваем с основного сайта Erlang, проставляем ERLANG_HOME. Далее качаем и ставим сам RabbitMQ. Запускаем Rabbit через командную строку:  
%RABBITMQ_SERVER%/sbin/rabbitmq-server.bat
Если появилось сообщение broking running то все ок, наш сервер работает.

Теперь надо настроить пользователя и виртуальный хост сервера. Запускаем с командной строки:
%RABBITMQ_SERVER%/sbin/rabbitmqctl.bat add_user admin admin
Пользователь admin с паролем admin создан. Теперь создадим виртуальный хост:
rabbitmqctl.bat add_vhost ru.vie2004.vhost
после чего надо дать права на пользование им нашим пользователем:
rabbitmqctl.bat set_permissions -p ru.vie2004.vhost admin .* .* .*

Все, сервер настроен на работу. Более тонкие настройки можно найти на RabbitMQ


Теперь напишем простенькие Sender и Consumer.


Sender:
ConnectionFactory factory = new ConnectionFactory();

factory.setUsername("admin");
factory.setPassword("admin");
factory.setVirtualHost("ru.vie2004.vhost");
factory.setHost("localhost");
factory.setPort(5672);

Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
String exchangeName = "myNewExchange";
String routingKey = "myNewRoute";

byte[] messageBodyBytes = "Наше сообщение".getBytes();

channel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, messageBodyBytes);
Если надо передать не текст, а обьект, то необходимо сериализовать его в byte[].

Consumer:
ConnectionFactory factory = new ConnectionFactory(); 

factory.setUsername("admin");
factory.setPassword("admin");
factory.setVirtualHost("ru.vie2004.vhost");
factory.setHost("localhost");
factory.setPort(5672);
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
boolean durable = true;

channel.exchangeDeclare("myNewExchange", "direct", durable);
channel.queueDeclare("myNewQueue", durable, false, false, null);
channel.queueBind("myNewQueue", "myNewExchange", "myNewRoute");

boolean noAck = false;
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, noAck, consumer);

while (true) {
  QueueingConsumer.Delivery delivery;
  try {
    delivery = consumer.nextDelivery();
  } catch (InterruptedException ie) {
    continue;
  }
  System.out.println("Получено сообщение: " + new String(delivery.getBody()));
}
Все, запускаем слушателя с отправителем и читаем сообщение.


ps. Не могу не упомянуть что для RabbitMQ есть аппендер log4j. Работать с ним можно, но придется немного подработать напильником для удобного пользования.

среда, 9 февраля 2011 г.

primefaces migrate from 2.2.RC2 to 2.2

С выходом на днях новой версии primefaces 2.2 появилось два нововведения. К сожалению не все они мне понравились:

1. теперь user guide платная, стоит порядка 10 евро.
http://primefaces.org/documentation.html
2. темы теперь надо подключать через jar.
http://primefaces.org/themes.html

ps. Документация от 26.07.2010. Настройки тем действительны только до версии 2.2.RC2 включительно.