Общее·количество·просмотров·страницы

Java Dev Notes

Java Dev Notes - разработка на Java (а также на JavaScript/Python/Flex и др), факты, события из АйТи

четверг, 27 марта 2014 г.

Grails - Convention over Configuration

Изучаю популярный фремворк для разработки веб-приложений для JVM-стека - Grails. Вместо того, чтобы писать длинные портянки XML-конфигураций (как в Spring, до тех пор, пока не стали пользоваться аннотациями для конфигов), Grails использует другой подход: есть жестко заданные правила, которые определяют, что где лежит, а также схему именования этих сущностей (контроллеры, вьюшки, сервисы, доменные объекты и т.п.). Для Grails 2.3.7 схема такая (Convention over Configuration):
  • grails-app - родительский каталог для исходников приложения
    • conf - конфиги
    • controllers - контроллеры - буковка "C" в аббревиатуре MVC.
    • domain - доменные объекты. Т.е. здесь лежат энтити, по которым будет создаваться схема БД
    • i18n - поддержка интернационализации (i18n).
    • services - сервисы
    • taglib - библиотеки тегов
    • utils - Grails -специфичные утилиты
    • views - Groovy Server Pages - вьюшки, аналог JSP, буковка "V" в аббревиатуре MVC
  • scripts - Gant-скрипты.
  • src - вспомогательные исходники
    • groovy - исходники на Groovy
    • java - исходники на Java
  • test - юнит-тесты, интеграционные тесты

суббота, 15 марта 2014 г.

Семафоры в Java: реализация простого семафора

Семафор используется для обмена сигналами между потоками, или же для охраны критической секции. Их также можно использовать и вместо локов. Несмотря на то, что в JDK уже реализован семафор (java.util.concurrent.Semaphore), полезно будет самим реализовать этот объект.

Итак, у нашего семафора будет всего лишь два метода: take, release. Соответственно, простейшая реализация будет такой:

public class SimpleSemaphore {
    boolean taken = false;

    public synchronized void take() {
        this.taken = true;
        this.notify();
    }

    public synchronized void release() throws InterruptedException {
        while (!this.taken) wait();
        this.taken = false;
    }
}

Теперь давайте рассмотрим программу, которая использует семафор для обмена сигналами. У нас будет два потока: SignalSender, SignalReceiver, которые будут посылать друг другу сигналы. Вот код:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        SimpleSemaphore semaphore = new SimpleSemaphore();
        new Thread(new SignalSender(semaphore)).start();
        Thread.currentThread().sleep(2000);
        new Thread(new SignalReceiver(semaphore)).start();
    }


    static class SignalSender implements Runnable {
        private final SimpleSemaphore semaphore;

        public SignalSender(SimpleSemaphore semaphore) {
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            System.out.println("[SignalSender] run");
            while (true) {
                try {
                    doSomeWork();
                    semaphore.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }

        private void doSomeWork() throws InterruptedException {
            System.out.println("[SignalSender] do some work");
            Thread.sleep(500);
        }
    }

    static class SignalReceiver implements Runnable {
        private final SimpleSemaphore semaphore;

        public SignalReceiver(SimpleSemaphore semaphore) {
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            System.out.println("[SignalReceiver] run");
            while (true) {
                try {
                    semaphore.release();
                    doSomeWork();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }

        private void doSomeWork() throws InterruptedException {
            System.out.println("[SignalReceiver] do some work");
            Thread.sleep(700);
        }
    }
}

Посмотрим на вывод консоли:

[SignalSender] run
[SignalSender] do some work
[SignalSender] do some work
[SignalSender] do some work
[SignalSender] do some work
[SignalSender] do some work
[SignalReceiver] run
[SignalReceiver] do some work
[SignalSender] do some work
[SignalReceiver] do some work
[SignalSender] do some work
[SignalReceiver] do some work
[SignalSender] do some work
[SignalSender] do some work
[SignalReceiver] do some work
[SignalSender] do some work
[SignalReceiver] do some work
[SignalSender] do some work
[SignalSender] do some work
[SignalReceiver] do some work
[SignalSender] do some work
[SignalReceiver] do some work
[SignalSender] do some work
[SignalReceiver] do some work
[SignalSender] do some work
[SignalSender] do some work
[SignalReceiver] do some work
Мы видим, как потоки обмениваются сигналами, при этом они ждут друг друга, и начинают работу, только получив соответствующий сигнал от семафора.

пятница, 14 марта 2014 г.

Элементарная реализация BlockingQueue на Java

BlockingQueue (блокирующая очередь) - это очередь, которая блокирует поток, в двух случаях:
  • поток пытается получить элементы из пустой очереди
  • поток пытается положить элементы в полную очередь

Когда поток пытается получить элементы из пустой очереди, он ставится в ожидание до тех пор, пока какой-нибудь другой поток не положит элементы в очередь. Аналогично, когда поток пытается положить элементы в полную очередь, он ставится в ожидание до тех пор, пока какой-нибудь другой поток не возьмет элементы их очереди и таким образом не освободит место в ней. Естественно, введение понятия "полная очередь" подразумевает, что очередь имеет ограниченный размер, который обычно задается в конструкторе.

В Java есть интерфейс BlockingQueue, а также множество реализаций этого интерфейса:

Тем не менее, было бы полезно самим реализовать простейшую блокирующую очередь для понимания ее работы. Причем наша реализация (для начала) должна быть самой простой. Реализации в JDK намного более сложные, чем наша примитивная реализация, которая приведена ниже. Затем можно было бы детально разобрать реализации блокирующих очередей в Java. Впрочем, разбор реализаций в Java мы оставим когда-нибудь на потом, а сейчас займемся собственной простейшей реализацией блокирующей очереди.

public class BlockingQueue {

    private List queue = new LinkedList();
    private int  limit = 10;

    public BlockingQueue(int limit){
        this.limit = limit;
    }


    public synchronized void put(Object item) throws InterruptedException  {
        while (this.queue.size() == this.limit) {
            wait();
        }
        if (this.queue.size() == 0) {
            notifyAll();
        }
        this.queue.add(item);
    }


    public synchronized Object take() throws InterruptedException{
        while (this.queue.size() == 0){
            wait();
        }
        if (this.queue.size() == this.limit){
            notifyAll();
        }

        return this.queue.remove(0);
    }
}

Рассмотрим метод put:

public synchronized void put(Object item) throws InterruptedException  {
    while (this.queue.size() == this.limit) {
        wait();
    }
    if (this.queue.size() == 0) {
        notifyAll();
    }
    this.queue.add(item);
}
Если очередь заполнена, то поток ставится в ожидание. Если же очередь пустая, то вызывается метод notifyAll, который пробуждает потоки, которые поставлены в ожидание при получении элементов из очереди. Аналогично работает метод take.

Обратите внимание, что в нашей простейшей реализации методы put, take являются synchronized в отличие от реализаций в JDK. Это сделано из двух соображений:

  • простота
  • желание обойтись только элементарными примитивами синхронизации в JDK, а именно методами wait, notifyAll

Давайте промоделируем работу очереди размеров в 5 элементов. Пусть у нас есть один поток (Producer), который, скажем, каждые 2 миллисекунды создает и кладет новый объект в очередь (пока что не подключаем поток, который забирает элементы из очереди). Очевидно, что через 10 миллисекунд, после того как очередь вся будет заполнена, при попытке положить шестой по счету элемент в очередь, этот поток будет поставлен в ожидание вот этим кусом кода из метода put:

while (this.queue.size() == this.limit) {
    wait();
}
Теперь подключим второй поток, который будет забирать элементы из очереди (Consumer). Он определит, что очередь полная и пошлет notification всем ожидающим потокам. Это произойдет в следующем куске кода метода take:
if (this.queue.size() == this.limit){
    notifyAll();
}
После этого поток заберет первый элемент из списка, и вернет его в методе take, выйдя таким образом из этого (synchronized!!!) метода и освободив монитор, ассоциированный c очередью. Затем монитор захватит поток Producer, пробудившийся от ожидания, и продолжит класть элементы в очередь.

Теперь приведем полный код демо-примера:

import java.util.LinkedList;
import java.util.List;

public class BlockingQueue {

    private List queue = new LinkedList();
    private int  limit = 10;

    public BlockingQueue(int limit){
        this.limit = limit;
    }


    public synchronized void put(T item) throws InterruptedException  {
        System.out.println("[BlockingQueue] try to put: " + item );
        while (this.queue.size() == this.limit) {
            System.out.println("[BlockingQueue] queue is full, waiting until space is free");
            wait();
        }
        if (this.queue.size() == 0) {
            System.out.println("[BlockingQueue] queue is empty, notify");
            notifyAll();
        }
        this.queue.add(item);
        System.out.println("[BlockingQueue] put ok: " + item );
    }


    public synchronized T take() throws InterruptedException{
        System.out.println("[BlockingQueue] try to take");
        while (this.queue.size() == 0){
            System.out.println("[BlockingQueue] queue is empty, waiting until smth is put");
            wait();
        }
        if (this.queue.size() == this.limit){
            System.out.println("[BlockingQueue] queue is full, notify");
            notifyAll();
        }

        T item = this.queue.remove(0);
        System.out.println("[BlockingQueue] take ok: " + item );
        return item;
    }
}
import java.util.Random;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue queue = new BlockingQueue(5);
        new Thread(new Producer(queue)).start();
        Thread.currentThread().sleep(1000);
        new Thread(new Consumer(queue)).start();
    }

    static class Producer implements Runnable {
        private final BlockingQueue queue;

        public Producer(BlockingQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            System.out.println("[Producer] run");
            while (true) {
                try {
                    queue.put(produce());
                    Thread.currentThread().sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private Integer produce() {
            Integer i = new Random().nextInt(100);
            System.out.println("[Producer] produce: " + i);
            return i;
        }
    }

    static class Consumer implements Runnable {
        private final BlockingQueue queue;

        public Consumer(BlockingQueue queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            System.out.println("[Consumer] run");
            while (true) {
                try {
                    consume();
                    Thread.currentThread().sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void consume() throws InterruptedException {
            Integer i = queue.take();
            System.out.println("[Consumer] consumed: " + i);
        }
    }
}

Вывод консоли будет такой (первая часть, где Producer кладет первые пять элементов в очередь):
[Producer] run
[Producer] produce: 86
[BlockingQueue] try to put: 86
[BlockingQueue] queue is empty, notify
[BlockingQueue] put ok: 86
[Producer] produce: 73
[BlockingQueue] try to put: 73
[BlockingQueue] put ok: 73
[Producer] produce: 20
[BlockingQueue] try to put: 20
[BlockingQueue] put ok: 20
[Producer] produce: 84
[BlockingQueue] try to put: 84
[BlockingQueue] put ok: 84
[Producer] produce: 73
[BlockingQueue] try to put: 73
[BlockingQueue] put ok: 73
[Producer] produce: 67
[BlockingQueue] try to put: 67
[BlockingQueue] queue is full, waiting until space is free
Затем вступает в работу Consumer:
[Consumer] run
[BlockingQueue] try to take
[BlockingQueue] queue is full, notify
[BlockingQueue] take ok: 86
[Consumer] consumed: 86
[BlockingQueue] put ok: 67
[Producer] produce: 63
[BlockingQueue] try to put: 63
[BlockingQueue] queue is full, waiting until space is free
[BlockingQueue] try to take
[BlockingQueue] queue is full, notify
[BlockingQueue] take ok: 73
[Consumer] consumed: 73
[BlockingQueue] put ok: 63
[Producer] produce: 90
[BlockingQueue] try to put: 90
[BlockingQueue] queue is full, waiting until space is free
[BlockingQueue] try to take
[BlockingQueue] queue is full, notify
[BlockingQueue] take ok: 20
[Consumer] consumed: 20
[BlockingQueue] put ok: 90
[Producer] produce: 57
[BlockingQueue] try to put: 57
[BlockingQueue] queue is full, waiting until space is free
На этом куске вывода хорошо видно, как Consumer и Producer попеременно используют очередь для обмена данными.

пятница, 7 марта 2014 г.

Демо-проект: Apache CXF, Jetty, MySQL

Решил выложить на Github небольшой демо-проект: клиент-серверное приложение, управляющее аккаунтами (счетами).

Начальная задача: сделать сервер, который позволяет читать состояние счета по его ID, а также добавлять/снимать средства со счета. Состояния счетов хранятся с БД. Сервер должен работать в нагруженное среде, поэтому нужно добавить кеширование. Транспортный протокол может быть любой. Я выбрал веб-сервисы, реализация веб-сервисов была сделана с помощью Apache CXF. После запуска сервера WSDL можно посмотреть по адресу: http://localhost:9000/accountService?wsdl

Вторая часть задачки: сделать клиента, который в несколько потоков читает/пишет значения счетов. Кол-во читателей/писателей, а также множество идентификаторов счетов задается параметрами клиента (например, из командной строки).

Третья часть задачки: сервер должен статистику работы скидывать в файл. Статистика должна быть такой: общее кол-во обработанных запросов, а также удельное кол-во запросов в ед. времени (т.е. rps - requests per second). Нужно предусмотреть возможность сброса статсы в ноль. Сброс статсы в ноль я сделал с помощью вызова HTTP-хендлера, который и обрабатывается Jetty. Сброс статсы осуществляется HTTP GET-ом урлы http://localhost:9001/zero. По урлу http://localhost:9001/getStats получаем текущее значение rps.

Несколько моментов про реализацию. Итак, рассмотрим реализацию AccountServiceImpl:

@WebService(endpointInterface = "net.iryndin.clientserverdemo1.commons.api.AccountService",serviceName = "AccountService")
public class AccountServiceImpl implements AccountService {
    private Map cache = new ConcurrentHashMap<>();

    @Override
    public Long getAmount(Integer id) {
        AtomicLong balance = cache.get(id);
        statsHelper.incrQueries();
        if (balance == null) {
            return 0L;
        } else {
            return balance.get();
        }
    }

    @Override
    public void addAmount(final Integer id, Long value) throws Exception {
        AtomicLong balance = cache.get(id);
        if (balance == null) {
            balance = new AtomicLong(0);
            cache.put(id, balance);
        }
        balance.addAndGet(value);
        final AtomicLong finalBalance = balance;
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    accountDao.save(id, finalBalance.get());
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        });
        statsHelper.incrQueries();
    }
}  
Видно, что чтение и запись идет прямиком к кеш, т.е. скорость чтения и записи определяется скоростью работы кеша. Запись в базу запускается в отдельном потоке. Если бы она запускалась из этого же потока, то и производительность записи была бы совсем другой. На самом деле, такое решение, конечно, не всегда приемлемо, но поскольку это тестовый проект, я сделал именно так.

Теперь рассмотрим моменты реализации клиента. См. файл MainClient:

    private static void runReaders(int count) {
        if (count <=0) {
            System.out.println("No readers run");
            return;
        }
        ExecutorService executorService = Executors.newFixedThreadPool(count);
        System.out.println("Run readers: " + count);
        while (count > 0) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    while (running) {
                        accountService.getAmount(idsHelper.getRandomId());
                    }
                }
            });
            count--;
        }
    }

    private static void runWriters(int count) {
        if (count <=0) {
            System.out.println("No writers run");
            return;
        }
        ExecutorService executorService = Executors.newFixedThreadPool(count);
        System.out.println("Run writers: " + count);
        while (count > 0) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    while (running) {
                        boolean positive = new Random().nextBoolean();
                        int balance = new Random().nextInt(100000);
                        balance = positive ? balance : -balance;
                        try {
                            accountService.addAmount(idsHelper.getRandomId(), (long) balance);
                        } catch (Exception e) {
                            //
                        }
                    }
                }
            });
            count--;
        }
    }
Мы запускаем потоки по числу rCount, wCount и крутимся там в бесконечном цикле (в бесконечном, так как переменную running в коде никто не меняет). Все этот создает неплохую нагрузку на сервер.

Последнее, рассмотрим скрипт на Python 3 (measure.py), который запускает клиент с различными значениями параметров rCount, wCount и измеряет производительность (просто считывает данные с урла http://localhost:9001/getStats):

import subprocess
import time
import urllib.request

def main():
    readers = [0, 20, 40, 60, 80, 100]
    writers = [0, 20, 40, 60, 80, 100]

    output = open('rps.txt','w', 1)
    
    for r in readers:
        for w in writers:
            run_client(r,w,output)
            print('Done reader=%d, writer=%d' % (r,w))
            
    output.close()

def run_client(readers_qty, writers_qty, output):        
    if readers_qty==0 and writers_qty==0:
        return
    proc = subprocess.Popen(['java', '-jar', '-Xms2G', '-Xmx8G', 'client.jar', str(readers_qty), str(writers_qty)], shell=False)
    time.sleep(1)
    f = urllib.request.urlopen('http://localhost:9001/zero')
    time.sleep(30)
    f = urllib.request.urlopen('http://localhost:9001/getStats')
    s = f.read()
    rps = int(s)
    print('rps=%d' % rps)
    output.write('(%d,%d): %d\n' % (readers_qty,writers_qty,rps))
    
    proc.terminate()
    time.sleep(5)
     
if __name__ == "__main__":
    main()

В результате, мы получаем следующую табличку (по горизонтали: число wCount, по вертикали: rCount, значения в ячейках: requests-per-second):

rCount/wCount 0 20 40 60 80 100
0 - 12197 12319 12865 12648 13614
20 13324 13240 13049 12657 12499 12858
40 13198 12249 12676 13112 12882 12442
60 12420 12250 12348 12928 12534 13013
80 12531 12934 12304 12428 13133 12978
100 12972 13204 11869 11541 12781 12496
Т.е. сервер держит где-то 12-13К реквестов. Это число полносью определяется производительностью ConcurrentHashMap.

понедельник, 30 сентября 2013 г.

Создание базы, пользователя в MySQL, настройки доступа

Для MySQL создаем БД и настраиваем права доступа:
 
  mysql> CREATE DATABASE `mydb` CHARSET utf8 COLLATE utf8_general_ci;
  mysql> GRANT USAGE ON *.* TO 'webapp'@'localhost' IDENTIFIED BY 'SecretP@ssw0rd';
  mysql> GRANT ALL PRIVILEGES ON `mydb`.* TO 'webapp'@'localhost';
  mysql> flush privileges;
  mysql> SHOW GRANTS FOR webapp@localhost;
  +---------------------------------------------------------------------------------------------------------------+
  | Grants for webapp@localhost                                                                                   |
  +---------------------------------------------------------------------------------------------------------------+
  | GRANT USAGE ON *.* TO 'webapp'@'localhost' IDENTIFIED BY PASSWORD '*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19' |
  | GRANT ALL PRIVILEGES ON `mydb`.* TO 'webapp'@'localhost'                                                      |
  +---------------------------------------------------------------------------------------------------------------+
  2 rows in set (0.00 sec)

понедельник, 23 сентября 2013 г.

Управление проектами - правила Ашманова

Весьма занятные мысли по управлению проектами от Игоря Ашманова: Правила Ашманова - Часть 1 и Правила Ашманова-2. Управление проектами.

JokerConf

15 Октября 2013 года в Санкт-Петербурге пройдет JokerConf - конференция по Java-технологиям. Сайт конференции: http://jokerconf.com.

Программа (только названия докладов):

  • Скрипты в Java-приложениях
  • Компромиссы, или Как проектируются языки программирования
  • Разработка API в Java-проекте: как оказывать влияние на людей и не приобрести врагов
  • Факты и заблуждения о Java-сериализации
  • Invokedynamic: Роскошь или необходимость?
  • Extreme Programming practices for your team
  • Спорим, в твоем приложении есть утечка памяти?
  • Understanding Java Garbage Collection and what you can do about it
  • Как я создал desktop-приложение на Java, скачанное 9 000 000 раз
  • The (not so) dark art of Performance Tuning
  • О чём молчит профайлер
  • О чём молчат Heap Dump-ы
  • JDK8: Stream style
  • Занимательные истории из жизни технической поддержки JVM
  • Project Jigsaw. Take 2
  • Unlocking the Java EE Platform with HTML5
  • Spring 4.0: новое поколение
  • В поисках Tommy Hilfiger
  • Аварийный дамп — «черный ящик» упавшей JVM
  • Java mapping для прагматичных программистов
Пойдете ли вы на нее? Я собираюсь пойти. Кто пойдет - отписывайтесь в комментариях!

Курсы по финансовой математике

Если вы интересуетесь финансовой математикой, математическими моделями, применяемыми при инвестировании, а также вообще Financial Engineering, то рекомендую обратить внимание на следующие бесплатные обучающие курсы:

пятница, 31 августа 2012 г.

Разбивка PDF на страницы

Исходник говорит сам за себя:
import java.io.FileOutputStream;

import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfCopy;
import com.itextpdf.text.pdf.PdfImportedPage;
import com.itextpdf.text.pdf.PdfReader;

/**
 * See
 * http://stackoverflow.com/questions/5736675/itext-split-a-pdf-into-several-pdf-1-per-page
 * 
 * Pdf to image:
 * http://stackoverflow.com/questions/4886042/pdf-to-image-using-java
 *
 * Display PDF into flash:http://www.swftools.org/
 * http://stackoverflow.com/questions/580807/how-can-i-show-doc-or-rtf-or-pdf-in-flash-player-or-in-none-editble-format
 * 
 * Display PDF in html5
 * https://github.com/mozilla/pdf.js
 * http://www.pdftron.com/pdfnet/webviewer/demo.html
 * 
 *  http://stackoverflow.com/questions/3113334/is-there-any-way-to-embed-a-pdf-file-into-an-html5-page
 *  
 *  pdf.js: Rendering PDF with HTML5 and JavaScript
 *  http://andreasgal.com/2011/06/15/pdf-js/
 *  http://habrahabr.ru/post/122034/
 *  
 *  google on
 *  html5 view PDF
 *  
 */
public class PdfIntoPagesMain {

 public static void main(String[] args) {

        try {
            //String inFile = args[0].toLowerCase();
         
         String inFile = "book1/book1.pdf";
         
            System.out.println ("Reading " + inFile);
            PdfReader reader = new PdfReader(inFile);
            int n = reader.getNumberOfPages();
            System.out.println ("Number of pages : " + n);
            int i = 0;            
            while ( i < n ) {
                String outFile = inFile.substring(0, inFile.indexOf(".pdf")) 
                    + "-" + String.format("%03d", i + 1) + ".pdf"; 
                System.out.println ("Writing " + outFile);
                Document document = new Document(reader.getPageSizeWithRotation(1));
                PdfCopy writer = new PdfCopy(document, new FileOutputStream(outFile));
                document.open();
                PdfImportedPage page = writer.getImportedPage(reader, ++i);
                writer.addPage(page);
                document.close();
                writer.close();
            }
        } 
        catch (Exception e) {
            e.printStackTrace();
        }

        /* example : 
            java SplitPDFFile d:\temp\x\tx.pdf

            Reading d:\temp\x\tx.pdf
            Number of pages : 3
            Writing d:\temp\x\tx-001.pdf
            Writing d:\temp\x\tx-002.pdf
            Writing d:\temp\x\tx-003.pdf
         */

    }

}

понедельник, 27 февраля 2012 г.

OpenMQ database persistence in Glassfish

JMS-провайдер в сервере Glassfish является OpenMQ - сановская (а теперь оракловая) разработка с открытым исходным кодом.

По-умолчанию, OpenMQ записывает данные в файлы. Это не всегда хорошо, я столкнулся с тем, что при вырубании Глассфиша по kill -9 PID файловое хранилище OpenMQ портится и с восстановлением сообщений в очередях и топиках возникают проблемы. Поэтому имеет смысл рассмотреть переход на хранение сообщений в СУБД. Мы рассмотрим MySQL.

Пусть директория, в которую установлен Глассфиш обозначается как GLASSFISH_HOME. Тогда нужно отредактировать файл с настройками OpenMQ: GLASSFISH_HOME/glassfish/domains/domain1/imq/instances/imqbroker/props/config.properties. Добавим в него следующие строки:

imq.brokerid=imqbroker
imq.persist.store=jdbc
imq.persist.jdbc.dbVendor=mysql
imq.persist.jdbc.mysql.property.url=jdbc:mysql://localhost:3306/mqdb
imq.persist.jdbc.mysql.createdburl=jdbc:mysql://localhost:3306/mqdb
imq.persist.jdbc.mysql.needpassword=true
imq.persist.jdbc.mysql.user=USERNAME
imq.persist.jdbc.mysql.password=PASSWORD

Таким образом, мы настроили сохранение JMS-данных в БД. Нужно еще положить JAR-файл с драйвером БД (mysql-connector-java-5.1.17-bin.jar) в каталог GLASSFISH_HOME/mq/lib/ext.

Теперь создадим БД. Для этого воспользуемся утилитой imqdbmgr, которая находится в каталоге GLASSFISH_HOME/mq/bin. Пишем команду:
./imqdbmgr create all -b imqbroker
и получаем следующие таблицы в БД:

MQBKR41Simqbroker       
MQCON41Simqbroker       
MQCONSTATE41Simqbroker  
MQCREC41Simqbroker      
MQDST41Simqbroker       
MQJMSBG41Simqbroker     
MQMSG41Simqbroker       
MQPROP41Simqbroker      
MQSES41Simqbroker       
MQTMLRJMSBG41Simqbroker 
MQTXN41Simqbroker       
MQVER41Simqbroker
Теперь можно запускать сам Глассфиш - сохранение JMS-данных пойдет в БД, а не в файлы.

Когда создавалась БД, я столкнулся с одной проблемой. Дело в том, что у меня в MySQL кодировкой по умолчанию является UTF-8. Это проблема для OpenMQ, т.к. при создании таблицы MQTMLRJMSBG41Simqbroker появляются ошибки. Но эта проблема решается очень просто - для этого для данной Бд нужно объявить кодировку latin1:

ALTER DATABASE mqdb DEFAULT CHARACTER SET latin1 COLLATE latin1_general_ci;

Тогда все пройдет без ошибок.

Полезные ссылки:

OpenMQ, the Open source Message Queuing, for beginners and professionals (OpenMQ from A to Z)

Browse OpenMQ source code

Oracle GlassFish Message Queue 4.4.2 Technical Overview

Oracle GlassFish Message Queue 4.4.2 Administration Guide

среда, 18 января 2012 г.

Доступ к Wikipedia на время действия экрана SOPA

Википедия сегодня, 18 Января 2011 года, протестует против SOPA и закрыла доступ к контенту.

Но при желании можно все равно этот контент посмотреть. Википедия никуда не убрала контент, она просто добавила на каждую страничку новый div, который и содержит слой с информацией про протест против SOPA. А также добавила стиль display=none к слоям с контентом.

Все это дело легко отключается следующим скриптом (я написал его за 1-2 минуты), который выполняется из консоли Chrome или Firefox:

var jq = document.createElement('script');
jq.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);
$('#mw-sopaOverlay').remove();
$('#content').show();

Все элементарно: первые три строчки подключают jquery, 4-я строчка удаляет слой SOPA, 5-я строчка показывает слой с контентом.

Можно этот скрипт повесить на Greasemonkey, и тогда автоматически вся википедия будет нормально показываться, как и прежде.

И да, я поддерживаю их протест против SOPA. Просто иногда нужно срочно получить информацию, даже если она скрыта.

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

Скрипт для подсчета числа файлов java, xml и др.

Нужно было посчитать статистику по файлам java и др. Вот что получилось:
import os
 
startPath = os.getcwd()
 
java = 0
xmlxsd = 0
 
def findFiles(path):
    global java
    global xmlxsd
    files = os.listdir(path)
    for f in files:
        p = os.path.join(path, f)
        if os.path.isdir(p):
            findFiles(p)
        else:
            fn, fe = os.path.splitext(p)
            print fe
            if fe == '.java':
                java = java+1
            elif fe in ['.xsd', '.xml', '.html', '.xhtml']:
                xmlxsd = xmlxsd + 1 
 
def main():
    findFiles(startPath)
    print 'java = %d ' % java
    print 'xmlxsd = %d' % xmlxsd
 
 
if __name__ == "__main__":
    main()   

четверг, 20 октября 2011 г.

Кодировки в MySQL

Хорошо объясняется, как выставлять кодировки в MySQL: MySQL + кодировки В результате, я сделал так: открываем файл my.cnf (/etc/mysql/my.cnf) и добавляем:
[client]
default-character-set=utf8

[mysqld]
default-character-set=utf8
That's all!

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

MapReduce и MongoDB

Для обработки данных в коллекциях в MongoDB есть замечательный инструмент - map-reduce. Посмотрим, как он работает.

Я это делал на реальном примере по одному проекту - нужно обработать данные по пользовательским покупкам, которые хранятся в монге. Нас интересует два поля: ID пользователя и сумма покупки. В качестве ID пользователя выступает GUID.

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

Если бы мы были в SQL, то табличка с данными о покупках была бы следующие:

CREATE TABLE sales(
  guid varchar(32) NOT NULL PRIMARY KEY,
  price float NOT NULL
);
Запрос, который выводит данный отчет, был бы следующий:
SELECT guid, sum(price) AS summa FROM sales 
GROUP BY guid
ORDER BY summa DESC;
В монге данные хранятся в коллекции stat, вид данных следующий:
> db.stat.findOne();
{ 
  "_id" : ObjectId("4e4c73eb41e5c790a7391848"), 
  "guid" : "346fbb3968c9453d9dc8f8ebe0fa6763", 
  "item" : "Apple MacBook Air MC968LL/A 11.6-Inch", 
  "price" : 78.64, 
  "retailerId" : "COST", 
  "ip" : "0:0:0:0:0:0:0:1", 
  "datetime" : "2011-07-28 10:12:38", 
  "timezone" : "-1", 
  "createDate" : "Thu Aug 18 2011 06:07:39 GMT+0400 (MSK)", 
}
Что ж, приступим в мап-редьюсу...
function salesMap() {
    emit(this.guid, {price: this.price });
}
 
function salesReduce(key, values) {
    var result = { 
        count: 0,
        summa: 0.0
    };
    values.forEach(function(v){
        if (v.price) {
            result.summa += v.price;
            result.count++;
        }
    });
    return result;
}
db.userrep.drop();
db.stat.mapReduce(salesMap,salesReduce,{out:'userrep', verbose: true});
db.userrep.find();
Функция emit(key, value), которую мы использовали в функции salesMap, подает на вход редьюсу пару ключ - значение. Ключом мы выбрали GUID, т.к. по нему идет группировка, а в значение кладем цену товара. Функция salesReduce получает на вход набор объектов, сгруппированных по ключу.

Внутри функции все тривиально:
1) создаем объект с результатами, пока что содержащий нулевые значения var result = { count: 0, summa: 0.0 };
2) пробегаемся по коллекции и заполняем результат
3) возвращаем результат.
4) PROFIT!!!
Вид результатов следующий:

> db.userrep.find().limit(5)
{ "_id" : "01dc299862094450ac232d384d883a5f", "value" : { "count" : 2, "summa" : 245.19 } }
{ "_id" : "0a1afef3b8b27942d9a8d02903ca2c28", "value" : { "count" : 1, "summa" : 30.43 } }
{ "_id" : "0c9d6a05458313b85706548c290991e9", "value" : { "count" : 0, "summa" : 0 } }
{ "_id" : "0f4e595202884408ae4c4c6306d764f9", "value" : { "count" : 1, "summa" : 88.43 } }
{ "_id" : "17a57229a9c14b2cb46337a3d196051c", "value" : { "count" : 1, "summa" : 49.54 } }
Какая жалость - данные не отсортированы.

Монго не умеет сортировать коллекцию данным из составных объектов, т.е. мы не можем написать что-то вроде:

db.userrep.find().sort({value.summa: -1});
Поэтому придется немножко извратиться:
db.userrep.find().forEach(function(v) {
    var s = v.value.summa;
    var c = v.value.count;
    var id = v._id;
    db.userrep.update({_id: id},{$set:{summa:s, count: c}}, true, true);
});
Мы просто скопировали поля summa, count из объекта value в объект-контейнер коллекции. Теперь данные выглядят так:
> db.userrep.find().limit(5);
{ "_id" : "01dc299862094450ac232d384d883a5f", "count" : 2, "summa" : 245.19, "value" : { "count" : 2, "summa" : 245.19 } }
{ "_id" : "0a1afef3b8b27942d9a8d02903ca2c28", "count" : 1, "summa" : 30.43, "value" : { "count" : 1, "summa" : 30.43 } }
{ "_id" : "0c9d6a05458313b85706548c290991e9", "count" : 0, "summa" : 0, "value" : { "count" : 0, "summa" : 0 } }
{ "_id" : "0f4e595202884408ae4c4c6306d764f9", "count" : 1, "summa" : 88.43, "value" : { "count" : 1, "summa" : 88.43 } }
{ "_id" : "17a57229a9c14b2cb46337a3d196051c", "count" : 1, "summa" : 49.54, "value" : { "count" : 1, "summa" : 49.54 } }
Теперь, наконец, можно получить отсортированные данные:
> db.userrep.find().sort({summa:-1}).limit(5);
{ "_id" : "cb1332aed05d48fd9c51d0c5be584156", "count" : 4, "summa" : 496.72999999999996, "value" : { "count" : 4, "summa" : 496.72999999999996 } }
{ "_id" : "ade1efffe9d3fb116f33c320bf9b447e", "count" : 3, "summa" : 412.45000000000005, "value" : { "count" : 3, "summa" : 412.45000000000005 } }
{ "_id" : "ef98c2c430c4486394bc2b85ec5b1a77", "count" : 3, "summa" : 408.5, "value" : { "count" : 3, "summa" : 408.5 } }
{ "_id" : "5295f9b927c59dacdd3fb20d8173a19b", "count" : 4, "summa" : 367.08, "value" : { "count" : 4, "summa" : 367.08 } }
{ "_id" : "01dc299862094450ac232d384d883a5f", "count" : 2, "summa" : 245.19, "value" : { "count" : 2, "summa" : 245.19 } }

четверг, 1 сентября 2011 г.

Установка MongoDB на RHEL

Довелось мне недавно устанавливать MongoDB на RHEL и настраивать репликацию (Replica sets). Про репликацию я напишу в другом посте, а вот про установку - здесь. Машина - 64х битный linux Amazon Image for EC2. Итак, по шагам: 1) Добавляем репозиторий
     sudo touch /etc/yum.repos.d/10gen.repo
    
и в файл добавляем следующее:
     [10gen]
     name=10gen Repository
     baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64
     gpgcheck=0
     
2) Устанавливаем
    sudo yum install mongo-10gen mongo-10gen-server
    
3) Редактируем конфиг:
    sudo vi /etc/mongod.conf
    
Например, можем отредактировать порт, расположение логов и файлов с данными:
    logpath=/var/log/mongo/mongod.log
    port=27017
    dbpath=/var/lib/mongo
    
4) Запускаем!
    sudo /etc/init.d/mongod start
    

среда, 31 августа 2011 г.

java.text.SimpleDateFormat и потоки

Использование класса java.text.SimpleDateFormat, не является потокобезопасным.

Например, вот такой код:


import java.text.*;

class MiscUtils {
public final static DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

public static String formatDate(Date date) {
return fmt.format(date);
}
}

//
// usage:
//
void anyMethod() {
Date date = getDate();
String s = MiscUtils.formatDate(date);
// now we have formatted date here!
}


может вызвать проблемы, если метод MiscUtils.formatDate будет одновременно вызван из нескольких потоков.

Вот выдержка со страницы Java Docs SimpleDateFormat:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.


Решение 1.
Всякий раз, когда нужно отформатировать (или пропарсить) дату, создавать новый экземпляр java.text.SimpleDateFormat

Решение 2.
Создать свою, потокобезопасную реализацию форматтера с блэкджеком и шлюхами с синхронизацией:

public class ThreadSafeSimpleDateFormat {

private DateFormat df;

public ThreadSafeSimpleDateFormat(String format) {
this.df = new SimpleDateFormat(format);
}

public synchronized String format(Date date) {
return df.format(date);
}

public synchronized Date parse(String string) throws ParseException {
return df.parse(string);
}
}


Решение 3.
Заюзать какую-нибудь стороннюю бибилиотечку наподобие JodaTime или класс FastDateFormat из Apache Commons.

пятница, 19 августа 2011 г.

Эффективное копирование файлов в NIO

Эффективное копирование файлов в NIO:

 // Getting file channels

FileChannel in = new FileInputStream(source).getChannel();
FileChannel out = new FileOutputStream(target).getChannel();
 
// JavaVM does its best to do this as native I/O operations.
in.transferTo (0, in.size(), out);
 
// Closing file channels will close corresponding stream objects as well.
out.close();
in.close();

четверг, 11 августа 2011 г.

Установка Postgres-9.0 на Ubuntu 10.10

Сделаем установку Postgres-9.0 на Ubuntu 10.10.

1) В репах по-умолчанию нет пакета Postgres-9.0, поэтому добавим репозиторий, откуда будем качать пакеты:

deb http://ppa.launchpad.net/pitti/postgresql/ubuntu maverick main
deb-src http://ppa.launchpad.net/pitti/postgresql/ubuntu maverick main

добавляем в файл /etc/apt/sources.list. В итоге должны получить что-то вроде

$ cat /etc/apt/sources.list
deb http://ru.archive.ubuntu.com/ubuntu/ maverick main restricted
deb http://ru.archive.ubuntu.com/ubuntu/ maverick multiverse
deb http://archive.canonical.com/ubuntu maverick partner
deb http://archive.canonical.com/ maverick partner
deb http://ru.archive.ubuntu.com/ubuntu/ maverick-updates restricted main multiverse universe
deb http://security.ubuntu.com/ubuntu/ maverick-security restricted main multiverse universe
deb http://extras.ubuntu.com/ubuntu maverick main #Third party developers repository
# for postgres 9.0
deb http://ppa.launchpad.net/pitti/postgresql/ubuntu maverick main
deb-src http://ppa.launchpad.net/pitti/postgresql/ubuntu maverick main

:~$


2) Идем сюда: https://launchpad.net/~pitti/+archive/postgresql, смотрим значение ключа (сейчас это 8683D8A2) и добавляем ключ:

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 8683D8A2

3) Апдейтим репы:
sudo apt-get update

4) Останавливаем сервис Postgres 8:
~$ sudo service postgresql-8.4 stop

5) Пуржим пакеты:
sudo apt-get purge postgresql*

6) Устанавливаем postgres 9.0
sudo apt-get install postgresql-9.0

7) Заходим в psql:
sudo -u postgres psql

Ссылки:
Install PostgreSQL 9 on Ubuntu Linux
Postgres 9.x installation in Ubuntu
Установка Postgresql 9, pgAdmin III в Ubuntu 10.04
Installing PostgreSQL 9.0 on Ubuntu 10.04

Как узнать, какую версию Ubuntu вы используете


user1:~$ cat /etc/issue
Ubuntu 10.10 \n \l

user1:~$


или через GUI: System -> Administration -> System Monitor

или:


user1:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 10.10
Release: 10.10
Codename: maverick

Установка пароля админа в Glassfish 3

Как установить админский пароль в Glassgish v3:

  1. Запустить Glassfish - GF_HOME/glassfish/bin/startserv

  2. Вызвать скрипт asadmn: GF_HOME/bin/asadmin change-admin-password




~/dev/glassfishv3/bin$ ./asadmin change-admin-password
Enter admin user name [default: admin]> admin
Enter admin password>
Enter new admin password>
Enter new admin password again>

Command change-admin-password executed successfully.
~/dev/glassfishv3/bin$

Постоянные читатели