/** * License Agreement for OpenSearchServer * <p/> * Copyright (C) 2008-2015 Emmanuel Keller / Jaeksoft * <p/> * http://www.open-search-server.com * <p/> * This file is part of OpenSearchServer. * <p/> * OpenSearchServer is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * <p/> * OpenSearchServer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * <p/> * You should have received a copy of the GNU General Public License * along with OpenSearchServer. * If not, see <http://www.gnu.org/licenses/>. **/ package com.jaeksoft.searchlib.index; import com.jaeksoft.searchlib.Client; import com.jaeksoft.searchlib.ClientCatalog; import com.jaeksoft.searchlib.SearchLibException; import com.jaeksoft.searchlib.analysis.PerFieldAnalyzer; import com.jaeksoft.searchlib.filter.FilterAbstract; import com.jaeksoft.searchlib.filter.FilterHits; import com.jaeksoft.searchlib.function.expression.SyntaxError; import com.jaeksoft.searchlib.query.ParseException; import com.jaeksoft.searchlib.request.AbstractLocalSearchRequest; import com.jaeksoft.searchlib.request.AbstractRequest; import com.jaeksoft.searchlib.request.DocumentsRequest; import com.jaeksoft.searchlib.result.AbstractResult; import com.jaeksoft.searchlib.result.ResultDocuments; import com.jaeksoft.searchlib.schema.FieldValue; import com.jaeksoft.searchlib.schema.Schema; import com.jaeksoft.searchlib.schema.SchemaField; import com.jaeksoft.searchlib.util.IOUtils; import com.jaeksoft.searchlib.util.Timer; import com.jaeksoft.searchlib.util.XmlWriter; import com.jaeksoft.searchlib.webservice.query.document.IndexDocumentResult; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.lucene.index.*; import org.apache.lucene.search.Query; import org.apache.lucene.search.similar.MoreLikeThis; import org.json.JSONException; import org.xml.sax.SAXException; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.*; public class IndexSingle extends IndexAbstract { final private IndexDirectory indexDirectory; private volatile ReaderLocal _reader; private final WriterLocal writer; private volatile boolean online; private final Set<UpdateInterfaces.Before> beforeUpdateSet = new HashSet<>(); private final Set<UpdateInterfaces.After> afterUpdateSet = new HashSet<>(); private final Set<UpdateInterfaces.Delete> afterDeleteSet = new HashSet<>(); private final Set<UpdateInterfaces.Reload> afterReloadSet = new HashSet<>(); private volatile UpdateInterfaces.Before[] _beforeUpdateArray = null; private volatile UpdateInterfaces.After[] _afterUpdateArray = null; private volatile UpdateInterfaces.Delete[] _afterDeleteArray = null; private volatile UpdateInterfaces.Reload[] _afterReloadArray = null; private final List<IndexAbstract> reloadIndexList; private final UpdateInterfaces.Reload reloadUpdateInterface = new UpdateInterfaces.Reload() { @Override public void reload() throws SearchLibException { IndexSingle.this.reload(); } }; public IndexSingle(File configDir, IndexConfig indexConfig, boolean createIfNotExists) throws IOException, URISyntaxException, SearchLibException, JSONException { super(indexConfig); this.online = true; boolean bCreate = false; File indexDir = new File(configDir, "index"); if (!indexDir.exists()) { if (!createIfNotExists) { indexDirectory = null; _reader = null; writer = null; reloadIndexList = null; return; } indexDir.mkdir(); bCreate = true; } else indexDir = findIndexDirOrSub(indexDir); URI remoteURI = indexConfig.getRemoteURI(); indexDirectory = remoteURI == null ? new IndexDirectory(indexDir) : new IndexDirectory(remoteURI); bCreate = bCreate || indexDirectory.isEmpty(); if (!indexConfig.isMulti()) { writer = new WriterLocal(indexConfig, indexDirectory); if (bCreate) ((WriterLocal) writer).create(); reloadIndexList = null; } else { writer = null; reloadIndexList = new ArrayList<>(); } _reader = new ReaderLocal(indexConfig, indexDirectory); eventUpdateInterface(); } private void emptyReloadEvents() { for (IndexAbstract index : reloadIndexList) index.removeUpdateInterface(reloadUpdateInterface); reloadIndexList.clear(); } private void subscribeReloadEvents() throws SearchLibException { for (String indexName : indexConfig.getIndexList()) { Client client = ClientCatalog.getClient(indexName); if (client == null) continue; IndexAbstract index = client.getIndex(); reloadIndexList.add(index); index.addUpdateInterface(reloadUpdateInterface); } } private void eventUpdateInterface() throws SearchLibException { if (reloadIndexList == null) return; synchronized (reloadIndexList) { emptyReloadEvents(); subscribeReloadEvents(); } } private void generateUpdateInterfaceArrays() { synchronized (beforeUpdateSet) { _beforeUpdateArray = beforeUpdateSet.isEmpty() ? null : beforeUpdateSet.toArray(new UpdateInterfaces.Before[beforeUpdateSet.size()]); } synchronized (afterUpdateSet) { _afterUpdateArray = afterUpdateSet.isEmpty() ? null : afterUpdateSet.toArray(new UpdateInterfaces.After[afterUpdateSet.size()]); } synchronized (afterDeleteSet) { _afterDeleteArray = afterDeleteSet.isEmpty() ? null : afterDeleteSet.toArray(new UpdateInterfaces.Delete[afterDeleteSet.size()]); } synchronized (afterReloadSet) { _afterReloadArray = afterReloadSet.isEmpty() ? null : afterReloadSet.toArray(new UpdateInterfaces.Reload[afterReloadSet.size()]); } } @Override public void addUpdateInterface(UpdateInterfaces updateInterface) { if (updateInterface == null) return; if (updateInterface instanceof UpdateInterfaces.Before) { synchronized (beforeUpdateSet) { beforeUpdateSet.add((UpdateInterfaces.Before) updateInterface); } } if (updateInterface instanceof UpdateInterfaces.After) { synchronized (afterUpdateSet) { afterUpdateSet.add((UpdateInterfaces.After) updateInterface); } } if (updateInterface instanceof UpdateInterfaces.Delete) { synchronized (afterDeleteSet) { afterDeleteSet.add((UpdateInterfaces.Delete) updateInterface); } } if (updateInterface instanceof UpdateInterfaces.Reload) { synchronized (afterReloadSet) { afterReloadSet.add((UpdateInterfaces.Reload) updateInterface); } } generateUpdateInterfaceArrays(); } @Override public void removeUpdateInterface(UpdateInterfaces updateInterface) { if (updateInterface == null) return; if (updateInterface instanceof UpdateInterfaces.Before) { synchronized (beforeUpdateSet) { beforeUpdateSet.remove((UpdateInterfaces.Before) updateInterface); } } if (updateInterface instanceof UpdateInterfaces.After) { synchronized (afterUpdateSet) { afterUpdateSet.remove((UpdateInterfaces.After) updateInterface); } } if (updateInterface instanceof UpdateInterfaces.Delete) { synchronized (afterDeleteSet) { afterDeleteSet.remove((UpdateInterfaces.Delete) updateInterface); } } if (updateInterface instanceof UpdateInterfaces.Reload) { synchronized (afterReloadSet) { afterReloadSet.remove((UpdateInterfaces.Reload) updateInterface); } } generateUpdateInterfaceArrays(); } /** * Check if there is old style index sub directory * * @param indexDir * @return */ private File findIndexDirOrSub(File indexDir) { File[] dirs = indexDir.listFiles((FileFilter) DirectoryFileFilter.INSTANCE); if (dirs == null) return indexDir; if (dirs.length == 0) return indexDir; return dirs[dirs.length - 1]; } @Override public void close() { if (reloadIndexList != null) { synchronized (reloadIndexList) { emptyReloadEvents(); } } if (_reader != null) IOUtils.close(_reader); _reader = null; indexDirectory.close(); } private void checkOnline(boolean online) throws SearchLibException { if (this.online != online) throw new SearchLibException("Index is offline"); } @Override public void deleteAll() throws SearchLibException { checkOnline(true); if (writer == null) return; writer.deleteAll(); reloadNoLock(); } private int[] getIds(AbstractRequest request) throws IOException, ParseException, SyntaxError, SearchLibException { ReaderLocal reader = acquire(); try { if (request instanceof AbstractLocalSearchRequest) { DocSetHits dsh = reader.searchDocSet((AbstractLocalSearchRequest) request, null); if (dsh != null) return dsh.getIds(); } else if (request instanceof DocumentsRequest) { ResultDocuments result = (ResultDocuments) reader.request(request); if (result != null) return result.getDocIdArray(); } return null; } finally { release(reader); } } @Override public int deleteDocuments(AbstractRequest request) throws SearchLibException { try { checkOnline(true); if (writer == null) return 0; int[] ids = getIds(request); if (ids == null || ids.length == 0) return 0; int res = writer.deleteDocuments(ids); reloadNoLock(); return res; } catch (IOException | ParseException | SyntaxError e) { throw new SearchLibException(e); } } private void beforeUpdate(Schema schema, IndexDocument document) throws SearchLibException { UpdateInterfaces.Before[] array = _beforeUpdateArray; if (array == null) return; for (UpdateInterfaces.Before beforeUpdate : array) beforeUpdate.update(schema, document); } private void afterUpdate(IndexDocument document) throws SearchLibException { UpdateInterfaces.After[] array = _afterUpdateArray; if (array == null) return; for (UpdateInterfaces.After afterUpdate : array) afterUpdate.update(document); } private void afterReload() throws SearchLibException { UpdateInterfaces.Reload[] array = _afterReloadArray; if (array == null) return; for (UpdateInterfaces.Reload afterReload : array) afterReload.reload(); } @Override public boolean updateDocument(Schema schema, IndexDocument document) throws SearchLibException { checkOnline(true); if (writer == null) return false; beforeUpdate(schema, document); if (!writer.updateDocument(schema, document)) return false; reloadNoLock(); afterUpdate(document); return true; } @Override public int updateDocuments(Schema schema, Collection<IndexDocument> documents) throws SearchLibException { checkOnline(true); if (writer == null) return 0; for (IndexDocument document : documents) beforeUpdate(schema, document); int res = writer.updateDocuments(schema, documents); reloadNoLock(); for (IndexDocument document : documents) afterUpdate(document); return res; } @Override public int updateIndexDocuments(Schema schema, Collection<IndexDocumentResult> documents) throws SearchLibException { checkOnline(true); if (writer == null) return 0; int res = writer.updateIndexDocuments(schema, documents); reloadNoLock(); return res; } private synchronized void reloadNoLock() throws SearchLibException { ReaderLocal oldReader = _reader; try { _reader = new ReaderLocal(indexConfig, indexDirectory); } catch (IOException e) { throw new SearchLibException(e); } if (oldReader != null) IOUtils.closeQuietly(oldReader); afterReload(); } @Override public void reload() throws SearchLibException { checkOnline(true); eventUpdateInterface(); ReaderLocal reader = acquire(); try { reloadNoLock(); } finally { release(reader); } } private synchronized ReaderLocal acquire() { ReaderLocal reader = _reader; reader.acquire(); return reader; } private synchronized void release(ReaderLocal reader) { reader.release(); } @Override public AbstractResult<?> request(AbstractRequest request) throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.request(request); } finally { release(reader); } } @Override public String explain(AbstractRequest request, int docId, boolean bHtml) throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.explain(request, docId, bHtml); } finally { release(reader); } } @Override public boolean sameIndex(ReaderInterface reader) { if (reader == this) return true; if (reader == this._reader) return true; return false; } @Override public IndexStatistics getStatistics() throws IOException, SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getStatistics(); } finally { release(reader); } } @Override public IndexSingle get(String name) { return this; } @Override final public int getDocFreq(final Term term) throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getDocFreq(term); } finally { release(reader); } } @Override final public TermEnum getTermEnum() throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getTermEnum(); } finally { release(reader); } } @Override final public TermEnum getTermEnum(final Term term) throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getTermEnum(term); } finally { release(reader); } } @Override final public TermDocs getTermDocs(final Term term) throws SearchLibException, IOException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getTermDocs(term); } finally { release(reader); } } @Override final public TermPositions getTermPositions() throws IOException, SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getTermPositions(); } finally { release(reader); } } @Override final public TermFreqVector getTermFreqVector(final int docId, final String field) throws IOException, SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getTermFreqVector(docId, field); } finally { release(reader); } } @Override final public void putTermVectors(final int[] docIds, final String field, final Collection<String[]> termVectors) throws IOException, SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { reader.putTermVectors(docIds, field, termVectors); } finally { release(reader); } } @Override final public FieldCacheIndex getStringIndex(final String fieldName) throws IOException, SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getStringIndex(fieldName); } finally { release(reader); } } @Override public FilterHits getFilterHits(SchemaField defaultField, PerFieldAnalyzer analyzer, AbstractLocalSearchRequest request, FilterAbstract<?> filter, Timer timer) throws ParseException, IOException, SearchLibException, SyntaxError { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getFilterHits(defaultField, analyzer, request, filter, timer); } finally { release(reader); } } @Override public DocSetHits searchDocSet(AbstractLocalSearchRequest searchRequest, Timer timer) throws IOException, ParseException, SyntaxError, SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.searchDocSet(searchRequest, timer); } finally { release(reader); } } @Override final public boolean isOnline() { return online; } @Override public synchronized void setOnline(boolean v) throws SearchLibException { if (v == online) return; online = v; if (v) reloadNoLock(); else { IOUtils.close(_reader); _reader = null; } } @Override public long getVersion() throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getVersion(); } finally { release(reader); } } public DocSetHitsCache getSearchCache() throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getDocSetHitsCache(); } finally { release(reader); } } @Override public String[] getDocTerms(String field) throws SearchLibException, IOException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getDocTerms(field); } finally { release(reader); } } @Override protected void writeXmlConfigIndex(XmlWriter xmlWriter) throws SAXException { indexConfig.writeXmlConfig(xmlWriter); } @Override public Collection<?> getFieldNames() throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getFieldNames(); } finally { release(reader); } } @Override final public LinkedHashMap<String, FieldValue> getDocumentFields(final int docId, final LinkedHashSet<String> fieldNameSet, final Timer timer) throws IOException, ParseException, SyntaxError, SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getDocumentFields(docId, fieldNameSet, timer); } finally { release(reader); } } @Override public LinkedHashMap<String, FieldValue> getDocumentStoredField(final int docId) throws IOException, SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getDocumentStoredField(docId); } finally { release(reader); } } @Override public Query rewrite(Query query) throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.rewrite(query); } finally { release(reader); } } @Override public MoreLikeThis getMoreLikeThis() throws SearchLibException { checkOnline(true); ReaderLocal reader = acquire(); try { return reader.getMoreLikeThis(); } finally { release(reader); } } @Override public void mergeData(WriterInterface source) throws SearchLibException { if (!(source instanceof IndexSingle)) throw new SearchLibException("Unsupported operation"); if (writer == null) return; final IndexSingle sourceIndex = (IndexSingle) source; ReaderLocal reader = sourceIndex.acquire(); try { writer.mergeData(sourceIndex.writer); reloadNoLock(); } finally { release(reader); } } @Override public boolean isMerging() { return writer != null && writer.isMerging(); } }