/* * 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.manager; import com.orientechnologies.common.concur.resource.OSharedResourceAdaptiveExternal; import com.orientechnologies.common.log.OLogManager; import com.orientechnologies.lucene.OLuceneIndexType; import com.orientechnologies.lucene.OLuceneMapEntryIterator; import com.orientechnologies.lucene.query.QueryContext; import com.orientechnologies.lucene.utils.OLuceneIndexUtils; import com.orientechnologies.orient.core.OOrientListener; import com.orientechnologies.orient.core.Orient; import com.orientechnologies.orient.core.command.OCommandContext; import com.orientechnologies.orient.core.config.OGlobalConfiguration; 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.id.OContextualRecordId; import com.orientechnologies.orient.core.id.ORID; import com.orientechnologies.orient.core.index.*; import com.orientechnologies.orient.core.metadata.schema.OClass; import com.orientechnologies.orient.core.metadata.schema.OProperty; import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.serialization.serializer.stream.OStreamSerializer; import com.orientechnologies.orient.core.storage.OStorage; import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; import com.orientechnologies.orient.core.storage.impl.local.paginated.OLocalPaginatedStorage; 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.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.TrackingIndexWriter; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.search.*; import org.apache.lucene.store.Directory; import org.apache.lucene.store.NIOFSDirectory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.Version; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.*; public abstract class OLuceneIndexManagerAbstract<V> extends OSharedResourceAdaptiveExternal implements OIndexEngine<V>, OOrientListener { public static final String RID = "RID"; public static final String KEY = "KEY"; public static final String STORED = "_STORED"; public static final Version LUCENE_VERSION = Version.LUCENE_47; public static final String OLUCENE_BASE_DIR = "luceneIndexes"; protected SearcherManager searcherManager; protected OIndexDefinition index; protected TrackingIndexWriter mgrWriter; protected String indexName; protected String clusterIndexName; protected OStreamSerializer serializer; protected boolean automatic; protected ControlledRealTimeReopenThread nrt; protected ODocument metadata; protected Version version; private OIndex managedIndex; private boolean rebuilding; private long reopenToken; protected Map<String, Boolean> collectionFields = new HashMap<String, Boolean>(); public OLuceneIndexManagerAbstract() { super(OGlobalConfiguration.ENVIRONMENT_CONCURRENT.getValueAsBoolean(), OGlobalConfiguration.MVRBTREE_TIMEOUT .getValueAsInteger(), true); Orient.instance().registerListener(this); } @Override public void create(OIndexDefinition indexDefinition, String clusterIndexName, OStreamSerializer valueSerializer, boolean isAutomatic) { } public void createIndex(OIndexDefinition indexDefinition, String clusterIndexName, OStreamSerializer valueSerializer, boolean isAutomatic, ODocument metadata) { // PREVENT EXCEPTION initIndex(indexName, indexDefinition, clusterIndexName, valueSerializer, isAutomatic, metadata); } public abstract IndexWriter openIndexWriter(Directory directory, ODocument metadata) throws IOException; public abstract IndexWriter createIndexWriter(Directory directory, ODocument metadata) throws IOException; public void addDocument(Document doc) { try { reopenToken = mgrWriter.addDocument(doc); } catch (IOException e) { OLogManager.instance().error(this, "Error on adding new document '%s' to Lucene index", e, doc); } } public void deleteDocument(Query query) { try { reopenToken = mgrWriter.deleteDocuments(query); if (!mgrWriter.getIndexWriter().hasDeletions()) { OLogManager.instance().error(this, "Error on deleting document by query '%s' to Lucene index", new OIndexException("Error deleting document"), query); } } catch (IOException e) { OLogManager.instance().error(this, "Error on deleting document by query '%s' to Lucene index", e, query); } } public boolean remove(Object key, OIdentifiable value) { Query query = null; if (isCollectionDelete()) { query = OLuceneIndexType.createDeleteQuery(value, index.getFields(), key); } else { query = OLuceneIndexType.createQueryId(value); } if (query != null) deleteDocument(query); return true; } public void commit() { try { mgrWriter.getIndexWriter().commit(); } catch (IOException e) { OLogManager.instance().error(this, "Error on committing Lucene index", e); } } @Override public void deleteWithoutLoad(String indexName) { internalDelete(indexName); } protected void internalDelete(String indexName) { try { if (mgrWriter != null && mgrWriter.getIndexWriter() != null) { closeIndex(); } ODatabaseDocumentInternal database = getDatabase(); final OAbstractPaginatedStorage storageLocalAbstract = (OAbstractPaginatedStorage) database.getStorage().getUnderlying(); if (storageLocalAbstract instanceof OLocalPaginatedStorage) { OLocalPaginatedStorage localAbstract = (OLocalPaginatedStorage) storageLocalAbstract; File f = new File(getIndexPath(localAbstract, indexName)); OLuceneIndexUtils.deleteFolder(f); f = new File(getIndexBasePath(localAbstract)); OLuceneIndexUtils.deleteFolderIfEmpty(f); } } catch (IOException e) { OLogManager.instance().error(this, "Error on deleting Lucene index", e); } } public void delete() { if (mgrWriter == null) // INDEX NOT COMPLETELY INITIALIZED, SKIP IT return; try { if (mgrWriter.getIndexWriter() != null) { closeIndex(); } ODatabaseDocumentInternal database = getDatabase(); final OAbstractPaginatedStorage storageLocalAbstract = (OAbstractPaginatedStorage) database.getStorage().getUnderlying(); if (storageLocalAbstract instanceof OLocalPaginatedStorage) { File f = new File(getIndexPath((OLocalPaginatedStorage) storageLocalAbstract)); OLuceneIndexUtils.deleteFolder(f); f = new File(getIndexBasePath((OLocalPaginatedStorage) storageLocalAbstract)); OLuceneIndexUtils.deleteFolderIfEmpty(f); } } catch (IOException e) { OLogManager.instance().error(this, "Error on deleting Lucene index", e); } } public Iterator<Map.Entry<Object, V>> iterator() { try { IndexReader reader = getSearcher().getIndexReader(); return new OLuceneMapEntryIterator<Object, V>(reader, index); } catch (IOException e) { OLogManager.instance().error(this, "Error on creating iterator against Lucene index", e); } return new HashSet<Map.Entry<Object, V>>().iterator(); } public void clear() { try { mgrWriter.getIndexWriter().deleteAll(); } catch (IOException e) { OLogManager.instance().error(this, "Error on clearing Lucene index", e); } } @Override public void flush() { try { mgrWriter.getIndexWriter().commit(); } catch (IOException e) { OLogManager.instance().error(this, "Error on flushing Lucene index", e); } catch (Throwable e) { e.printStackTrace(); } } @Override public void close() { try { closeIndex(); } catch (Throwable e) { OLogManager.instance().error(this, "Error on closing Lucene index", e); } } public void rollback() { try { mgrWriter.getIndexWriter().rollback(); reOpen(metadata); } catch (IOException e) { OLogManager.instance().error(this, "Error on rolling back Lucene index", e); } } @Override public void load(final ORID indexRid, final String indexName, final OIndexDefinition indexDefinition, final OStreamSerializer valueSerializer, final boolean isAutomatic) { initIndex(indexName, indexDefinition, null, valueSerializer, isAutomatic, metadata); } public void load(final ORID indexRid, final String indexName, final OIndexDefinition indexDefinition, final boolean isAutomatic, final ODocument metadata) { initIndex(indexName, indexDefinition, null, null, isAutomatic, metadata); } public long size(final ValuesTransformer<V> transformer) { IndexReader reader = null; IndexSearcher searcher = null; try { reader = getSearcher().getIndexReader(); } catch (IOException e) { OLogManager.instance().error(this, "Error on getting size of Lucene index", e); } finally { if (searcher != null) { release(searcher); } } return reader.numDocs(); } public void release(IndexSearcher searcher) { try { searcherManager.release(searcher); } catch (IOException e) { OLogManager.instance().error(this, "Error on releasing index searcher of Lucene index", e); } } public void setManagedIndex(OIndex index) { this.managedIndex = index; } public boolean isRebuilding() { return rebuilding; } public void setRebuilding(boolean rebuilding) { this.rebuilding = rebuilding; } public Analyzer getAnalyzer(final ODocument metadata) { Analyzer analyzer = null; if (metadata != null && metadata.field("analyzer") != null) { final String analyzerString = metadata.field("analyzer"); if (analyzerString != null) { try { final Class classAnalyzer = Class.forName(analyzerString); final Constructor constructor = classAnalyzer.getConstructor(Version.class); analyzer = (Analyzer) constructor.newInstance(getLuceneVersion(metadata)); } catch (ClassNotFoundException e) { throw new OIndexException("Analyzer: " + analyzerString + " not found", e); } catch (NoSuchMethodException e) { Class classAnalyzer = null; try { classAnalyzer = Class.forName(analyzerString); analyzer = (Analyzer) classAnalyzer.newInstance(); } catch (Throwable e1) { throw new OIndexException("Couldn't instantiate analyzer: public constructor not found", e1); } } catch (Exception e) { OLogManager.instance().error(this, "Error on getting analyzer for Lucene index", e); } } } else { analyzer = new StandardAnalyzer(getLuceneVersion(metadata)); } return analyzer; } public Version getLuceneVersion(ODocument metadata) { if (version == null) { version = LUCENE_VERSION; } return version; } protected void initIndex(String indexName, OIndexDefinition indexDefinition, String clusterIndexName, OStreamSerializer valueSerializer, boolean isAutomatic, ODocument metadata) { this.index = indexDefinition; this.indexName = indexName; this.serializer = valueSerializer; this.automatic = isAutomatic; this.clusterIndexName = clusterIndexName; this.metadata = metadata; try { this.index = indexDefinition; checkCollectionIndex(indexDefinition); reOpen(metadata); } catch (IOException e) { OLogManager.instance().error(this, "Error on initializing Lucene index", e); } } private void checkCollectionIndex(OIndexDefinition indexDefinition) { List<String> fields = indexDefinition.getFields(); OClass aClass = getDatabase().getMetadata().getSchema().getClass(indexDefinition.getClassName()); for (String field : fields) { OProperty property = aClass.getProperty(field); if (property.getType().isEmbedded() && property.getLinkedType() != null) { collectionFields.put(field, true); } else { collectionFields.put(field, false); } } } protected Field.Store isToStore(String f) { return collectionFields.get(f) ? Field.Store.YES : Field.Store.NO; } protected boolean isCollectionDelete() { boolean collectionDelete = false; for (Boolean aBoolean : collectionFields.values()) { collectionDelete = collectionDelete || aBoolean; } return collectionDelete; } public IndexSearcher getSearcher() throws IOException { try { nrt.waitForGeneration(reopenToken); } catch (InterruptedException e) { OLogManager.instance().error(this, "Error on get searcher from Lucene index", e); } return searcherManager.acquire(); } protected void closeIndex() throws IOException { OLogManager.instance().debug(this, "Closing Lucene index '" + this.indexName + "'..."); if (nrt != null) { nrt.interrupt(); nrt.close(); } if (searcherManager != null) searcherManager.close(); if (mgrWriter != null) { mgrWriter.getIndexWriter().commit(); mgrWriter.getIndexWriter().close(); } } private void reOpen(final ODocument metadata) throws IOException { ODatabaseDocumentInternal database = getDatabase(); final OAbstractPaginatedStorage storageLocalAbstract = (OAbstractPaginatedStorage) database.getStorage().getUnderlying(); Directory dir = null; if (storageLocalAbstract instanceof OLocalPaginatedStorage) { String pathname = getIndexPath((OLocalPaginatedStorage) storageLocalAbstract); OLogManager.instance().debug(this, "Opening NIOFS Lucene db=%s, path=%s", database.getName(), pathname); dir = NIOFSDirectory.open(new File(pathname)); } else { OLogManager.instance().debug(this, "Opening RAM Lucene index db=%s", database.getName()); dir = new RAMDirectory(); } final IndexWriter indexWriter = createIndexWriter(dir, metadata); mgrWriter = new TrackingIndexWriter(indexWriter); searcherManager = new SearcherManager(indexWriter, true, null); if (nrt != null) { nrt.close(); } nrt = new ControlledRealTimeReopenThread(mgrWriter, searcherManager, 60.00, 0.1); nrt.setDaemon(true); nrt.start(); flush(); } public void sendTotalHits(OCommandContext context, TopDocs docs) { if (context != null) { if (context.getVariable("totalHits") == null) { context.setVariable("totalHits", docs.totalHits); } else { context.setVariable("totalHits", null); } context.setVariable((indexName + ".totalHits").replace(".", "_"), docs.totalHits); } } public void sendLookupTime(OCommandContext context, final TopDocs docs, final Integer limit, long startFetching) { if (context != null) { final long finalTime = System.currentTimeMillis() - startFetching; context.setVariable((indexName + ".lookupTime").replace(".", "_"), new HashMap<String, Object>() { { put("limit", limit); put("totalTime", finalTime); put("totalHits", docs.totalHits); put("returnedHits", docs.scoreDocs.length); put("maxScore", docs.getMaxScore()); } }); } } private String getIndexPath(OLocalPaginatedStorage storageLocalAbstract, String indexName) { return storageLocalAbstract.getStoragePath() + File.separator + OLUCENE_BASE_DIR + File.separator + indexName; } private String getIndexPath(OLocalPaginatedStorage storageLocalAbstract) { return getIndexPath(storageLocalAbstract, indexName); } protected String getIndexBasePath(OLocalPaginatedStorage storageLocalAbstract) { return storageLocalAbstract.getStoragePath() + File.separator + OLUCENE_BASE_DIR; } protected ODatabaseDocumentInternal getDatabase() { return ODatabaseRecordThreadLocal.INSTANCE.get(); } @Override public OIndexCursor descCursor(ValuesTransformer<V> vValuesTransformer) { return null; } public abstract void onRecordAddedToResultSet(QueryContext queryContext, OContextualRecordId recordId, Document ret, ScoreDoc score); @Override public int getVersion() { return 0; } public void setIndexName(String indexName) { this.indexName = indexName; } @Override public void onShutdown() { close(); } @Override public void onStorageRegistered(OStorage storage) { } @Override public void onStorageUnregistered(OStorage storage) { } public abstract Document buildDocument(Object key, OIdentifiable value); public abstract Query buildQuery(Object query) throws ParseException; public abstract Analyzer analyzer(String field); }