суббота, 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();        
      }
    }
  }
}