Все конечно с нетерпением ждут, когда в App Engine появится полнотекстовый поиск, но пока его нет даже в roadmap. Тем не менее, для GAE/Java полнотекстовый поиск можно прикрутить самостоятельно уже сейчас.
Вы удивитесь, но это Lucene. Нет-нет, не спешите разочарованно уходить, вздыхая «ну это ж сколько возиться надо». Подружить Lucene с App Engine datastore дело, наверное, не пяти минут, но если вы используете JDO, то за вас это уже сделали разработчики compass-project в разрабатываемом compass 2.3 (сейчас в стадии бета-версии).
Compass умеет превращать ваши JDO объекты в проиндексированные документы и возвращать их в качестве результатов поиска. Код простейшего JDO объекта с индексированием и поиском, и комментарии к интересным местам в нём смотрите ниже. Чтобы это скомпилировалось и заработало, нужно добавить в
[1] Аннотациями отмечаются JDO классы, подлежащие индексированию…
[2]… свойства, идентифицирующие документы,…
[3]… и наконец свойства, значения которых будут проиндексированы.
[4] При конструировании экземпляра
[5] Это тонкость, специфичная для App Engine. Compass вообще работает в несколько потоков, но на App Engine потоки создавать нельзя, поэтому нужно потоки запретить.
[6] Это тоже небольшой хак актуальный для App Engine. Compass вообще-то умеет актуализировать индекс автоматически, когда что-нибудь меняется в интересных ему персистентных данных. Но при работе на App Engine он может не уложиться в отведённые 30 секунд. Вызов
[7, 8] Поддержка App Engine ещё сыровата, и иногда Compass, кажется, забывает снимать блокировки индекса. В причинах и правильном способе исправления я пока не разбирался, но явная просьба отпустить блокировки помогает. Надо, конечно, понимать что это может быть не очень безопасно. Если кто найдёт время проблему отловить и победить, дайте знать.
[9] Собственно так объект индексируется вручную.
[10] Результатом поиска является коллекция
Вы удивитесь, но это Lucene. Нет-нет, не спешите разочарованно уходить, вздыхая «ну это ж сколько возиться надо». Подружить Lucene с App Engine datastore дело, наверное, не пяти минут, но если вы используете JDO, то за вас это уже сделали разработчики compass-project в разрабатываемом compass 2.3 (сейчас в стадии бета-версии).
Compass умеет превращать ваши JDO объекты в проиндексированные документы и возвращать их в качестве результатов поиска. Код простейшего JDO объекта с индексированием и поиском, и комментарии к интересным местам в нём смотрите ниже. Чтобы это скомпилировалось и заработало, нужно добавить в
war/WEB-INF/lib
несколько библиотек: lucene-core, commons-logging и compass-2.3.0-beta1
. Они все живут в дистрибутиве ночного билда Compass'а, который закопан глубоко в дебри их continuous build системы. На момент написания, последний успешный билд живёт тут: http://build.compass-project.org/download/CMPTRK-NIGHTLY/artifacts/build-786/Release. Признаюсь что я не проверял работоспособность конкретно этого билда.@PersistenceCapable(identityType = IdentityType.APPLICATION)
@Searchable // [1]
public class GreetingServiceUser {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
@SearchableId
private Long id; // [2]
@Persistent
private String name;
public GreetingServiceUser(String name) {
this.name = name;
}
@SearchableProperty // [3]
public String getName() {
return name;
}
private static final PersistenceManagerFactory factory =
JDOHelper.getPersistenceManagerFactory("transactions-optional");
private static Compass compass;
private static CompassGps compassGps;
static {
// [4]
compass = new CompassConfiguration()
.setConnection("gae://index")
// [5]
.setSetting(CompassEnvironment.ExecutorManager.EXECUTOR_MANAGER_TYPE,
"disabled")
.addScan("compass_test")
.buildCompass();
compassGps = new SingleCompassGps(compass);
Jdo2GpsDevice jdo2GpsDevice = new Jdo2GpsDevice("appengine", factory);
// [6]
jdo2GpsDevice.setMirrorDataChanges(false);
compassGps.addGpsDevice(jdo2GpsDevice);
// [7]
// if (compass.getSearchEngineIndexManager().isLocked()) {
// compass.getSearchEngineIndexManager().releaseLocks();
// }
compassGps.start();
}
public void save() {
PersistenceManager pm = factory.getPersistenceManager();
CompassIndexSession indexSession = null;
try {
pm.makePersistent(this);
// [8]
// compass.getSearchEngineIndexManager().releaseLocks();
// [9]
indexSession = compass.openIndexSession();
indexSession.save(this);
indexSession.commit();
}
catch (Throwable e) {
e.printStackTrace();
}
finally {
pm.close();
if (indexSession != null) {
indexSession.close();
}
}
}
public static List<GreetingServiceUser> search(String query) {
CompassSearchSession searchSession = compass.openSearchSession();
CompassHits hits = searchSession.find(query);
List<GreetingServiceUser> results =
new ArrayList<GreetingServiceUser>(hits.getLength());
PersistenceManager pm = factory.getPersistenceManager();
try {
for (int i=0; i<hits.length(); i++) {
// [10]
results.add(pm.getObjectById(
GreetingServiceUser.class,
Long.valueOf(hits.resource(i).getId())));
}
}
catch (JDOObjectNotFoundException e) {
e.printStackTrace();
}
finally {
pm.close();
}
return results;
}
}
* This source code was highlighted with Source Code Highlighter.
[1] Аннотациями отмечаются JDO классы, подлежащие индексированию…
[2]… свойства, идентифицирующие документы,…
[3]… и наконец свойства, значения которых будут проиндексированы.
[4] При конструировании экземпляра
Compass
вы указываете, какие пакеты надо просканировать в поисках таких аннотаций[5] Это тонкость, специфичная для App Engine. Compass вообще работает в несколько потоков, но на App Engine потоки создавать нельзя, поэтому нужно потоки запретить.
[6] Это тоже небольшой хак актуальный для App Engine. Compass вообще-то умеет актуализировать индекс автоматически, когда что-нибудь меняется в интересных ему персистентных данных. Но при работе на App Engine он может не уложиться в отведённые 30 секунд. Вызов
setMirrorDataChanges(false)
отключит автоматическое обновление и обновлять индекс нужно будет вручную. Это не больно, и в качестве компенсации это можно делать, например, в отдельной задаче в task queue.[7, 8] Поддержка App Engine ещё сыровата, и иногда Compass, кажется, забывает снимать блокировки индекса. В причинах и правильном способе исправления я пока не разбирался, но явная просьба отпустить блокировки помогает. Надо, конечно, понимать что это может быть не очень безопасно. Если кто найдёт время проблему отловить и победить, дайте знать.
[9] Собственно так объект индексируется вручную.
[10] Результатом поиска является коллекция
CompassHits
. Из неё теоретически можно извлечь найденные объекты (GreetingServiceUser
в нашем случае) методом data(int)
, но у меня они получались с незаполненными значениями. Поэтому я пользуюсь объектом Resource, который соответствует документу, извлекаю из него id документа и ищу соответствующий JDO объект средствами JDO/AppEngine.