/** * Copyright (c) 2002-2010 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.index.lucene; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.lucene.document.Field; import org.apache.lucene.index.Term; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.apache.lucene.search.TermQuery; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.helpers.collection.CombiningIterator; import org.neo4j.helpers.collection.IteratorUtil; import org.neo4j.index.IndexHits; import org.neo4j.index.IndexService; import org.neo4j.index.ReadOnlyIndexException; import org.neo4j.index.impl.GenericIndexService; import org.neo4j.index.impl.IdToNodeIterator; import org.neo4j.index.impl.SimpleIndexHits; import org.neo4j.kernel.EmbeddedGraphDatabase; import org.neo4j.kernel.EmbeddedReadOnlyGraphDatabase; import org.neo4j.kernel.impl.cache.LruCache; /** * A version of {@link LuceneIndexService} which is read-only and will throw * {@link ReadOnlyIndexException} in * {@link IndexService#index(Node, String, Object)} and * {@link IndexService#removeIndex(Node, String, Object)}. See * {@link EmbeddedReadOnlyGraphDatabase}. */ public class LuceneReadOnlyIndexService extends GenericIndexService { protected static final String DOC_ID_KEY = "id"; protected static final String DOC_INDEX_KEY = "index"; private final LuceneReadOnlyDataSource xaDs; private int lazynessThreshold = 100; /** * @param graphDb the {@link GraphDatabaseService} to use. */ public LuceneReadOnlyIndexService( GraphDatabaseService graphDb ) { super( graphDb ); String luceneDirectory; if ( graphDb instanceof EmbeddedReadOnlyGraphDatabase ) { EmbeddedReadOnlyGraphDatabase embeddedGraphDb = ( (EmbeddedReadOnlyGraphDatabase) graphDb ); luceneDirectory = embeddedGraphDb.getStoreDir() + "/" + getDirName(); } else { EmbeddedGraphDatabase embeddedGraphDb = ( (EmbeddedGraphDatabase) graphDb ); luceneDirectory = embeddedGraphDb.getStoreDir() + "/" + getDirName(); } xaDs = new LuceneReadOnlyDataSource( luceneDirectory ); } protected String getDirName() { return "lucene"; } protected Field.Index getIndexStrategy() { return Field.Index.NOT_ANALYZED; } /** * Enables an LRU cache for a specific index (specified by {@code key}) so * that the {@code maxNumberOfCachedEntries} number of results found with * {@link #getNodes(String, Object)} are cached for faster consecutive * lookups. It's preferred to enable cache at construction time. * * @param key the index to enable cache for. * @param maxNumberOfCachedEntries the max size of the cache before old ones * are flushed from the cache. * @see LuceneIndexService#enableCache(String, int) */ public void enableCache( String key, int maxNumberOfCachedEntries ) { xaDs.enableCache( key, maxNumberOfCachedEntries ); } @Override protected void indexThisTx( Node node, String key, Object value ) { throw new ReadOnlyIndexException(); } /** * (Copied from {@link LuceneIndexService#setLazySearchResultThreshold(int)} * ) * * Sets the threshold for when a result is considered big enough to skip * cache and be returned as a fully lazy iterator so that * {@link #getNodes(String, Object)} will return very fast and all the * reading and fetching of nodes is done lazily before each step in the * iteration of the returned result. The default value is * {@link LuceneIndexService#DEFAULT_LAZY_SEARCH_RESULT_THRESHOLD}. * * @param numberOfHitsBeforeLazyLoading the threshold where results which * are bigger than that threshold becomes lazy. */ public void setLazySearchResultThreshold( int numberOfHitsBeforeLazyLoading ) { this.lazynessThreshold = numberOfHitsBeforeLazyLoading; xaDs.invalidateCache(); } /** * (Copied from {@link LuceneIndexService#getLazySearchResultThreshold()} * * Returns the threshold for when a result is considered big enough to skip * cache and be returned as a fully lazy iterator so that * {@link #getNodes(String, Object)} will return very fast and all the * reading and fetching of nodes is done lazily before each step in the * iteration of the returned result. The default value is * {@link LuceneIndexService#DEFAULT_LAZY_SEARCH_RESULT_THRESHOLD}. * * @return the threshold for when a result is considered big enough to be * returned as a lazy iteration. */ public int getLazySearchResultThreshold() { return this.lazynessThreshold; } public IndexHits<Node> getNodes( String key, Object value ) { return getNodes( key, value, null ); } /** * Just like {@link #getNodes(String, Object)}, but with sorted result. * * @param key the index to query. * @param value the value to query for. * @param sortingOrNull lucene sorting behaviour for the result. Ignored if * {@code null}. * @return nodes that has been indexed with key and value, optionally sorted * with {@code sortingOrNull}. */ public IndexHits<Node> getNodes( String key, Object value, Sort sortingOrNull ) { List<Long> nodeIds = new ArrayList<Long>(); IndexSearcher searcher = xaDs.getIndexSearcher( key ); Iterator<Long> nodeIdIterator = null; Integer nodeIdIteratorSize = null; if ( searcher != null ) { LruCache<String, Collection<Long>> cachedNodesMap = xaDs.getFromCache( key ); boolean foundInCache = false; String valueAsString = value.toString(); if ( cachedNodesMap != null ) { Collection<Long> cachedNodes = cachedNodesMap.get( valueAsString ); if ( cachedNodes != null ) { foundInCache = true; nodeIds.addAll( cachedNodes ); } } if ( !foundInCache ) { DocToIdIterator searchedNodeIds = searchForNodes( key, value, sortingOrNull ); if ( searchedNodeIds.size() >= this.lazynessThreshold ) { if ( cachedNodesMap != null ) { cachedNodesMap.remove( valueAsString ); } Collection<Iterator<Long>> iterators = new ArrayList<Iterator<Long>>(); iterators.add( nodeIds.iterator() ); iterators.add( searchedNodeIds ); nodeIdIterator = new CombiningIterator<Long>( iterators ); nodeIdIteratorSize = nodeIds.size() + searchedNodeIds.size(); } else { ArrayList<Long> readNodeIds = new ArrayList<Long>(); while ( searchedNodeIds.hasNext() ) { Long readNodeId = searchedNodeIds.next(); nodeIds.add( readNodeId ); readNodeIds.add( readNodeId ); } if ( cachedNodesMap != null ) { cachedNodesMap.put( valueAsString, readNodeIds ); } } } } if ( nodeIdIterator == null ) { nodeIdIterator = nodeIds.iterator(); nodeIdIteratorSize = nodeIds.size(); } return new SimpleIndexHits<Node>( IteratorUtil.asIterable( instantiateIdToNodeIterator( nodeIdIterator ) ), nodeIdIteratorSize ); } protected Iterator<Node> instantiateIdToNodeIterator( final Iterator<Long> ids ) { return new IdToNodeIterator( ids, getGraphDb() ); } protected Query formQuery( String key, Object value ) { return new TermQuery( new Term( DOC_INDEX_KEY, value.toString() ) ); } private DocToIdIterator searchForNodes( String key, Object value, Sort sortingOrNull ) { Query query = formQuery( key, value ); try { IndexSearcher searcher = xaDs.getIndexSearcher( key ); Hits hits = new Hits( searcher, query, null, sortingOrNull ); return new DocToIdIterator( new HitsIterator( hits ), Collections.<Long>emptyList(), null ); } catch ( IOException e ) { throw new RuntimeException( "Unable to search for " + key + "," + value, e ); } } public Node getSingleNode( String key, Object value ) { Iterator<Node> nodes = getNodes( key, value ).iterator(); Node node = nodes.hasNext() ? nodes.next() : null; if ( nodes.hasNext() ) { throw new RuntimeException( "More than one node for " + key + "=" + value ); } return node; } @Override protected void removeIndexThisTx( Node node, String key, Object value ) { throw new ReadOnlyIndexException(); } public void removeIndex( Node node, String key ) { throw new ReadOnlyIndexException(); } public void removeIndex( String key ) { throw new ReadOnlyIndexException(); } @Override public synchronized void shutdown() { super.shutdown(); xaDs.close(); } }