/*
* Copyright 2010 Impetus Infotech.
*
* 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 com.impetus.kundera.index;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lucandra.IndexReader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.util.Version;
import com.impetus.kundera.CassandraClient;
import com.impetus.kundera.Client;
import com.impetus.kundera.Constants;
import com.impetus.kundera.db.accessor.ColumnFamilyDataAccessor;
import com.impetus.kundera.loader.DBType;
import com.impetus.kundera.metadata.EntityMetadata;
import com.impetus.kundera.metadata.EntityMetadata.PropertyIndex;
import com.impetus.kundera.property.PropertyAccessException;
import com.impetus.kundera.property.PropertyAccessorHelper;
/**
* The Class LucandraIndexer.
*
* @author animesh.kumar
*/
public class LucandraIndexer implements Indexer {
/** log for this class. */
private static Log log = LogFactory.getLog(ColumnFamilyDataAccessor.class);
/** The INDEX_NAME. */
private static String INDEX_NAME = "kundera-alpha";// is
// persistent-unit-name
/** The Constant UUID. */
private static final long UUID = 6077004083174677888L;
/** The Constant DELIMETER. */
private static final String DELIMETER = "~";
/** The Constant ENTITY_ID_FIELD. */
public static final String ENTITY_ID_FIELD = UUID + ".entity.id";
/** The Constant KUNDERA_ID_FIELD. */
public static final String KUNDERA_ID_FIELD = UUID + ".kundera.id";
/** The Constant ENTITY_INDEXNAME_FIELD. */
public static final String ENTITY_INDEXNAME_FIELD = UUID
+ ".entity.indexname";
/** The Constant ENTITY_CLASS_FIELD. */
public static final String ENTITY_CLASS_FIELD = /*UUID +*/ "entity.class";
/** The Constant DEFAULT_SEARCHABLE_FIELD. */
private static final String DEFAULT_SEARCHABLE_FIELD = UUID
+ ".default_property";
/** The client. */
private Client client;
/** The analyzer. */
private Analyzer analyzer;
/**
* Instantiates a new lucandra indexer.
*
* @param client
* the client
* @param analyzer
* the analyzer
*/
public LucandraIndexer(Client client, Analyzer analyzer) {
this.client = client;
this.analyzer = analyzer;
}
/*
* @see
* com.impetus.kundera.index.Indexer#unindex(com.impetus.kundera.metadata
* .EntityMetadata, java.lang.String)
*/
@Override
public final void unindex(EntityMetadata metadata, String id) {
log.debug("Unindexing @Entity[" + metadata.getEntityClazz().getName()
+ "] for key:" + id);
try {
getIndexWriter().deleteDocuments(
new Term(KUNDERA_ID_FIELD, getKunderaId(metadata, id)));
} catch (CorruptIndexException e) {
throw new IndexingException(e.getMessage());
} catch (IOException e) {
throw new IndexingException(e.getMessage());
}
}
/*
* @see
* com.impetus.kundera.index.Indexer#index(com.impetus.kundera.metadata.
* EntityMetadata, java.lang.Object)
*/
@Override
public final void index(EntityMetadata metadata, Object object) {
if (!metadata.isIndexable()) {
return;
}
log.debug("Indexing @Entity[" + metadata.getEntityClazz().getName()
+ "] " + object);
String indexName = metadata.getIndexName();
Document document = new Document();
Field luceneField;
// index row
try {
String id = PropertyAccessorHelper.getId(object, metadata);
luceneField = new Field(ENTITY_ID_FIELD, id, // adding class
// namespace
Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
document.add(luceneField);
// index namespace for unique deletion
luceneField = new Field(KUNDERA_ID_FIELD,
getKunderaId(metadata, id), // adding
// class
// namespace
Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
document.add(luceneField);
// index entity class
luceneField = new Field(ENTITY_CLASS_FIELD, metadata
.getEntityClazz().getCanonicalName().toLowerCase(), Field.Store.YES,
Field.Index.NOT_ANALYZED_NO_NORMS);
document.add(luceneField);
// index index name
luceneField = new Field(ENTITY_INDEXNAME_FIELD, metadata
.getIndexName(), Field.Store.YES,
Field.Index.NOT_ANALYZED_NO_NORMS);
document.add(luceneField);
} catch (PropertyAccessException e) {
throw new IllegalArgumentException("Id could not be read.");
}
// now index all indexable properties
for (PropertyIndex index : metadata.getIndexProperties()) {
java.lang.reflect.Field property = index.getProperty();
String propertyName = index.getName();
try {
String value = PropertyAccessorHelper.getString(object,
property).toString();
luceneField = new Field(getCannonicalPropertyName(indexName,
propertyName), value, Field.Store.NO,
Field.Index.ANALYZED);
document.add(luceneField);
} catch (PropertyAccessException e) {
// TODO: do something with the exceptions
// e.printStackTrace();
}
}
// flush the indexes
try {
log.debug("Flushing to Lucandra: " + document);
if(!metadata.getDBType().equals(DBType.CASSANDRA)) {
IndexWriter w = getDefaultIndexWriter();
w.addDocument(document, analyzer);
w.optimize();
w.commit();
w.close();
} else {
getIndexWriter().addDocument(document, analyzer);
}
} catch (CorruptIndexException e) {
throw new IndexingException(e.getMessage());
} catch (IOException e) {
throw new IndexingException(e.getMessage());
}
}
// TODO: this is not the best implementation. need to improve!
/* @see com.impetus.kundera.index.Indexer#search(java.lang.String, int, int) */
@SuppressWarnings("deprecation")
@Override
public final List<String> search(String luceneQuery, int start, int count) {
if (Constants.INVALID == count) {
count = 100;
}
log.debug("Searhcing index with query[" + luceneQuery + "], start:"
+ start + ", count:" + count);
Set<String> entityIds = new HashSet<String>();
org.apache.lucene.index.IndexReader indexReader = null;
try {
if(client.getType().equals(DBType.CASSANDRA)) {
indexReader = new IndexReader(INDEX_NAME, ((CassandraClient)client).getCassandraClient());
}else {
indexReader = getDefaultReader();
}
} catch (Exception e) {
throw new IndexingException(e.getMessage());
}
IndexSearcher searcher = new IndexSearcher(indexReader);
QueryParser qp = new QueryParser(Version.LUCENE_CURRENT,
DEFAULT_SEARCHABLE_FIELD, analyzer);
try {
Query q = qp.parse(luceneQuery);
TopDocs docs = searcher.search(q, count);
for (ScoreDoc sc : docs.scoreDocs) {
Document doc = searcher.doc(sc.doc);
entityIds.add(doc.get(ENTITY_ID_FIELD));
}
} catch (ParseException e) {
new IndexingException(e.getMessage());
} catch (IOException e) {
new IndexingException(e.getMessage());
}
log.debug("Result[" + entityIds + "]");
return new ArrayList<String>(entityIds);
}
/**
* Gets the kundera id.
*
* @param metadata
* the metadata
* @param id
* the id
*
* @return the kundera id
*/
private String getKunderaId(EntityMetadata metadata, String id) {
return metadata.getEntityClazz().getCanonicalName() + DELIMETER + id;
}
/**
* Gets the cannonical property name.
*
* @param indexName
* the index name
* @param propertyName
* the property name
*
* @return the cannonical property name
*/
private String getCannonicalPropertyName(String indexName,
String propertyName) {
return indexName + "." + propertyName;
}
// helper method to get Lucandra IndexWriter object
/**
* Gets the index writer.
*
* @return the index writer
*/
private lucandra.IndexWriter getIndexWriter() {
try {
return new lucandra.IndexWriter(INDEX_NAME, ((CassandraClient)client).getCassandraClient());
} catch (Exception e) {
throw new IndexingException(e.getMessage());
}
}
/**
* Added for HBase support.
* @return default index writer
*/
private IndexWriter getDefaultIndexWriter() {
StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);
Directory index = null;
IndexWriter w=null;
try {
index = FSDirectory.open(getIndexDirectory());
if(index.listAll().length == 0) {
log.info("Creating fresh Index because it was empty");
w = new IndexWriter(index, analyzer, true, IndexWriter.MaxFieldLength.LIMITED);
} else {
w = new IndexWriter(index, analyzer, false, IndexWriter.MaxFieldLength.LIMITED);
}
} catch (CorruptIndexException e) {
throw new IndexingException(e.getMessage());
} catch (LockObtainFailedException e) {
throw new IndexingException(e.getMessage());
} catch (IOException e) {
throw new IndexingException(e.getMessage());
}
return w;
}
/**
* Returns default index reader.
* @return index reader.
*/
private org.apache.lucene.index.IndexReader getDefaultReader() {
org.apache.lucene.index.IndexReader reader = null;
try {
reader = IndexReader.open(FSDirectory.open(getIndexDirectory()));
} catch (CorruptIndexException e) {
throw new IndexingException(e.getMessage());
} catch (IOException e) {
throw new IndexingException(e.getMessage());
}
return reader;
}
/**
* Creates a directory if it does not exist.
* @return
*/
private File getIndexDirectory() {
String filePath = System.getProperty("user.home")+"/lucene";
File file = new File(filePath);
if(!file.isDirectory()){
file.mkdir();
}
return file;
}
}