All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.ao.SearchableEntityManager Maven / Gradle / Ivy

/*
 * Copyright 2007 Daniel Spiewak
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 * 
 *	    http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.ao;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.List;

import net.java.ao.types.TypeInfo;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.StopAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * 

Required to manage entities with enabled full-text searching. This is where * all the "meat" of the Lucene indexing support is actually implemented. It is * required to use this {@link EntityManager} implementation to use full-text * searching. Example:

* *
public interface Post extends Entity {
 *     // ...
 *
 *     @Searchable
 *     @SQLType(Types.BLOB)
 *     public String getBody();
 *
 *     @Searchable
 *     @SQLType(Types.BLOB)
 *     public void setBody(String body);
 * }
 *
 * // ...
 * SearchableEntityManager manager = new SearchableEntityManager(
 *         uri, username, password, FSDirectory.getDirectory("~/lucene_index"));
 * manager.search(Post.class, "my search string");       // returns results as Post[]
* *

This class does not support any Java full-text search libraries other than * Lucene. Also, the support for Lucene itself is comparatively limited. If your * requirements dictate more advanced functionality, you should consider * writing a custom implementation of this class to provide the enhancements * you need. More features are planned for this class in future...

* * @author Daniel Spiewak * @see net.java.ao.Searchable */ public class SearchableEntityManager extends EntityManager { private final Directory indexDir; private final Analyzer analyzer; public SearchableEntityManager(DatabaseProvider databaseProvider, EntityManagerConfiguration configuration, LuceneConfiguration luceneConfiguration) throws IOException { super(databaseProvider, configuration); this.indexDir = checkNotNull(checkNotNull(luceneConfiguration).getIndexDirectory()); this.analyzer = new StopAnalyzer(); init(); } @Override protected , K> T getAndInstantiate(Class type, K key) { T back = super.getAndInstantiate(type, key); back.addPropertyChangeListener(new IndexAppender(back)); return back; } /** * Runs a Lucene full-text search on the specified entity type with the given * query. The search will be run on every {@link Searchable} field within * the entity. No caching is performed in this method. Rather, AO relies * upon the underlying Lucene code to be performant. * * @param type The type of the entities to search for. * @param strQuery The query to pass to Lucene for the search. * @throws IOException If Lucene was unable to open the index. * @throws ParseException If Lucene was unable to parse the search string into a valid query. * @return The entity instances which correspond with the search results. */ @SuppressWarnings("unchecked") public , K> T[] search(Class type, String strQuery) throws IOException, ParseException, SQLException { String table = getTableNameConverter().getName(type); List indexFields = Common.getSearchableFields(this, type); String[] searchFields = new String[indexFields.size()]; String primaryKeyField = Common.getPrimaryKeyField(type, getFieldNameConverter()); TypeInfo dbType = Common.getPrimaryKeyType(getProvider().getTypeManager(), type); for (int i = 0; i < searchFields.length; i++) { searchFields[i] = table + '.' + indexFields.get(i); } IndexSearcher searcher = new IndexSearcher(indexDir); QueryParser parser = new MultiFieldQueryParser(searchFields, analyzer); org.apache.lucene.search.Query query = parser.parse(strQuery); Hits hits = searcher.search(query); K[] keys = (K[]) new Object[hits.length()]; for (int i = 0; i < hits.length(); i++) { keys[i] = (K) dbType.getLogicalType().parseDefault(hits.doc(i).get(table + "." + primaryKeyField)); } searcher.close(); return peer(type, keys); } @Override public void delete(RawEntity... entities) throws SQLException { super.delete(entities); IndexReader reader = null; try { reader = IndexReader.open(indexDir); for (RawEntity entity : entities) { removeFromIndexImpl(entity, reader); } } catch (IOException e) { throw (SQLException) new SQLException().initCause(e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { } } } } /** * Adds the entity instance to the index. No checking is performed to * ensure that the entity is not already part of the index. All of the * {@link Searchable} fields within the entity will be added to the * index as part of the document corresponding to the instance. * * @param entity The entity to add to the index. * @throws IOException If Lucene was unable to open the index. */ @SuppressWarnings("unchecked") public void addToIndex(RawEntity entity) throws IOException { String table = getTableNameConverter().getName(entity.getEntityType()); IndexWriter writer = null; try { writer = new IndexWriter(indexDir, analyzer, false); Document doc = new Document(); doc.add(new Field(getTableNameConverter().getName(entity.getEntityType()) + "." + Common.getPrimaryKeyField(entity.getEntityType(), getFieldNameConverter()), primaryKeyToString(entity), Field.Store.YES, Field.Index.UN_TOKENIZED)); boolean shouldAdd = false; for (Method m : entity.getEntityType().getMethods()) { Searchable indexAnno = Common.getAnnotationDelegate(getFieldNameConverter(), m).getAnnotation(Searchable.class); if (indexAnno != null) { shouldAdd = true; if (Common.isAccessor(m)) { String attribute = getFieldNameConverter().getName(m); Object value = m.invoke(entity); if (value != null) { doc.add(new Field(table + '.' + attribute, value.toString(), Field.Store.YES, Field.Index.TOKENIZED)); } } } } if (shouldAdd) { writer.addDocument(doc); } } catch (IllegalArgumentException e) { throw (IOException) new IOException().initCause(e); } catch (IllegalAccessException e) { throw (IOException) new IOException().initCause(e); } catch (InvocationTargetException e) { throw (IOException) new IOException().initCause(e); } finally { if (writer != null) { writer.close(); } } } /** * Removes the specified entity from the Lucene index. This performs a lookup * in the index based on the value of the entity primary key and removes the * appropriate {@link Document}. * * @param entity The entity to remove from the index. * @throws IOException If Lucene was unable to open the index. */ public void removeFromIndex(RawEntity entity) throws IOException { IndexReader reader = null; try { reader = IndexReader.open(indexDir); removeFromIndexImpl(entity, reader); } finally { if (reader != null) { reader.close(); } } } private void removeFromIndexImpl(RawEntity entity, IndexReader reader) throws IOException { reader.deleteDocuments(new Term(getTableNameConverter().getName(entity.getEntityType()) + "." + Common.getPrimaryKeyField(entity.getEntityType(), getFieldNameConverter()), primaryKeyToString(entity))); } /** *

Optimizes the Lucene index for searching. This call peers down to * IndexWriter#optimize(). For sizable indexes, this * call will take some time, so it is best not to perform the operation * in scenarios where it may block interface responsiveness (such as * in the middle of a page request, or within the EDT).

* *

This method is the only optimization call made against the Lucene * index. Meaning, SearchableEntityManager never * optimizes the index automatically, as this could potentially cause major * performance issues. Developers should be aware of this and the * negative impact lack-of optimization can have upon search performance.

* * @throws IOException If Lucene was unable to open the index. */ public void optimize() throws IOException { IndexWriter writer = null; try { writer = new IndexWriter(indexDir, analyzer, false); writer.optimize(); } finally { if (writer != null) { writer.close(); } } } public Directory getIndexDir() { return indexDir; } public Analyzer getAnalyzer() { return analyzer; } private void init() throws IOException { if (!IndexReader.indexExists(indexDir)) { new IndexWriter(indexDir, analyzer, true).close(); } } private class IndexAppender, K> implements PropertyChangeListener { private List indexFields; private Document doc; private IndexAppender(T entity) { indexFields = Common.getSearchableFields(SearchableEntityManager.this, entity.getEntityType()); doc = new Document(); doc.add(new Field(getTableNameConverter().getName(entity.getEntityType()) + "." + Common.getPrimaryKeyField(entity.getEntityType(), getFieldNameConverter()), primaryKeyToString(entity), Field.Store.YES, Field.Index.UN_TOKENIZED)); } public void propertyChange(final PropertyChangeEvent evt) { if (indexFields.contains(evt.getPropertyName())) { T entity = (T) evt.getSource(); doc.add(new Field(getTableNameConverter().getName(entity.getEntityType()) + '.' + evt.getPropertyName(), evt.getNewValue().toString(), Field.Store.YES, Field.Index.TOKENIZED)); IndexWriter writer = null; try { writer = new IndexWriter(getIndexDir(), getAnalyzer(), false); writer.updateDocument(new Term(getTableNameConverter().getName(entity.getEntityType()) + "." + Common.getPrimaryKeyField(entity.getEntityType(), getFieldNameConverter()), primaryKeyToString(entity)), doc); } catch (IOException e) { } finally { if (writer != null) { try { writer.close(); } catch (CorruptIndexException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } } } } @SuppressWarnings("unchecked") private final String primaryKeyToString(RawEntity entity) { TypeInfo pkType = Common.getPrimaryKeyType(getProvider().getTypeManager(), entity.getEntityType()); return pkType.getLogicalType().valueToString(Common.getPrimaryKeyValue(entity)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy