Alexander Shchekoldin "all in one" blog http://shchekoldin.com Thu, 27 Oct 2011 10:35:48 +0000 http://wordpress.org/?v=2.8.2 en hourly 1 Comet на Embedded Jetty. Первый блин) http://shchekoldin.com/2011/02/23/comet-%d0%bd%d0%b0-embedded-jetty-%d0%bf%d0%b5%d1%80%d0%b2%d1%8b%d0%b9-%d0%b1%d0%bb%d0%b8%d0%bd/ http://shchekoldin.com/2011/02/23/comet-%d0%bd%d0%b0-embedded-jetty-%d0%bf%d0%b5%d1%80%d0%b2%d1%8b%d0%b9-%d0%b1%d0%bb%d0%b8%d0%bd/#comments Wed, 23 Feb 2011 12:53:14 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=627 комом. Мой первый эксперимент с COMET и с Embedded Jetty. “Пощупать” COMET хотелось уже давно. Во-первых сама по себе технология достаточно интересная, а во-вторых — хотелось попробовать что-то новенькое. Jetty был выбран в качестве сервера для экспериментов как наиболее легковесный (особенно в embedded-варианте) из известных мне Java-серверов.

В качестве примера напишем простенький чат. Будем использовать технику long polling. Допустим у нас запущен Jetty-сервер с приложением-чатом, к которому подключились два клиента Саша и Маша. На диаграмме все их мучения будут выглядеть так:

comet

Теперь попробуем объяснить увиденное с небольшими вставками кода (полный исходный код можно скачать в конце статьи).

Сервер запускается и переходит в режим ожидания новых подключений

private void initializeJettyServer() {
    jettyServer = new Server();
 
    // Thread pool
    QueuedThreadPool threadPool = new QueuedThreadPool();
    threadPool.setMaxThreads(THREAD_POOL_MAX_THREADS);
    jettyServer.setThreadPool(threadPool);
 
    // NIO connector
    SelectChannelConnector connector = new SelectChannelConnector();
    connector.setPort(CONNECTOR_PORT);
    connector.setMaxIdleTime(CONNECTOR_MAX_IDLE_TIME);
    jettyServer.setConnectors(new Connector[] { connector });
 
    // Handlers
    HandlerList handlers = new HandlerList();
    ContextHandlerCollection contexts = new ContextHandlerCollection();
 
    Context rootContext = new Context(contexts, "/", Context.SESSIONS);
    rootContext.addServlet(new ServletHolder(ChatServlet.class), "/chat/*");
 
    ResourceHandler resourceHandler = new ResourceHandler();
    resourceHandler.setResourceBase("../src/client");
 
    handlers.setHandlers(new Handler[] { resourceHandler, contexts });
    jettyServer.setHandler(handlers);
}

Нужно использовать именно SelectChannelConnector, т.к. он является неблокирующим и позволяет использовать Continuation’ы (именно они позволяют создавать “долгие” соединения, рассмотрим чуть поздже). Далее мы подцепляем к нашему серверу сервлет ChatServlet, который будет обрабатывать все AJAX-запросы от клиентов и через ResourceHandler указываем директорию с клиентской частью приложения.

Саша регистрируется на сервере, получает уникальный номер (uid) и подписывается на события сервера

Саша вводит свой ник и нажимает кнопку “Register”, после чего выполняется следующий код:

_register : function(nickname) {
    var request = new AjaxRequest(
        '/chat/',
        { 'action' : 'register', 'nickname' : nickname },
 
        function(responseText) {
            chat._userUid = responseText;
            chat._subscribe();
        }
    );
 
    request.send();
}

На сервлет ChatServlet отправляется запрос с двумя параметрами: действием, которое нужно выполнить (action) и ником (nickname). При успешном выполнении запроса скрипт сохраняет выданный сервером uid и подписывается на события с сервера: регистрация новых пользователей и новые сообщения (рассмотрим далее).

При регистрации пользователя на сервере выполнится функция:

public synchronized String registerUser(String nickname) {
    ChatUser user = new ChatUser(nickname);
    users.put(user.getUid(), user);
 
    return user.getUid();
}

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

После регистрации скрипт сразу подписывает Сашу на новые события с сервера путём установки с сервером “долгого” соединения (в нашем случае оно ограничено 30 секундами). На диаграмме такие “долгие” соединения представлены прямоугольниками. При успешном завершении такого соединения скрипт выводит полученный от сервера текст в окно чата.

_subscribe : function() {
    var request = new AjaxRequest(
        '/chat/',
        { 'action' : 'subscribe', 'user-uid' : this._userUid },
 
        function(responseText) {
            if ((responseText != 'null') && (responseText != '')) {
                chat._renderMessage(responseText);
            }
 
            setTimeout(function() { chat._subscribe(); }, 0);
        }
    );
 
    request.send();
}

Вот мы и добрались до Continuation’ов) Итак, что же происходит на стороне сервера при установлении “долгого” соединения? Для начала немного слов про то, что такое Continuation. Эта фишка Jetty, которая позволяет “приостановить” запрос ровно до того момента пока не произойдёт какое либо событие, либо произойдёт таймаут запроса. Типичная последовательность действий при работе с Continuation выглядит так:

  1. Клиент посылает запрос на сервер.
  2. Сервер оборачивает запрос клиента в Continuation-объект и вызывает у него метод suspend, тем самым оставляя соединее открытым.
  3. При возникновении какого-либо события, на которое подписан клиент, сервер получает Continuation-объект для этого клиента, устанавливает для него дополнительные данные, которые будут возвращены клиенту (с помощью вызова метода setObject) и вызывает у него метод resume.
  4. Исходный запрос выполняется ещё раз ровно до того момента, когда был вызван метод suspend. Но в этом случае запрос не “приостанавливается”, а выполняется как обычно. Выбор ветки (”приостановить” или продолжить нормально выполнение) делается на основании свойства Continuation-объекта isPending, которое сигнализирует о том, что запрос уже “приостановлен”.
  5. Клиент снова подписывается на события сервера (посылает запрос). Круг замкнулся.

Особенностью Continuation’ов является то, что они не держат поток пока запрос “приостановлен”. Т.е. один поток сервера может обслуживать несколько открытых соединений (никакого “thread per connection”). Очень круто! Также в документации сказано, что с выходом Servlet 3.0 Continuation’ы будут заменены на “suspendable requests”. Более подробную информацию можно прочитать в материалах в конце статьи.

А вот и сам код:

private void doSubscribeAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
    Continuation continuation = ContinuationSupport.getContinuation(request, null);
 
    if (continuation.isPending()) {
        writeOkResponse(response, (String) continuation.getObject());
        continuation.setObject(null);
    } else {
        String userUid = request.getParameter("user-uid");
 
        if (Chat.getInstance().subscribeUser(userUid, continuation)) {
            continuation.suspend(30000);
        } else {
            writeErrorResponse(response, "User not found. Please, register.");
        }
    }
}

С учётом всего написанного выше код должен быть понятен)

Итак, Саша зарегистрировался в чате и ждёт, но нифига не происходит. Маша изрядно тупит. Но в итоге каким-то чудом и она смогла зарегистрироваться.

Маша регистрируется на сервере, получает уникальный номер (uid) и подписывается на события сервера

Не будем снова разбирать эту цепочку, она такая же как и у Саши. Поэтому посмотрим каким образом ему придёт сообщение о новом участнике чата.

Когда Маша (или Саша, один фиг) регистрируется, то выполняется код:

private void doRegisterAction(HttpServletRequest request, HttpServletResponse response) throws IOException {
    String nickname = request.getParameter("nickname");
    String userUid = Chat.getInstance().registerUser(nickname);
 
    Chat.getInstance().broadcastMessage(userUid, "Welcome, " + nickname + "!");
    writeOkResponse(response, userUid);
}

Наибольший интерес для нас сейчас представляет функция broadcastMessage:

public synchronized void broadcastMessage(String senderUid, String message) {
    for (ChatUser user : users.values()) {
        if (user.getUid().equals(senderUid)) continue;
 
        user.getContinuation().setObject(message);
        user.getContinuation().resume();
    }
}

Она служит для рассылки сообщения message всем участникам чата, кроме пользователя, который стал виновником события (uid равен параметру senderUid).

Саша, на момент регистрации Маши, был подписан на события сервера и с ним был связан Continuation-объект в pending-состоянии. Между Сашей и сервером было открыто соединение, незавершённый запрос. При регистрации Маши выполнилась функция broadcastMessage, которая это запрос завершила, вернув в качестве результата сообщение о регистрации Маши. Таким образом Саше будет отправлено сообщение “Welcome, Маша!”. После чего Саша снова подписывается на события с сервера. Всё просто)

Обмен сообщениями

Всё один в один с тем, что происходит при регистрации.

Произошёл таймаут соединения

Аааааааааа! Мы все умрём!!! На самом деле нет. Всё, что нужно сделать, это просто переподключиться к серверу. Т.е. всегда (как при таймауте, так и в случае ответа от сервера) при завершении “долгого” соединения мы сразу же устанавливаем новое.

Исходный код и исполняемый файл

Для сборки проекта используется Maven 2. Для этого в директории …/embedded-jetty-comet/src/server вызываем команду

mvn clean package

Для запуска тестового примера необходимо в директории …/embedded-jetty-comet/bin выполнить команду (архив надо распаковать полностью — директория src должна лежать рядом с директорией bin)

java -jar comet-server-1.0.jar

Скачать исходный код и исполняемый файл

Материалы:

]]>
http://shchekoldin.com/2011/02/23/comet-%d0%bd%d0%b0-embedded-jetty-%d0%bf%d0%b5%d1%80%d0%b2%d1%8b%d0%b9-%d0%b1%d0%bb%d0%b8%d0%bd/feed/ 3
Визуализируем репозиторий #2 http://shchekoldin.com/2011/01/17/%d0%b2%d0%b8%d0%b7%d1%83%d0%b0%d0%bb%d0%b8%d0%b7%d0%b8%d1%80%d1%83%d0%b5%d0%bc-%d1%80%d0%b5%d0%bf%d0%be%d0%b7%d0%b8%d1%82%d0%be%d1%80%d0%b8%d0%b9-2/ http://shchekoldin.com/2011/01/17/%d0%b2%d0%b8%d0%b7%d1%83%d0%b0%d0%bb%d0%b8%d0%b7%d0%b8%d1%80%d1%83%d0%b5%d0%bc-%d1%80%d0%b5%d0%bf%d0%be%d0%b7%d0%b8%d1%82%d0%be%d1%80%d0%b8%d0%b9-2/#comments Mon, 17 Jan 2011 17:14:08 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=629 Продолжаем эксперименты с Code Swarm. В связи со сменой работы решил натравить визуализацию на текущий проект) Вот что получилось:

P.S.: хорошая пошаговая инструкция по этой утилите.

]]>
http://shchekoldin.com/2011/01/17/%d0%b2%d0%b8%d0%b7%d1%83%d0%b0%d0%bb%d0%b8%d0%b7%d0%b8%d1%80%d1%83%d0%b5%d0%bc-%d1%80%d0%b5%d0%bf%d0%be%d0%b7%d0%b8%d1%82%d0%be%d1%80%d0%b8%d0%b9-2/feed/ 0
Reorder JQuery event handlers http://shchekoldin.com/2010/11/18/reorder-jquery-event-handlers/ http://shchekoldin.com/2010/11/18/reorder-jquery-event-handlers/#comments Wed, 17 Nov 2010 22:06:46 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=613 I was little bit suprised when I didn’t find in JQuery an ability to bind event’s handler before than already binded event’s handlers list. Yes, it is possible to first attach our handler rather than others, but this may be very difficult sometimes. So I spent some time looking through JQuery sources and found what I need:

/*
 * A number of helper functions used for managing events.
 * Many of the ideas behind this code originated from
 * Dean Edwards' addEvent library.
 */
jQuery.event = {
 
    // Bind an event to an element
    // Original by Dean Edwards
    add: function( elem, types, handler, data ) {
        ...
 
        // Init the element's event structure
        var elemData = jQuery.data( elem );
 
        // If no elemData is found then we must be trying to bind to one of the
        // banned noData elements
        if ( !elemData ) {
            return;
        }
 
        // Use a key less likely to result in collisions for plain JS objects.
        // Fixes bug #7150.
        var eventKey = elem.nodeType ? "events" : "__events__",
            events = elemData[ eventKey ],
            eventHandle = elemData.handle;
 
        ...
 
        while ( (type = types[ i++ ]) ) {
            ...
 
            // Get the current list of functions bound to this event
            var handlers = events[ type ],
                special = jQuery.event.special[ type ] || {};
 
            ...
 
            // Add the function to the element's handler list
            handlers.push( handleObj );
 
            ...
        }
 
        // Nullify elem to prevent memory leaks in IE
        elem = null;
    }
 
    ...
}

As we can see from this code all we need to do is get “handlers” array (for “click” event in this example) from target element. Then we can do anything we need with event’s handlers. Code to do this is quite simple:

var
elementData = jQuery.data(targetElementDom),
events = (elementData.events ? elementData.events : elementData.__events__),
handlers = events['click'];
 
// Only one handler. Nothing to change.
if (handlers.length == 1) {
    return;
}
 
// Insert last handler at first index.
handlers.splice(0, 0, handlers.pop());

I used previous code to attach confirm window to elements which already have (or not) “click” event handlers.

Little demo.

]]>
http://shchekoldin.com/2010/11/18/reorder-jquery-event-handlers/feed/ 6
Jersey: how to get temporary file http://shchekoldin.com/2010/09/05/jersey-how-to-get-temporary-file/ http://shchekoldin.com/2010/09/05/jersey-how-to-get-temporary-file/#comments Sun, 05 Sep 2010 16:21:07 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=596 I use Jersey with flash uploader and the problem I met is that I need to determine “file partialy uploaded” situation ’cause user has an opportunity to cancel it at any time. All solutions that I found for this problem were on PHP (look for The uploaded file was only partially uploaded text). I didn’t find any standart technics how to get size of uploaded file (uploaded file’s input stream doesn’t has such information) to compare it with actual file size. So I used a “little reflection magic” to get it. ’cause classes that we’ll use to get information is private for org.jvnet.mimepull package we need to create out util class in exactly the same package. Then we can use reflection to get any infomation that we need.

Listing of TemporaryFileUtil class:

package org.jvnet.mimepull;
 
import java.io.File;
import java.lang.reflect.Field;
 
public class TemporaryFileUtil {
 
    // -------------------------------------------------------
    // -                        LOGIC                        -
    // -------------------------------------------------------
 
    private static Object readFieldValue(String fieldName, Object o) throws Exception {
        Field field = o.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(o);
    }
 
    private static File getFile(DataHead dataHead) {
        try {
            DataFile dataFile = (DataFile) dataHead.getClass().getDeclaredField("dataFile").get(dataHead);
 
            if (dataFile != null) {
                WeakDataFile weakDataFile = (WeakDataFile) readFieldValue("weak", dataFile);
                return (File) readFieldValue("file", weakDataFile);
            }
        } catch (Exception ignored) {
        }
 
        return null;
    }
 
    public static long getSize(MIMEPart part) {
        try {
            DataHead dataHead = (DataHead) readFieldValue("dataHead", part);
            File file = getFile(dataHead);
 
            if (file != null) {
                // Temporary file was created for uploaded file.
                return file.length();
            } else {
                // Uploaded file stored in memory.
                return (Long) readFieldValue("inMemory", dataHead);
            }
        } catch (Exception ignored) {
        }
 
        return -1;
    }
 
}

Usage example:

@POST
@Path("/upload-new-file/")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_HTML)
public String uploadNewMusicFile(@FormDataParam("Filedata") FormDataBodyPart newFileBodyPart) {
    if (isFilePartiallyUploaded(newFileBodyPart)) {
        return null;
    }
 
    ...
}
 
protected boolean isFilePartiallyUploaded(FormDataBodyPart bodyPart) {
    FormDataMultiPart multiPart = (FormDataMultiPart) bodyPart.getParent();
 
    // This information is provides by Flash object and attaches to request's data.
    long realFileSize = Long.valueOf(multiPart.getField("Filesize").getValue());
 
    long uploadedFileSize = 0;
 
    BodyPartEntity entity = (BodyPartEntity) bodyPart.getEntity();
 
    try {
        Field mimePartField = entity.getClass().getDeclaredField("mimePart");
        mimePartField.setAccessible(true);
        MIMEPart mimePart = (MIMEPart) mimePartField.get(entity);
        uploadedFileSize = TemporaryFileUtil.getSize(mimePart);
    } catch (Exception e) {
        log.error("Can't get uploaded file size.");
        log.error(e);
        throw new RestServiceException();
    }
 
    return (realFileSize != uploadedFileSize);
}
]]>
http://shchekoldin.com/2010/09/05/jersey-how-to-get-temporary-file/feed/ 2
Fix for Jersey’s russian files names bug http://shchekoldin.com/2010/08/21/fix-for-jerseys-russian-files-names-bug/ http://shchekoldin.com/2010/08/21/fix-for-jerseys-russian-files-names-bug/#comments Sat, 21 Aug 2010 14:15:31 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=583 In our project we use Sun/Oracle implementation of JAX-RS — Jersey ’cause it’s greate framework for build REST-services. But there are some troubles with uploading files feature. When you uploading file with latin chars in file name everything is fine, you read this file name on server and it’s exactly the same as on client computer. But when you trying to upload file with russian chars in file name you get “abracadabra” instead of them. For example:

Original file name Received file name
Test123.txt Test123.txt
Тест123.txt Тест123.txt


I found that is not exactly Jersey problem but MIME pull which Jersey using to parse multi part requests. The problem is that the MIME pull reads headers in non UTF8 encoding. And the solution is to encode header’s data in UTF8.

I wrote Jersey’s custom multi part reader provider using built in multi part reader sources (MultiPartReader class) as base. The only thing I had to change — add a function getFixedHeaderValue which fixes headers’ encoding and used it in read headers loop:

for (Header h : mp.getAllHeaders()) {
    bodyPart.getHeaders().add(h.getName(), getFixedHeaderValue(h));
}

Now everything works perfectly)

P.S.: This solution works only when you using Jersey’s multipart module

Usage example:

@POST
@Path("/upload-file/")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public String uploadFile(@FormDataParam("Filedata") FormDataBodyPart newFileBodyPart) {
    ...
    String fileName = newFileBodyPart.getContentDisposition().getFileName();
    ...
}

Listing of my custom multi part reader provider:

/**
 * Replaces build-in Jersey multipart reader to fix russian files names problem.
 *
 * @see com.sun.jersey.multipart.impl.MultiPartReader
 */
@Provider
@Consumes("multipart/*")
public class UTF8MultiPartReader implements MessageBodyReader<MultiPart> {
 
    private final Providers providers;
 
    private final MultiPartConfig config;
 
    private final CloseableService closeableService;
 
    private final MIMEConfig mimeConfig;
 
    // -------------------------------------------------------
    // -                        LOGIC                        -
    // -------------------------------------------------------
 
    private String getFixedHeaderValue(Header h) {
        String result = h.getValue();
 
        if (h.getName().equals("Content-Disposition") && (result.indexOf("filename=") != -1)) {
            try {
                result = new String(result.getBytes(), "utf8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException("Can't convert header \"Content-Disposition\" to UTF8 format.");
            }
        }
 
        return result;
    }
 
    public UTF8MultiPartReader(@Context Providers providers, @Context MultiPartConfig config,
        @Context CloseableService closeableService) {
        this.providers = providers;
 
        if (config == null) {
            throw new IllegalArgumentException("The MultiPartConfig instance we expected is not present. "
                + "Have you registered the MultiPartConfigProvider class?");
        }
 
        this.config = config;
        this.closeableService = closeableService;
 
        mimeConfig = new MIMEConfig();
        mimeConfig.setMemoryThreshold(config.getBufferThreshold());
    }
 
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return MultiPart.class.isAssignableFrom(type);
    }
 
    public MultiPart readFrom(Class<MultiPart> type, Type genericType, Annotation[] annotations, MediaType mediaType,
        MultivaluedMap<String, String> headers, InputStream stream) throws IOException, WebApplicationException {
        try {
            MIMEMessage mm = new MIMEMessage(stream, mediaType.getParameters().get("boundary"), mimeConfig);
 
            boolean formData = false;
            MultiPart multiPart = null;
 
            if (MediaTypes.typeEquals(mediaType, MediaType.MULTIPART_FORM_DATA_TYPE)) {
                multiPart = new FormDataMultiPart();
                formData = true;
            } else {
                multiPart = new MultiPart();
            }
 
            multiPart.setProviders(providers);
 
            if (!formData) {
                multiPart.setMediaType(mediaType);
            }
 
            for (MIMEPart mp : mm.getAttachments()) {
                BodyPart bodyPart = null;
 
                if (formData) {
                    bodyPart = new FormDataBodyPart();
                } else {
                    bodyPart = new BodyPart();
                }
 
                bodyPart.setProviders(providers);
 
                for (Header h : mp.getAllHeaders()) {
                    bodyPart.getHeaders().add(h.getName(), getFixedHeaderValue(h));
                }
 
                try {
                    String contentType = bodyPart.getHeaders().getFirst("Content-Type");
 
                    if (contentType != null) {
                        bodyPart.setMediaType(MediaType.valueOf(contentType));
                    }
 
                    bodyPart.getContentDisposition();
                } catch (IllegalArgumentException ex) {
                    throw new WebApplicationException(ex, 400);
                }
 
                bodyPart.setEntity(new BodyPartEntity(mp));
                multiPart.getBodyParts().add(bodyPart);
            }
 
            if (closeableService != null) {
                closeableService.add(multiPart);
            }
 
            return multiPart;
        } catch (MIMEParsingException ex) {
            throw new WebApplicationException(ex, 400);
        }
    }
 
}
]]>
http://shchekoldin.com/2010/08/21/fix-for-jerseys-russian-files-names-bug/feed/ 0
Awesome Metal Singers http://shchekoldin.com/2010/07/03/awesome-metal-singers/ http://shchekoldin.com/2010/07/03/awesome-metal-singers/#comments Sat, 03 Jul 2010 14:10:27 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=546 Случайно наткнулся вот на эту парочку:


Обратите внимание на кота на заднем плане:)
Может с музыкальной точки зрения там не всё гладко, но девчонки всё равно молодцы)

]]>
http://shchekoldin.com/2010/07/03/awesome-metal-singers/feed/ 0
JRebel: хватит тратить своё время http://shchekoldin.com/2010/06/23/jrebel-%d1%85%d0%b2%d0%b0%d1%82%d0%b8%d1%82-%d1%82%d1%80%d0%b0%d1%82%d0%b8%d1%82%d1%8c-%d1%81%d0%b2%d0%be%d1%91-%d0%b2%d1%80%d0%b5%d0%bc%d1%8f/ http://shchekoldin.com/2010/06/23/jrebel-%d1%85%d0%b2%d0%b0%d1%82%d0%b8%d1%82-%d1%82%d1%80%d0%b0%d1%82%d0%b8%d1%82%d1%8c-%d1%81%d0%b2%d0%be%d1%91-%d0%b2%d1%80%d0%b5%d0%bc%d1%8f/#comments Wed, 23 Jun 2010 17:22:29 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=532 Небольшой обзор по установке и использованию JRebel. Штука действительно очень клёвая. Предназначена для java-разработчиков. В основном для тех, которые занимаются enterprise-решениями, так как они достаточно громоздкие и пересборка/перезапуск всего проекта занимает значительное время (несколько минут), что сказывается как на скорости, так и на желании работать) JRebel позволяет увидеть результат от внесённых изменений практически мгновенно. Обзор проводился в следующем окружении:

  • Maven 2
  • JBoss 4.3.2
  • Seam 2.2.0.GA
  • IntelliJ IDEA 9

Однако, как будет видно из ролика, настройка должна пройти без проблем и для других вариантов окружения.

 


]]>
http://shchekoldin.com/2010/06/23/jrebel-%d1%85%d0%b2%d0%b0%d1%82%d0%b8%d1%82-%d1%82%d1%80%d0%b0%d1%82%d0%b8%d1%82%d1%8c-%d1%81%d0%b2%d0%be%d1%91-%d0%b2%d1%80%d0%b5%d0%bc%d1%8f/feed/ 8
Чё за музяка? http://shchekoldin.com/2010/06/20/%d1%87%d1%91-%d0%b7%d0%b0-%d0%bc%d1%83%d0%b7%d1%8f%d0%ba%d0%b0/ http://shchekoldin.com/2010/06/20/%d1%87%d1%91-%d0%b7%d0%b0-%d0%bc%d1%83%d0%b7%d1%8f%d0%ba%d0%b0/#comments Sun, 20 Jun 2010 10:48:18 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=518 Бывает порой слышишь какую-нить клёвую песню/музыку, но не знаешь/не можешь вспомнить кто исполнитель/что за песня. В нелёгком деле распознавания музыки призвана помочь программка shazam. Так как чудо это ставится только на мобильные устройства с нормальными ОС типа Symbian, Windows Mobile и т.д., то испробовать на деле у меня её не получилось, но отзывы довольно лестные, например:

Суть данной программы проста — вы даете ей секунд 15 послушать некую музыку, а она определяет, что это за композиция и кто исполнитель. Даже обложку из интернета закачивает и ссылки на Amazon и Youtube даёт.

И главное, она определила практически все треки, которые я ей подсунул. Удобно, когда играет что-то по радио, или музыка в каком-то фильме или сериале, или что-то на фоне играет в кафе, а ты хочешь понять, что же это.

Gluek’s blog

Вот тут ещё небольшое описалово. Но, к сожалению, на моём чуде китайской промышленности эту прогу не поставить, поэтому нашёл аналог — Tunatic. Она бесплатная, есть версия для Windows и Mac. У меня успешно распознала песни исполнителей:

  • Кипелов;
  • Yngwie J. Malmsteen;
  • Van Halen;
  • Lacuna Coil;
  • Rammstein.

Интерфейс очень минималистичный:

tunatic

По-моему круто) Так же было бы интересно услышать мнение по поводу shazam, если кто-то себе установит. Также существует бесплатный отечественный сервис по распознаванию музыки — AudioTag.info.

]]>
http://shchekoldin.com/2010/06/20/%d1%87%d1%91-%d0%b7%d0%b0-%d0%bc%d1%83%d0%b7%d1%8f%d0%ba%d0%b0/feed/ 1
Sharing JSESSIONID cookie across subdomains on JBoss http://shchekoldin.com/2010/05/27/sharing-jsessionid-across-subdomains/ http://shchekoldin.com/2010/05/27/sharing-jsessionid-across-subdomains/#comments Thu, 27 May 2010 09:08:26 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=499 The problem with sharing cookie with JSESSIONID value arises when we start use subdomains system in our application. For example: images.portal.com, security.portal.com, etc.

Respectively a cookie with unique JSESSIONID value will be created for each domain address and you can get some problems. For example, with autorization if it stores credentials in session. So for our example we will have three diffrent cookies with three different session ids.

# Domain Value
1 portal.com JSESSIONID1
2 images.portal.com JSESSIONID2
3 security.portal.com JSESSIONID3

Not very good, really?) Our cookie must have “.portal.com” domain to starts sharing across all subdomains (and there also will be only one session cookie instead of three). I didn’t find any standart solution for this problem. Possible solutions are:

  1. hardcode domain name in TomCat source files and recompile them (heh, I think this is the most popular solution for this problem over the Internet, but it isn’t our way);
  2. use custom valve to set domain name (very flexible solution, so we will used it);
  3. write cookie with true domain from application. But this will make your application logic more complicated and you will have two cookie with identical JSESSIONID value and different domains. “.portal.com” — from your application, “portal.com” — from TomCat.

Valve is the great software development company and it is also a filter that can do some transformation with request. In our case the valve must takes session cookie from request and rewrites its’ domain to some that we setup in config file. It’s very easy, ’cause problem is already solved. From previous link we saw how to work with valve and now we can go here.

Step-by-step guide (for JBoss 4.2.3):

  1. download customvalve2.zip file;
  2. put customvalve.jar file from archive to “JBOSS_HOME/server/YOUR_CONFIGURATION/lib” folder;
  3. add string “<Valve className=”com.redhat.jboss.support.ConfigureSessionCookieValve” cookieDomain=”.portal.com” />” to your host in “JBOSS_HOME/server/YOUR_CONFIGURATION/jboss-web.deployer/server.xml” file. Looks like:
    <Host name="localhost"
        autoDeploy="false" deployOnStartup="false" deployXML="false"
        configClass="org.jboss.web.tomcat.security.config.JBossContextConfig"
        >
        ...
        <Valve className="com.redhat.jboss.support.ConfigureSessionCookieValve"
            cookieDomain=".portal.com" />
        ...
    </Host>

And at last let us see the method that does all work (located in ResponseWrapper.java):

// Called from addCookie() and addCookieInternal() methods.
protected void configureSessionCookie(Cookie cookie) {
    if (Globals.SESSION_COOKIE_NAME.equals(cookie.getName())) {
        if (cookiePath != null) {
            cookie.setPath(cookiePath);
        }
        if (cookieDomain != null) {
            cookie.setDomain(cookieDomain);
        }
        if (cookieSecure != null) {
            if (cookieSecure.equalsIgnoreCase("true")) {
                cookie.setSecure(true);
            } else if (cookieSecure.equalsIgnoreCase("false")) {
                cookie.setSecure(false);
            }
        }
    }
}

That’s all!) Valve sources lie in downloaded archive so they can be easily modificated for your purposes.

]]>
http://shchekoldin.com/2010/05/27/sharing-jsessionid-across-subdomains/feed/ 2
Яблоко из Гонконга (M002L) http://shchekoldin.com/2010/04/19/%d1%8f%d0%b1%d0%bb%d0%be%d0%ba%d0%be-%d0%b8%d0%b7-%d0%b3%d0%be%d0%bd%d0%ba%d0%be%d0%bd%d0%b3%d0%b0-m002l/ http://shchekoldin.com/2010/04/19/%d1%8f%d0%b1%d0%bb%d0%be%d0%ba%d0%be-%d0%b8%d0%b7-%d0%b3%d0%be%d0%bd%d0%ba%d0%be%d0%bd%d0%b3%d0%b0-m002l/#comments Mon, 19 Apr 2010 19:55:39 +0000 Alexander Shchekoldin http://shchekoldin.com/?p=366 Вступление

Данный девайс был куплен мною около 4 месяцев назад. И вот, наконец-то собравшись с силами, я решил написать небольшой обзор про это чудо устройство)

Захотелось)

Идея прикупить себе iPhone появилась у меня довольно давно, но немного смущала цена данного девайса) Ибо отдавать около 20 000 рублей за телефон я пока не готов. Оставалось только ждать улучшения материального положения… но всё сложилось иначе)

Во время пребывания в гостях у одного моего друга он показал мне забавный китайский интернет-магазин Focal Price. Собственно вся забавность заключалась в том, что там продавались самые невероятные устройства за самые невероятные деньги. Одной из групп таких устройств являются подделки под iPhone. Среди которых были обнаружены два интересных экземпляра F003+ и M002L. Оба телефона обладают примерно одинаковыми характеристиками. Выбор был сделан в пользу последнего исключительно на основе отзывов с форумов) Время покупать!

Покупка

На следующий день я оформил себе карточку для рассчётов через систему PayPal (некоторые ласково зовут её “Палкой”), положил на неё необходимую сумму в размере 132$ и оформил заказ на вожделенный телефон) Теперь осталось только ждать. Прождал я около 3-х недель (довольно быстро, так как некоторые ждут и по 2-а месяца).

Изучение

Упаковка

Ура! Пришло извещения с почты. Бежим получать! В посылке была симпотная коробка с розами)

m002l-box-1 m002l-box-2

Телефон и прочее

Внутри указанной выше коробки цветов не оказалось… но обнаружился телефон со всякими причендалами в числе которых:

  • сам телефон;
  • два аккумулятора!!!
  • карта памяти на 2 Гб;
  • небольшой мануал, в котором помимо прочего мелко-мелко вписан пароль для изменения настроек телефона)
  • гарнитура. Качество изготовления и удобство в использовании довольно паршивое, поэтому, проверив её работоспособность, спрятал подальше;
  • USB-кабель для зарядки и взаимодействия с ПК;
  • переходник с USB-кабеля на розетку. Для наших розеток не подойдёт — нужен ещё один переходник, который я заказал на том же сайте через пару недель)

m002l-all m002l-phone-1 m002l-phone-2 m002l-phone-3 m002l-phone-4 m002l-phone-5 m002l-phone-6 m002l-adapter

Форма

Вроде повторяет iPhone) Сначала думал, что телефон несколько великоват и мне будет неудобно его таскать. Ан, нет. Удобно лежит в руке и кармане)

Дисплей

Очень “цветастый” и яркий. До такой степени, что я убавил яркость на минимум и всё равно всё выглядело вполне прилично) Правда на ярком солнце нифига не разобрать. На лицевой панели были наклеены плёнки в количестве 3-х штук, две из которых были удалены за ненадобностью (первая плёнка — просто упаковочная). Вообще я думал, что отодранная вторая плёнка повысит чувствительность экрана, но я ошибся) “Сенсорность” для меня в новинку, поэтому радовался как ребёнок, хоть и не всегда всё правильно нажимается и реагирует. Защитная плёнка очень маркая, но при работающем дисплее следов почти не видно.

Софт

Далее кратко пробегусь по программам и функциям телефона, которыми пользуюсь чаще всего или которые показались мне интересными.

Экран блокировки

Можно заменить картинку на свою. К “слайдеру” разблокировки надо привыкнуть, не всегда получается “подцепить” его с первого раза.

m002l-main-screen

Главное меню

Справившись со слайдером, попадаем в главное меню. Меню разбито на страницы. Количество страниц не ограничено. Можно перетаскивать значки как угодно. Свои значки добавлять нельзя( Весь пользовательский софт попадает в папку “Java” и вытащить ярлык в меню не получится.

m002l-main-menu-1 m002l-main-menu-2 m002l-main-menu-3

Звонилка

Самое главное) Кнопки большие, не промахнёшься. Нажимаются на удивление хорошо. Есть возможность выбрать с какой симки звонить. Внизу расположены иконки… никогда по внешнего виду не угадаете какие функции за ними скрываются))) Слева-направо: “История звонков”, SMS, “Контакты”, “Главное меню”, “Быстрый поиск по контактам”.

m002l-dial-screen

Календарь

Есть всё, что нужно. Можно планировать разнообразные события, дабы не забыть) Особенно порадовало, что на иконке календаря в главном меню отображается текущее число.

m002l-calendar

Электронные книги

Вот этой штукой я пользуюсь с удовольствием. Закачал книженцию и читай где хочешь. Гораздо удобнее бумажного варианта. Размер экрана вполне подходит для комфортного чтения. Есть поиск и автопрокрутка. Также есть полноэкранный режим, закрашивающий фон ужасным синим цветом) Цифры внизу — это функциональные клавиши для того же поиска, полноэкранного режима и прочих фиговин. Понимает только формат txt в кодировке utf-8. Впрочем, всегда можно поставить какую-нить java-читалку)

m002l-ebook m002l-ebook-full-screen

Игры

Играл в основном только в одну из встроенных игрушек — “Мотогонки”. Прошёл всю) Управление сенсорное. Из других игр есть некое подобие тетриса и кости, в которых используется встроенный в телефон акселерометр (чтобы поиграть телефон нужно нехило потрясти).

m002l-moto-racer m002l-3d-brick m002l-dice

SMS

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

m002l-sms-russian-abc m002l-sms-english-abc m002l-sms-english-t9 m002l-sms-numbers

Internet

По веб-страничкам ползаю через заботливо установленную на заводе Opera (причём забота была настолько велика, что установлено примерно 10 браузеров на разных языках, а удалить их не так просто). Вообщем удобно) Также поддерживается акселерометр, который срабатывает иногда весьма неожиданно) Сижу через WiFi. Настройка GPRS/EDGE несколько запутана, но я справился)

m002l-opera

Настройки телефона

Всё довольно стандартно. Можно настроить какие из симок активны, откалибровать экран и прочее. Ещё есть так называемое “инженерное меню”. В нём можно (я бы сказал нужно) поменять уровень громкости микрофона и динамиков, так как поначалу слышимость очень плохая. Так же через это меню можно поменять ещё кучу вещей… в том числе сломать телефон навсегда)

m002l-settings m002l-dual-sim-settings m002l-accelerometer-settings m002l-phone-setup m002l-security-setup m002l-sound-effects

Будильник

Звенит. Причём, дай боже так звенеть каждому будильнику. Аж подпрыгиваешь, когда слышишь! Можно настроить до 5 будильников на разное время, разные дни недели и разные мелодии (можно ставить свои).

m002l-alarm m002l-alarm-edit

Калькулятор

Большие красивые кнопки, при нажатии на которые можно производить различные магические манипуляции над числами)

m002l-calculator

Менеджер файлов

Глядим, что же есть в памяти телефона и на карте памяти, и удаляем при случае) Несколько тормозно работает. Зашитые на заводе программы удалить нельзя.

m002l-file-manager m002l-file-manager-phone

Видео плеер

Поддерживает mp4 и 3gp (тестил только в этом формате). При небольшом разрешении довольно шустро воспроизводит видео, при большом… ну Вы поняли)

m002l-video-player

Телевизор

До чего техника дошла! Действительно… дошла. Телефон оборудован телескопической антенной для поиска внеземного разума. С гуманоидами пообщаться не удалось, но антенна вполне пригодна для приёма телепередач при хорошем сигнале. В моём случае почти всегда был шум, но иногда удавалось поглядеть какую-нибудь передачу)

m002l-tv

Радио

Работает немного получше своего телевизионного собрата) Вполне успешно ловит местные станции, ничего выдающегося.

m002l-radio

Камера

На самом деле их аж две) Но качество снимков при просмотре на компе оставляет желать лучшего. На телефоне смотреть терпимо. Вот примеры фоток:

m002l-camera-1 m002l-camera-2 m002l-camera-3

Связь с PC

Для работы с телефонной книгой пользуюсь программкой MediaTek Phone Suite. Через неё гораздо удобнее приводить в порядок свои контакты, а также делать backup телефонной книги. Настроить телефон для работы с сервисом Mobical не получилось( Выглядит Phone Suit вот так:

m002l-phone-suit

Характеристики

  • GSM 850/900/1800/1900MHz.
  • Поддержка 2-х сим-карт.
  • Языки: английский, французский, немецкий, испанский, португальский, турецкий, русский, итальянский, боснийский, греческий, индонезийский, вьетнамский, малайзийский, польский, голландский, чешский и арабский.
  • Bluetooth 2.0
  • Поддержка MP3/MP4/3GP.
  • Две 3-мегапиксельные камеры.
  • 3.5″ QVGA сенсорный дисплей с высоким разрешением.
  • FM радио.
  • WiFi.
  • Акселерометр.
  • Поддержка Java и встроенный Google Maps.
  • Телевизор (SECAM/PAL/NTSC).
  • Настраиваемое меню.
  • Поддержка карт памяти до 32 Гб.
  • Габариты: 60×115x12 мм.

Итого

Смело могу сказать, что телефон стоит потраченных на него денег. Да, до того же iPhone ему как до Луны, но со своими задачами он справляется. Про “инженерное меню”, а также по всяким тонкостям настройки и проблемам можно почитать здесь. Вот небольшое видео напоследок:

]]>
http://shchekoldin.com/2010/04/19/%d1%8f%d0%b1%d0%bb%d0%be%d0%ba%d0%be-%d0%b8%d0%b7-%d0%b3%d0%be%d0%bd%d0%ba%d0%be%d0%bd%d0%b3%d0%b0-m002l/feed/ 66