Показаны сообщения с ярлыком Java. Показать все сообщения
Показаны сообщения с ярлыком Java. Показать все сообщения

пятница, 5 июля 2013 г.

Веб-сервис Яндекс.Спеллера. Пример использования в Java.

Проверка орфографии из программы на Java с использованием веб-сервиса Яндекс.Спеллер

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

Разумеется, жена не могла удержаться от соблазна воспользоваться чьими-либо подсказками в решении этих головоломок, и я, блеснув пару раз удачным попаданием в цель, посчитал игрушку вполне логичной, а свои умственные способности достойными похвалы. Однако это продлилось не долго. После того, как правильным ответом, объединяющим фотографии лаптей, электронной схемы на транзисторах и паровоза, было слово "прототип", стало понятно, что народу предлагают играть в обычные угадалки. Мне даже показалось странным, почему уровень абстракции автора дошел только до "прототипа", а не до "материи" с философским подтекстом.

Как бы то не было, все это время я оставался программистом. И не просто программистом, а программистом изучающим разработку веб-сервисов и их клиентов на Java. Поэтому, я тут же нашел в Google страницу с описанием веб-сервиса Яндекс.Спеллер и написал класс, с помощью которого можно было бы отправить этому сервису набор букв и получить ответ о том, является ли этот набор букв словом из словаря Яндекс.Спеллера или нет. Таким образом, я рассчитывал написать на основе этого класса программу, которая бы перебирала из заданной кассы в 20 букв все возможные комбинации по заданному количеству букв, фильтровала бы их на основе простых фильтров (открытые буквы, мягкий знак в начале слова и т.д.) и спрашивала бы у Яндекс.Спеллера, принадлежит ли данное слово его словарю. Все получившиеся таким образом правильные слова я надеялся представить в виде списка из которого потом выбрать те, что могли бы подойти под содержание представленных фотографий.

Ублажая роботов поисковой системы Яндекса приведу ссылку на страницу, где я получил информацию об интересуемом меня веб-сервисе Яндекс.Спеллера: Яндекс API/Руководство разработчика/Web Service API/Метод checkText.

Забегая вперед скажу, что после запуска этой программы, Яндекс забанил меня примерно после двух десятков тысяч обращений к своему спеллеру и я уже сутки получаю в ответ на запросы к нему ответ "403 Forbidden", так что прикладная польза от этой затеи оказалась нулевая. Однако, программа была написана и соответствующая теория по работе с веб-сервисами была закреплена дополнительным практическим кодом. Вообще, вопрос об отказе обслуживания моих запросов остался. Почему? Разве не для программных обращений Яндекс выставил наружу свои публичные веб-сервисы? Что в моих запросах оказалось неправомерным с точки зрения его автоматов? Частота обращения? Простота запросов (по одному слову, вместо пакета, например, с сотней или тысячей слов)? Или огромное количество "слов" с ошибками? Ответа на этот вопрос я пока не знаю. Возможно, кто-то из читателей этих строк сможет грамотно прокомментировать ситуацию.

Сейчас я хочу добраться до сути данного сообщения в блоге. Пора представить код написанного класса и дать какие-нибудь вводные комментарии по этому коду. Сразу скажу, что интерфейс класса не полировался и в нем одновременно можно увидеть как избыточность, так и неполноту обработки ответного сообщения спеллера. Писал и проверял то, что было интересно, не забывая о главном. Так что если кто-то захочет это использовать, то, видимо, придется перекроить код интерфейса и обработки под свои нужды. Здесь я хочу просто показать пример того, как это можно сделать в Java используя известные сторонние библиотеки.

Обратите внимание. В коде используются возможности Java 7.

Представленный далее класс состоит из приватных полей внутренних данных, конструктора, акцепторов (интерфейс для получения некоторых внутренних данных) и, собственно, главного метода check(), который и выполняет синхронное обращение к веб-сервису Яндекса, получает ответ, разбирает его и сообщает о результате проверки.

package ru.knzsoft;

import java.io.UnsupportedEncodingException;

import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

public class YandexSpellChecker 
{
    private Client client;
    private List<String> help_list = new ArrayList<>();
    private long spell_error_code = 0;
    private int http_status = 0;
 
    public YandexSpellChecker() {
        client = Client.create();
    }
 
    public List<String> getHelpList() {
        return Collections.unmodifiableList(help_list);
    }
 
    public long getSpellErrorCode() {
        return spell_error_code;
    }
 
    public int getHttpStatus() {
        return http_status;
    }
 
    public enum CheckResult {
        OK,
        SPELLING_ERROR,
        ENCODING_ERROR,
        PARSING_ERROR,
        HTTP_ERROR;
    }
 
    public CheckResult check(String word) {
        spell_error_code = 0;
        http_status = 0;

        try {
            word = URLEncoder.encode(word, "UTF8");
        } catch (UnsupportedEncodingException e) {
            return CheckResult.ENCODING_ERROR;
        }

        WebResource webResource = client
            .resource("http://speller.yandex.net/services/spellservice.json/checkText"
            + "?text=" + word);

        ClientResponse response = webResource.accept("application/json")
            .get(ClientResponse.class);

        http_status = response.getStatus(); 
        if (http_status != 200) {
            return CheckResult.HTTP_ERROR;
        }

        String output = response.getEntity(String.class);
        JSONParser parser = new JSONParser();
        Object obj;
        try {
            obj = parser.parse(output);
        } catch (ParseException e) {
            return CheckResult.PARSING_ERROR;
        }

        JSONArray array = (JSONArray) obj;
        if (array == null) {
            return CheckResult.PARSING_ERROR;
        }

        if (array.isEmpty()) {
            return CheckResult.OK;
        }

        @SuppressWarnings("unchecked")
        Iterator<Object> it = array.iterator();
        while (it.hasNext()) {
            Object o = it.next();
            JSONObject jo = (JSONObject) o;

            o = jo.get("code");
            if (o == null) {
                return CheckResult.PARSING_ERROR;
            } else {
                spell_error_code = (java.lang.Long) o;
            }

            help_list.clear();
            o = jo.get("s");
            JSONArray sarray = (JSONArray) o;
            if (sarray != null) {
                @SuppressWarnings("unchecked")
                Iterator<Object> sit = sarray.iterator();
                while (sit.hasNext()) {
                    o = sit.next();
                    String v = (String) o;
                    help_list.add(v);
                }
            }
        }
        return CheckResult.SPELLING_ERROR;
    }
}

Для работы с веб-сервисами, в моей реализации, используется библиотека Jersey. Это известная и популярная библиотека для работы с веб-сервисами поддерживающими архитектуру REST. Для тех, кто не знает смысла этой умной аббревиатуры, поясню. Такая архитектура подразумевает общение на уровне сообщений, где каждое сообщение несет в себе весь необходимый контекст запроса. Т.е. не нужно создавать сессии, в которых хранить и накапливать контекст таких диалогов. Как вариант, такая архитектура может строиться на основе HTTP. Этот протокол, в чистом виде, как раз является протоколом REST, например, в отличие от того же протокола SIP, который основан на HTTP, но уже в наборе своих заголовков несет информацию не только о номере сессии, но и о номере коннекта внутри сессии.

Следует обратить внимание, что библиотека Jersey несколько преобразилась начиная со второй версии. Насколько мне показалось, коды, использующие первую и вторую версию Jersey не совместимы ни в какую из сторон. Так как меня, на этот момент интересовала первая версия Jersey, то и приведенный код использования Jersey соответствует первой версии библиотеки. Конкретно, для компиляции этого кода я использовал в проектном файле системы сборки Maven указание на следующую зависимость.

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-client</artifactId>
    <version>1.8</version>
</dependency>

Собственно, весь код использования Jarsey можно свести к следующему фрагменту.

  // Создаем клиента для работы с сервисом REST
  Client client = Client.create();

  // Уточняем вид клиента и адрес ресурса. Создаем веб-клиента под конкретный ресурс
  WebResource webResource = client
      .resource("http://speller.yandex.net/services/spellservice.json/checkText"
      + "?text=" + word);

  // Важно! Синхронная операция. Делаем запрос GET и ждем получения ответа
  ClientResponse response = webResource.accept("application/json")
      .get(ClientResponse.class);

  // Из объекта ответа извлекаем статус операции. 
  // Для HTTP, код 200 - успешное выполнение
  http_status = response.getStatus(); 
  if (http_status != 200) {
      return CheckResult.HTTP_ERROR;
  }

  // Получаем строку с телом сообщения. В нашем случае, строку JSON.
  String output = response.getEntity(String.class);

Некоторые дополнительные подробности по работе с Jersey можно получить на странице Client API из документации по первой версии библиотеки.

Еще раз обратите внимание на синхронность операции получения ответа на запрос GET к веб-сервису. Обратите внимание, что на время получения этого ответа, исполнение этой программы приостановится. Если вас это не устраивает, то придется искать другой способ выполнения данной операции.

Дальнейший код связан с обработкой ответа полученного от веб-сервиса. Так как ответ приходит в виде строки JSON, необходимо выполнить парсинг данной строки. В данном коде используется простая библиотека от Google, с многоговорящим названием JSON-simple. Для включения этой библиотеки в зависимости проектного файла системы сборки Maven, я использую следующий код.

<dependency>
    <groupId>com.googlecode.json-simple</groupId>
    <artifactId>json-simple</artifactId>
    <version>1.1</version>
</dependency>

Схема использования JSON-siple проста. Создается объект парсера сообщения JSON - JSONParser. Объекту парсера отдается сообщение со строкой JSON. Результатом парсинга является объект базового класса, который мы потом пребразуем к ожидаемому типу данных. Так как мы ожидаем массив JSON, то следует воспользоваться типом JSONArray. Класс JSONArray реализует интерфейс List, поэтому мы сможем выполнить обход массива через объект итератора. Далее, извлекаем основные данные из ответа, в том числе и массив подсказок по вариантам исправления ошибочного слова.

Подробности по работе с классами из библиотеки JSON.simple можно посмотреть на страницах ресурса JSON-simple.

Комментируя данный код, следует обратить внимание на еще одну маленькую деталь - кодирование русских букв в слове запроса. С этой функцией успешно справляется класс java.net.URLEncoder из стандартной библиотеки JDK.

четверг, 24 ноября 2011 г.

Как начать работать с Ant




Введение


За последнюю неделю у меня два раза попросили рассказать об Ant. Решил, по этому поводу, написать несколько строчек для начинающих пользователей Ant и для тех, кто не знает, что это такое, но интересуется возможностями применения.

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

По сути своей работы Ant аналогичен известному средству обработки сценариев make. Обычно, многие программисты, работающие с Си/Си++ знакомы с make не понаслышке. Особенно это относится к программистам, которые работают в *nix. Однако make исполняет сценарии зависимые от платформы, или, если быть более точным, от командного процессора для которого они написаны.


Как работает Ant


Ant исполняет сценарии, которые, по умолчанию, располагаются в файле с названием build.xml. Как видно из расширения файла, его содержимое представлено в формате XML.

Корневым тегом документа является тег project. Проект состоит из целей - теги target, а цели состоят из задач. Задач для Ant определено несколько десятков. Каждая задача представлена своим тегом.

Вообще, синтаксис файла сценария для Ant достаточно прост и интуитивно понятен. Особенно, если знать основы его структуры.

Рассмотрим это на примере простого сценария. Приведем возможный вариант файла build.xml.

<project name='test' default='release' >

    <target name='spec' >
        <echo message='Target: spec' >
    </target> 

    <target name='release' >
        <echo message='Target: release' >
    </target> 

    <target name='debug' depends='spec' >
        <echo message='Target: debug' >
    </target> 

</project>  

Прокомментируем представленный вариант сценария.

Описывается проект с именем test, в котором целью по умолчанию объявляется цель release. Т.е., если запустить Ant без специального указания исполняемой цели, то, согласно этому сценарию, будет выполнена цель release.

Кроме цели release представлены еще цели debug и spec, причем, исполнение цели debug зависит от исполнения цели spec. Значит, если заказать исполнение цели debug, то сначала выполнится цель spec, и только в случае ее успешного исполнения будет выполнена цель debug.

Во всех трех представленных целях выполняется одна простейшая задача echo, которая выводит в стандартный выходной поток (в нашем случае, в консоль) сообщение заданное атрибутом message.

Теперь остановимся на исполнении этого сценария. В системе Ant, наверное, главной утилитой является утилита ant, которая и призвана исполнять сценарии. Давайте зайдем в командную строку и в директории, где расположен нужный нам файл build.xml выполним следующую команду.

$ ant

(символ $ означает стандартное приглашение пользователя в системах *nix, поэтому его вводить не надо. Для DOS/Windows это будет символ > )

Такой вызов ant приведет к исполнению стандартной цели сценария, которой, в нашем случае, является цель release. Следовательно, при исполнении нашего сценария, мы увидим следующее сообщение в консоли исполнения.

$ ant
Target: release

Чтобы выполнить цель debug нашего сценария, необходимо выполнить ant с аргументом debug.

$ ant debug
Target: spec
Target: debug

На этом можно закончить краткое введение в работу системы Ant.Следует лишь добавить, что мы коснулись лишь малой части возможностей Ant и если вы хотите познакомиться с Ant в оригинале, то просто найдите в Google родной сайт системы и получите всю необходимую информацию из первых рук.

Для ленивых я предложу несколько готовых ссылок (которые, разумеется, могут в любой момент устареть).
http://ant.apache.org/ - Официальный сайт системы Ant.
http://ant.apache.org/manual/index.html - Главная страница официальной документации по системе Ant. Для просмотра всего списка задач выберите раздел Ant Tasks -> List of Tasks.


Установка Ant


Если вы работаете в Linux, то проблемы с установкой Ant у вас не будет. Разумеется, при условии, что вы умеете пользоваться своим Linux. Обычно, все что нужно сделать это обратиться к своему установщику с просьбой установки Ant, и, после завершения его работы, можно будет сразу приступать к использованию Ant. Здесь проблем возникнуть не должно. Хотя, конечно могут быть и кривые дистрибутивы и кривые руки и даже кривые ноги, но стандартной рекомендации в этом случае дать не получится - разбирайтесь с /dev/head.

Я хочу остановиться подробнее на установке Ant под Windows, так как именно там не все так просто для новичков.

Сложность в том, что разработчики Ant не предоставляют к системе Windows специального native-инсталлятора, который бы решил все проблемы.

Скачайте нужную вам версию Ant с cайта разработчика. Если ссылка не устареет, то попробуйте сделать это со страницы http://ant.apache.org/bindownload.cgi. Скачайте более привычный для пользователей DOS/Windows архив с компрессией zip. Скачайте и распакуйте его средствами, которые вам доступны в вашей системе. Для эксперимента с Ant под Windows я скачал архив apache-ant-1.8.2-bin.zip.

Если вы хотите получить информацию об установке Ant во всех подробностях из первоисточника, то надо опять обратиться к соответствующему разделу сайта производителя Ant. На момент написания этих строк можно было воспользоваться адресом http://ant.apache.org/manual/index.html. На указанной странице нужно выбрать Installing Apache Ant -> Installing Ant.

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


Во-первых.

Назовём корневой директорией Ant директорию, в которой вы найдете поддиректории bin, lib, docs, etc и прочие объекты файловой системы, относящиеся к системе Ant.

Создайте системную переменную ANT_HOME и установите ее в значение корневой директории Ant.

Добавим в системные пути поддиректорию bin из корневой директории Ant. Это нужно для того, чтобы запускать ant и другие утилиты системы без указания их полного пути из любого места файловой системы.

Во-вторых.


Для работы Ant требуется Java. Причем, если мы хотим собирать средствами Ant проекты Java (а, как правило, именно для этого мы и ставим Ant), то нам нужен именно пакет JDK, содержащий средства компиляции, а не JRE, где содержатся только средства исполнения байт-кода Java.

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

Теперь нам следует сообщить системе Ant о расположении установленного в системе JDK. Для этого, следует установить системную переменную JAVA_HOME в значение директории, где лежит установленное JDK.

Также, имеет смысл добавить в список системных путей поддиректорию bin корневой директории JDK. Это упростит вам самим использование предоставляемых утилит JDK. 

В-третьих.

Нужно проследить, чтобы значение системной переменной CLASSPATH было пустым, либо не было определено вообще. Это требование Ant может расстроить тех, кто организует свое рабочее место определяя значение этой системной переменной.

ВАЖНО!!! При установке значений ANT_HOME и JAVA_HOME следите за тем, чтобы ни одно из значений не заканчивалось на символ разделителя директорий - "\" (DOS\Windows) или "/" (POSIX).

Для тех кто не знает как устанавливать системные переменные и расширять список системных путей (системная переменная PATH) подскажу, что в Windows их можно установить из панели управления. Для Windows XP, в классическом размещении инструментов панели, установку системных переменных надо искать в последовательности выбора "Панель управления -> Система -> Дополнительно -> Переменные среды -> Создать|Изменить Системные переменные".

Если верить докуменации Ant, то часть этой работы должен выполнить скрипт ant.bat из поддиректории bin корневой директории Ant. Я не пробовал.

Итак, в моем Windows XP были установлены следующие значения указанных переменных.

JAVA_HOME=C:\Program Files\Java\jdk1.7.0_01
ANT_HOME=C:\apache-ant-1.8.2
PATH=<прежний список путей>;C:\Program Files\Java\jdk1.7.0_01\bin;C:\apache-ant-1.8.2\bin

Чтобы проверить значение установленных переменных, следует открыть консоль Windows (Пуск->Выполнить->cmd) и исполнить команду echo передав ей значение системной переменной. Напомню, что если переменная называется xyz, то обратиться к ее значению в DOS\Windows можно взяв ее в знаки процента, т.е. написав %xyz% (В оригинальном *nix чуть проще - $xyz). Итак, посмотрим:

> echo %JAVA_HOME%
C:\Program Files\Java\jdk1.7.0_01

> echo %ANT_HOME%
C:\apache-ant-1.8.2

> echo %PATH%
<длинный список путей>;C:\Program Files\Java\jdk1.7.0_01\bin;C:\apache-ant-1.8.2\bin

> echo %CLASSPATH%
<должно быть пусто>