четверг, 31 марта 2011 г.

Работа с JSON в Java

JSON (JavaScript Object Notation) - текстовый формат обмена данными, легко читаемый людьми и основанный на синтаксисе Javascript. JSON, как правило, используется с Javascript, если быть более точным - при обмене данными между Javascript и сервером. Стоит отметить, что JSON обладает существенным преимуществом перед XML - он менее избыточен.

Пример JSON объекта:

{
  userId: 32,
  firstName: "Алексей",
  lastName: "Голобурдин",
  address: {
    country: "Россия",
    city: "Москва"
  },
  phone: "8 (905) 777 77 77"
}

Как видно, все предельно просто. Фактически, JSON объект является валидным Javascript кодом, то есть может быть исполнен интерпретатором Javascript. Но как сгенерировать JSON на сервере, и как распарсить пришедший от клиента JSON?

PHP программисты сейчас довольно ухмыльнутся, ведь у них есть стандартные функции json_encode и json_decode, позволяющие легко и просто гонять PHP сущности в JSON и обратно.

Java программистам приходится сложнее - надо поддерживать свою крутость (где PHP - Personal HomePage language, и где крутая Enterprise Java), поэтому с JSON приходится работать путем сторонних библиотек, коих на офсайте json.org приводится немало. Программисты Python здесь тоже ухмыльнутся - их дзен "Должен быть только один, и лучше всего очевидный, способ сделать это".

Итак, в данной статье рассматривается использование отличной java библиотеки для генерирования и парсинга JSON - json-simple, официальный сайт на google code. Кстати, там можно найти немало отличных примеров!

Вот таблица сопоставления типов JSON и Java:

JSONJava
stringjava.lang.String
numberjava.lang.Number
true | falsejava.lang.Number
nullnull
arrayjava.util.List
objectjava.util.Map

Тааак, и как нам всем этим добром пользоваться?

Примеры давай, примеры!


Cоздадим JSON строку!

//import org.json.simple.JSONObject;
  
JSONObject resultJson = new JSONObject();

resultJson.put("name","foo");
resultJson.put("num",new Integer(100));
resultJson.put("is_vip",new Boolean(true));
resultJson.put("nickname",null);
System.out.print(obj.toString());
// {"name": "foo", "num": 100, "is_vip": true, "nickname: null}

Ну ладно, круто. А что-то посложнее?

JSONArray ar = new JSONArray();
JSONObject obj = new JSONObject();
JSONObject resultJson = new JSONObject();

ar.add("first");
ar.add(new Integer(100));

obj.put("one", "two");
obj.put("three", "four");

resultJson.put("paramsArray", ar);
resultJson.put("paramsObj", obj);
resultJson.put("paramsStr", "some string");
System.out.print(obj.toString());
// {"paramsArray": ["first", 100],
//  "paramsObj": {"one": "two", "three": "four"},
//  "paramsStr": "some string"}

Думаю, комментарии здесь излишни, все действительно чрезвычайно интуитивно и просто. Другие примеры генерирования JSON можно посмотреть на офсайте.

Парсинг JSON


А вот с парсингом дела обстоят намного хуже. Шутка:)



String json = "{paramsArray: [\"first\", 100],"
            + "paramsObj: {one: \"two\", three: \"four\"},"
            + "paramsStr: \"some string\"}";

JSONParser parser = new JSONParser();

Object obj = parser.parse(json);
JSONObject jsonObj = (JSONObject) obj;
System.out.println(jsonObj.get("paramsStr"));
// some string

JsonObject jo = jsonObj.get("paramsObj");
System.out.println(jo.get("three"));
// four

JsonArray ja = jsonObj.get("paramsArray");
System.out.println(ja.get(1));
// 100

Вот такая отличная библиотека. Приятной работы!

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

AUTO_INCREMENT поле в Oracle

Знакомые с MySQL программисты при первом знакомстве с СУРБД Oracle, вероятно, удивятся отсутствию в ней такой приятной возможности, как создание auto_increment полей, то есть полей, численное значение которых автоматически увеличивается при создании новых записей. Как правило, auto_increment поля используются как primary key - первичные ключи для таблиц.

В MySQL эти поля создаются буквально на лету:

CREATE TABLE users(
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    name CHAR(30) NOT NULL, PRIMARY KEY (id)
);

В такую таблицу можно вставлять записи, не задумываясь об автоматической генерации первичного ключа id, который будет увеличиваться на 1 для каждой новой записи. Иными словами, можно выполнить запрос вида

INSERT INTO userns(name) VALUES ("sterx"),("alex"),("kime");

и для каждой из созданных записей в случае MySQL будет автоматически вычислен id.

В Oracle auto_increment поля создаются несколько иначе, используются так называемые последовательности (sequence), или, другими словами, счетчики. Создадим последовательность:

CREATE SEQUENCE users_seq
START WITH 1 
INCREMENT BY 1 
NOMAXVALUE;

Как видно из объявления, последовательность начинается с 1 (конечно, при необходимости, можно задать другое значение), шаг последовательности также равен 1, последовательность не имеет максимального значения.

Теперь при создании записей в таблице нам необходимо присваивать полю id текущее значение счетчика-последовательности (или следующее значение счетчика в зависимости от реализации). Это можно сделать двумя способами - создать триггер, отслеживающий создание записей в таблице и присваивающий полю id значение счетчика, либо в самой команде INSERT использовать значение счетчика для задания значения id. Использование триггера с одной стороны усложняет реализацию, с другой стороны упрощает команды INSERT, поэтому выбор способа установки id оставлю на усмотрение читателя.

Рассмотрим оба варианта.

Использование триггера


create or replace trigger users_id_trg
before insert on users
for each row
begin
  if :new.id is null then
    select users_seq.nextval into :new.id from dual;
  end if;
end;

В теле триггера мы проверяем, что id новой записи не установлен пользователем, так как по своим причинам пользователь может захотеть установить значение id самостоятельно, не полагаясь на триггер. Если это не так - вычисляется новое значение счетчика и id устанавливается автоматически.

Использование хитрого INSERT


INSERT INTO users (id, name) VALUES (users_seq.nextval, 'sterx');

В данном случае новое значение счетчика вычисляется при каждом вызове INSERT, значение поля id вычисляется соответственно в самом теле команды без триггера.

PS. Созданные последовательности и триггеры легко удалить, поэтому не бойтесь с ними работать.

DROP SEQUENCE users_seq;
DROP TRIGGER users_id_trg;

вторник, 29 марта 2011 г.

ExtJS: изменились ли записи в хранилище?

Проблема: необходимо узнать, изменились ли записи в хранилище Store . Пример практической задачи, сводящейся к описанной - например, проверка состояния таблицы EditorGridPanel.

Решение:

if (Store.getModifiedRecords().length == 0)
    alert('Записи изменены');
else
    alert('Записи не изменены');

Аналогично можно проверить, например, изменились ли записи EditorGridPanel:

if (EditorGridPanelElement.store.getModifiedRecords().length == 0)
    alert('Записи изменены');
else
    alert('Записи не изменены');

Однако, этот подход не обнаруживает события удаления записей из хранилища! Таким образом, удалив одну или несколько записей, мы изменили хранилище, однако getModifiedRecords() не вернет нам ничего - событие удаления необходимо отслеживать самостоятельно. Предлагаемый автором вариант предельно прост: при загрузке хранилища мы устанавливаем ему метку dirtyMark, равную false - операции удаления не производились; при удалении обновляем метку в true:

  var myStore = new Ext.data.JsonStore({
    url: 'serviceUrl/',
    root: 'result',
    fields: myFields,
    listeners: {
      remove: function(){
        myStore.dirtyMark = true;
      },
      load: function() {
        myStore.dirtyMark = false;
      },
      save: {
        myStore.dirtyMark = false;
      }
    }
  });

Условие проверки изменения состояния хранилища в таком случае будет выглядеть следующим образом:

if (Store.getModifiedRecords().length == 0
       && myStore.dirtyMark === true)
    alert('Записи изменены');
else
    alert('Записи не изменены');

понедельник, 28 марта 2011 г.

ExtJS FormPanel, распределенный по табам TabPanel


Суть: имеем форму FormPanel, распределенную по нескольким табам (вкладкам) TabPanel. В качестве примера можно привести форму 4 на официальной странице примеров ExtJS.

Проблема: при отправке формы отправляются поля не все табов, если быть точным - отправляются только просмотренные табы.

Метод решения: очевидно, необходимо автоматически "просмотреть" или отрендерить (отрисовать) все табы TabPanel.

Решение: добавить в конфигурацию TabPanel параметр deferredRender: false, отвечающий за отложенный рендеринг табов. При установке этого параметра в false все табы рендерятся при первой возможности, не дожидаясь действий со стороны пользователя.

ExtJS ComboBox не отправляет valueField?

При использовании в формах ExtJS выпадающего списка ComboBox вы, должно быть, заметили проблему: форма отправляет отображаемое значение выпадающего списка (displayField), а не его фактическое значение (valueField). Если провести аналогию с обычным <select> HTML - отправляется Вася, а не 3.

<select name="user">
    <option value="1">Саша</option>
    <option value="2">Иннокентий</option>
    <option value="3">Вася</option>
</select>

Чтобы решить данную проблему и заставить ExtJS отправлять 3 вместо Вася, необходимо добавить конфигурационный параметр hiddenName:


var userName = new Ext.form.ComboBox({
    id:'userName',
    name: 'userName',
    fieldLabel: 'Имя',
    store: usersStore,
    displayField: 'name',
    valueField: 'id',
    hiddenName : 'razdel'
});

Будьте внимательны, параметр hiddenName должен быть уникальным для каждого поля формы - он используется при ее отправке вместо параметра name.

Протестировать параметры при отправке формы удобно с помощью Firebug.

воскресенье, 27 марта 2011 г.

RESTful приложение на ExtJS 3



О том, что такое RESTful приложение и почему это круто можно почитать на Wiki, хабре или даже в статье ibm. Фактически, RESTful приложение - это приложение, построенное на вызове удаленных REST веб сервисов, выполняющих CRUD функции (Create, Read, Update, Delete - создать, прочитать, обновить и удалить соответственно). Каждый REST сервис характеризуется URI, по которому он вызывается, и некоторым набором принимаемых и возвращаемых параметров.

Особенность REST подхода состоит в активном использовании HTTP методов для выполнения CRUD функций: вместо использования URI адресов типа

/user/23/
/user/23/?action=delete
/user/23/?action=update


предлагается использовать стандартный адрес /user/23/, обращение к которому ведется с использованием HTTP команд GET, DELETE, PUT. Для создания нового элемента используется HTTP команда POST (в нашем примере к адресу /user/ - ID создаваемого элемента до момента создания мы, как правило, не знаем). Для приложений, функциональность которых не выходит за рамки CRUD, а можно уверенно сказать, что это большинство современных веб приложений, RESTful подход является лучшим выбором для создания веб сервисов сервисов и API.



В данной статье я предлагаю рассмотреть создание простейшего RESTful приложения с использованием ExtJS 3 на клиентской стороне. Серверную часть можно написать на любой любимой технологии - будь то Java, PHP, Python или что-то еще.

В этой статье подразумевается, что Ext уже подключен, также рекомендуется использование связки Firefox + Firebug для отладки Javascript и просмотра http заголовков запросов и ответов. Итак, поехали!

Будем использовать хранилище (Store) с настроенным HttpProxy. Дело в том, что HttpProxy поддерживает api для установки методов загрузки, обновления и удаления данных в хранилищах Store:

var myProxy = new Ext.data.HttpProxy({
    api: {
        read:    {url: 'app/users/', method: 'GET'},
        create:  {url: 'app/users/', read: 'POST'},
        update:  {url: 'app/users/', method: 'PUT'},
        destroy: {url: 'app/users/', method: 'DELETE'}
    }
});

Как видим, можно настроить разные URI адреса для действий чтения, обновления и т.д., однако это плохо согласуется с идеологией REST, поэтому мы будем использовать один адрес с разными передаваемыми параметрами и разными HTTP методами. Стоит отметить, что по умолчанию используется метод POST, поэтому в каждом случае следует вручную указывать параметр method.

Сконфигурируем JsonReader и JsonWriter:

var myReader = new Ext.data.JsonReader({
    successProperty: 'success', // json элемент со статусом
                                // результата (false or true)
    idProperty: 'id',
    root: 'data', // корневой элемент с данными
}, [
    {name: 'id'},
    {name: 'email', allowBlank: false},
    {name: 'first', allowBlank: false},
    {name: 'last', allowBlank: false}
]);

var myWriter = new Ext.data.JsonWriter({
    encode: true,         // важно! кодировать Store
    writeAllFields: false // сохранять только измененные поля
});

Теперь можно сконфигурировать само хранилище. Фактически, мы указываем здесь reader, writer и proxy:

var myStore = new Ext.data.Store({
    id: 'user',
    proxy: myProxy,
    reader: myReader,
    writer: myWriter,
    idProperty: 'id', // важно! Id элементов хранилища
    autoSave: true // при изменении хранилища будет автоматически
                   // сформирован запрос на его сохранение
});

Теперь при изменении хранилища - то есть изменении записей в хранилище - будет автоматически посылаться запрос к серверу для сохранения всех модификаций. Если по каким-то причинам мы хотим отправлять запрос на сохранение текущего состояния хранилища сами, необходимо установить конфигурационный параметр autoSave хранилища в false; в таком случае запрос на сохранение будет сформирован при вызове метода save() хранилища:

myStore.save();

Обратите внимание на параметр idProperty хранилища; в случае некорректной установки этого параметра при сохранении измененного элемента все равно будет создан запрос на создание нового элемента вместо сохранения существующего (POST вместо PUT). Это связано с тем, что каждая запись в хранилище однозначно идентифицируется параметром id, который должен быть связан с реальным идентификатором нашей записи (id в JsonReader - как правило, идентификатор записи в базе данных).

Также обратите внимание, что если используется mapping в параметрах fields для JsonReader - в idProperty нужно указывать реальное название параметра в Json данных, а не название колонки данных в Ext'е. Для тех, кто не в курсе - маппинг это сопоставление имен параметров в Json данных и имен этих же параметров в Ext для манипуляции с ними, при желании можно задавать разные имена. Иными словами, мы могли бы так настроить JsonReader:

var myReader = new Ext.data.JsonReader({
    successProperty: 'success', // json элемент со статусом
                                // результата (false or true)
    idProperty: 'id',
    root: 'data', // корневой элемент с данными
}, [
    {name: 'ExtIdentificator', mapping: 'realJsonId'},
    {name: 'email', allowBlank: false},
    {name: 'first', allowBlank: false},
    {name: 'last', allowBlank: false}
]);

Для idParamater в таком случае надо указывать realJsonId.

Итак, теперь мы умеем сохранять данные хранилища. Но как модифицировать эти данные? Как правило, хранилище используется либо для загрузки данных с сервера с последующим использованием в формах или других элементах страницы, либо для загрузки данных в таблицу Grid. В случае использования EditorGridPanel данные таблицы и данные хранилища полностью синхронизированы, то есть при изменении данных таблицы автоматически меняется и хранилище, с ним связанное. Это первый способ изменения данных хранилища.

Другой способ - ручной. Например, мы хотим обновить данные первого пользователя в хранилище myStore:

myStore.getAt(0).data.items[0].name = 'Alexey';

myStore.getAt(0) возвращает первую запись (Record) хранилища; свойство data записи хранит в себе все данные записи.

Таким образом, изменив одним из описанных способов хранилище, мы имеем возможность сохранить новое состояние хранилища либо автоматически (путем настройки хранилища autoSave: true), либо вручную путем вызова метода хранилища save().

Пример использования JsonWriter можно посмотреть здесь.

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

Векторизация PDF в Autocad с WinTopo

Возникла задача сконвертировать (векторизовать) PDF файл в чертеж AutoCAD? В видео инструкции подробно описан процесс данного действия, для векторизации используется WinTopo.