четверг, 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")