/*
* Copyright 2014 Orient Technologies.
*
* 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.orientechnologies.lucene.operator;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.lucene.collections.OFullTextCompositeKey;
import com.orientechnologies.lucene.index.OLuceneFullTextIndex;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.ODatabase;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.index.OIndexCursor;
import com.orientechnologies.orient.core.index.OIndexCursorCollectionValue;
import com.orientechnologies.orient.core.index.OIndexCursorSingleValue;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.OIndexSearchResult;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField;
import com.orientechnologies.orient.core.sql.operator.OIndexReuseType;
import com.orientechnologies.orient.core.sql.operator.OQueryTargetOperator;
import com.orientechnologies.orient.core.sql.parser.ParseException;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.index.memory.MemoryIndex;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class OLuceneTextOperator extends OQueryTargetOperator {
public static final String MEMORY_INDEX = "_memoryIndex";
public OLuceneTextOperator() {
this("LUCENE", 5, false);
}
public OLuceneTextOperator(String iKeyword, int iPrecedence, boolean iLogical) {
super(iKeyword, iPrecedence, iLogical);
}
@Override
public OIndexReuseType getIndexReuseType(Object iLeft, Object iRight) {
return OIndexReuseType.INDEX_OPERATOR;
}
@Override
public OIndexSearchResult getOIndexSearchResult(OClass iSchemaClass, OSQLFilterCondition iCondition,
List<OIndexSearchResult> iIndexSearchResults, OCommandContext context) {
// FIXME questo non trova l'indice se l'ordine e' errato
OIndexSearchResult result = OLuceneOperatorUtil.buildOIndexSearchResult(iSchemaClass, iCondition,
iIndexSearchResults, context);
return result;
}
@Override
public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex<?> index, List<Object> keyParams, boolean ascSortOrder) {
Object indexResult = index.get(new OFullTextCompositeKey(keyParams).setContext(iContext));
if (indexResult == null || indexResult instanceof OIdentifiable)
return new OIndexCursorSingleValue((OIdentifiable) indexResult, new OFullTextCompositeKey(keyParams));
return new OIndexCursorCollectionValue(((Collection<OIdentifiable>) indexResult), new OFullTextCompositeKey(keyParams));
}
@Override
public ORID getBeginRidRange(Object iLeft, Object iRight) {
return null;
}
@Override
public ORID getEndRidRange(Object iLeft, Object iRight) {
return null;
}
@Override
public boolean canBeMerged() {
return false;
}
@Override
public Collection<OIdentifiable> filterRecords(ODatabase<?> iRecord, List<String> iTargetClasses, OSQLFilterCondition iCondition,
Object iLeft, Object iRight) {
return null;
}
@Override
public Object evaluateRecord(OIdentifiable iRecord, ODocument iCurrentResult, OSQLFilterCondition iCondition, Object iLeft,
Object iRight, OCommandContext iContext) {
OLuceneFullTextIndex index = involvedIndex(iRecord, iCurrentResult, iCondition, iLeft, iRight);
if (index == null) {
throw new OCommandExecutionException("Cannot evaluate lucene condition without index configuration.");
}
MemoryIndex memoryIndex = (MemoryIndex) iContext.getVariable(MEMORY_INDEX);
if (memoryIndex == null) {
memoryIndex = new MemoryIndex();
iContext.setVariable(MEMORY_INDEX, memoryIndex);
}
memoryIndex.reset();
try {
for (IndexableField field : index.buildDocument(iLeft).getFields()) {
memoryIndex.addField(field.name(), field.tokenStream(index.indexAnalyzer(), null));
}
return memoryIndex.search(index.buildQuery(iRight)) > 0.0f;
} catch (ParseException e) {
OLogManager.instance().error(this, "error occurred while building query", e);
} catch (IOException e) {
OLogManager.instance().error(this, "error occurred while building memory index", e);
}
return null;
}
protected OLuceneFullTextIndex involvedIndex(OIdentifiable iRecord, ODocument iCurrentResult, OSQLFilterCondition iCondition,
Object iLeft, Object iRight) {
ODocument doc = iRecord.getRecord();
OClass cls = getDatabase().getMetadata().getSchema().getClass(doc.getClassName());
if (isChained(iCondition.getLeft())) {
OSQLFilterItemField chained = (OSQLFilterItemField) iCondition.getLeft();
OSQLFilterItemField.FieldChain fieldChain = chained.getFieldChain();
OClass oClass = cls;
for (int i = 0; i < fieldChain.getItemCount() - 1; i++) {
oClass = oClass.getProperty(fieldChain.getItemName(i)).getLinkedClass();
}
if (oClass != null) {
cls = oClass;
}
}
Set<OIndex<?>> classInvolvedIndexes = cls.getInvolvedIndexes(fields(iCondition));
OLuceneFullTextIndex idx = null;
for (OIndex<?> classInvolvedIndex : classInvolvedIndexes) {
if (classInvolvedIndex.getInternal() instanceof OLuceneFullTextIndex) {
idx = (OLuceneFullTextIndex) classInvolvedIndex.getInternal();
break;
}
}
return idx;
}
protected static ODatabaseDocumentInternal getDatabase() {
return ODatabaseRecordThreadLocal.INSTANCE.get();
}
private boolean isChained(Object left) {
if (left instanceof OSQLFilterItemField) {
OSQLFilterItemField field = (OSQLFilterItemField) left;
return field.isFieldChain();
}
return false;
}
// restituisce una lista di nomi
protected Collection<String> fields(OSQLFilterCondition iCondition) {
Object left = iCondition.getLeft();
if (left instanceof String) {
String fName = (String) left;
return Arrays.asList(fName);
}
if (left instanceof Collection) {
Collection<OSQLFilterItemField> f = (Collection<OSQLFilterItemField>) left;
List<String> fields = new ArrayList<String>();
for (OSQLFilterItemField field : f) {
fields.add(field.toString());
}
return fields;
}
if (left instanceof OSQLFilterItemField) {
OSQLFilterItemField fName = (OSQLFilterItemField) left;
if (fName.isFieldChain()) {
int itemCount = fName.getFieldChain().getItemCount();
return Arrays.asList(fName.getFieldChain().getItemName(itemCount - 1));
} else {
return Arrays.asList(fName.toString());
}
}
return Collections.emptyList();
}
}