Package org.neo4j.index.impl.lucene

Source Code of org.neo4j.index.impl.lucene.LuceneIndex$RelationshipIndex

/**
* Copyright (c) 2002-2011 "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.impl.lucene;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MultiSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.TermQuery;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.index.lucene.QueryContext;
import org.neo4j.kernel.impl.cache.LruCache;
import org.neo4j.kernel.impl.core.ReadOnlyDbException;
import org.neo4j.kernel.impl.util.IoPrimitiveUtils;

public abstract class LuceneIndex<T extends PropertyContainer> implements Index<T>
{
    static final String KEY_DOC_ID = "_id_";
    static final String KEY_START_NODE_ID = "_start_node_id_";
    static final String KEY_END_NODE_ID = "_end_node_id_";
   
    final LuceneIndexImplementation service;
    private IndexIdentifier identifier;
    final IndexType type;
    private volatile boolean deleted;
   
    // Will contain ids which were found to be missing from the graph when doing queries
    // Write transactions can fetch from this list and add to their transactions to
    // allow for self-healing properties.
    final Collection<Long> abandonedIds = new CopyOnWriteArraySet<Long>();

    LuceneIndex( LuceneIndexImplementation service, IndexIdentifier identifier )
    {
        this.service = service;
        this.identifier = identifier;
        this.type = service.dataSource().getType( identifier );
    }
   
    LuceneXaConnection getConnection()
    {
        assertNotDeleted();
        if ( service.broker() == null )
        {
            throw new ReadOnlyDbException();
        }
        return service.broker().acquireResourceConnection();
    }
   
    private void assertNotDeleted()
    {
        if ( deleted )
        {
            throw new IllegalStateException( "This index (" + identifier + ") has been deleted" );
        }
    }

    LuceneXaConnection getReadOnlyConnection()
    {
        assertNotDeleted();
        return service.broker() == null ? null :
                service.broker().acquireReadOnlyResourceConnection();
    }
   
    void markAsDeleted()
    {
        this.deleted = true;
        this.abandonedIds.clear();
    }
   
    public String getName()
    {
        return this.identifier.indexName;
    }
   
    /**
     * See {@link Index#add(PropertyContainer, String, Object)} for more generic
     * documentation.
     *
     * Adds key/value to the {@code entity} in this index. Added values are
     * searchable withing the transaction, but composite {@code AND}
     * queries aren't guaranteed to return added values correctly within that
     * transaction. When the transaction has been committed all such queries
     * are guaranteed to return correct results.
     *
     * @param entity the entity (i.e {@link Node} or {@link Relationship})
     * to associate the key/value pair with.
     * @param key the key in the key/value pair to associate with the entity.
     * @param value the value in the key/value pair to associate with the
     * entity.
     */
    public void add( T entity, String key, Object value )
    {
        LuceneXaConnection connection = getConnection();
        for ( Object oneValue : IoPrimitiveUtils.asArray( value ) )
        {
            connection.add( this, entity, key, oneValue );
        }
    }

    /**
     * See {@link Index#remove(PropertyContainer, String, Object)} for more
     * generic documentation.
     *
     * Removes key/value to the {@code entity} in this index. Removed values
     * are excluded withing the transaction, but composite {@code AND}
     * queries aren't guaranteed to exclude removed values correctly within
     * that transaction. When the transaction has been committed all such
     * queries are guaranteed to return correct results.
     *
     * @param entity the entity (i.e {@link Node} or {@link Relationship})
     * to dissociate the key/value pair from.
     * @param key the key in the key/value pair to dissociate from the entity.
     * @param value the value in the key/value pair to dissociate from the
     * entity.
     */
    public void remove( T entity, String key, Object value )
    {
        LuceneXaConnection connection = getConnection();
        for ( Object oneValue : IoPrimitiveUtils.asArray( value ) )
        {
            connection.remove( this, entity, key, oneValue );
        }
    }
   
    public void remove( T entity, String key )
    {
        LuceneXaConnection connection = getConnection();
        connection.remove( this, entity, key );
    }
   
    public void remove( T entity )
    {
        LuceneXaConnection connection = getConnection();
        connection.remove( this, entity );
    }
   
    public void delete()
    {
        getConnection().deleteIndex( this );
    }
   
    public IndexHits<T> get( String key, Object value )
    {
        return query( type.get( key, value ), key, value, null );
    }

    /**
     * {@inheritDoc}
     *
     * {@code queryOrQueryObject} can be a {@link String} containing the query
     * in Lucene syntax format, http://lucene.apache.org/java/3_0_2/queryparsersyntax.html.
     * Or it can be a {@link Query} object. If can even be a {@link QueryContext}
     * object which can contain a query ({@link String} or {@link Query}) and
     * additional parameters, such as {@link Sort}.
     *
     * Because of performance issues, including uncommitted transaction modifications
     * in the result is disabled by default, but can be enabled using
     * {@link QueryContext#tradeCorrectnessForSpeed()}.
     */
    public IndexHits<T> query( String key, Object queryOrQueryObject )
    {
        QueryContext context = queryOrQueryObject instanceof QueryContext ?
                (QueryContext) queryOrQueryObject : null;
        return query( type.query( key, context != null ?
                context.getQueryOrQueryObject() : queryOrQueryObject, context ), null, null, context );
    }

    /**
     * {@inheritDoc}
     *
     * @see #query(String, Object)
     */
    public IndexHits<T> query( Object queryOrQueryObject )
    {
        return query( null, queryOrQueryObject );
    }
   
    protected IndexHits<T> query( Query query, String keyForDirectLookup,
            Object valueForDirectLookup, QueryContext additionalParametersOrNull )
    {
        List<Long> ids = new ArrayList<Long>();
        LuceneXaConnection con = getReadOnlyConnection();
        LuceneTransaction luceneTx = con != null ? con.getLuceneTx() : null;
        Collection<Long> removedIds = Collections.emptySet();
        Searcher additionsSearcher = null;
        if ( luceneTx != null )
        {
            if ( keyForDirectLookup != null )
            {
                ids.addAll( luceneTx.getAddedIds( this, keyForDirectLookup, valueForDirectLookup ) );
            }
            else
            {
                additionsSearcher = luceneTx.getAdditionsAsSearcher( this, additionalParametersOrNull );
            }
            removedIds = keyForDirectLookup != null ?
                    luceneTx.getRemovedIds( this, keyForDirectLookup, valueForDirectLookup ) :
                    luceneTx.getRemovedIds( this, query );
        }
        service.dataSource().getReadLock();
        IndexHits<Long> idIterator = null;
        IndexSearcherRef searcher = null;
        try
        {
            searcher = service.dataSource().getIndexSearcher( identifier, true );
            if ( searcher != null )
            {
                boolean foundInCache = false;
                LruCache<String, Collection<Long>> cachedIdsMap = null;
                if ( keyForDirectLookup != null )
                {
                    cachedIdsMap = service.dataSource().getFromCache(
                            identifier, keyForDirectLookup );
                    foundInCache = fillFromCache( cachedIdsMap, ids,
                            keyForDirectLookup, valueForDirectLookup.toString(), removedIds );
                }
               
                if ( !foundInCache )
                {
                    DocToIdIterator searchedIds = new DocToIdIterator( search( searcher,
                            query, additionalParametersOrNull, additionsSearcher, removedIds ), removedIds, searcher );
                    if ( ids.isEmpty() )
                    {
                        idIterator = searchedIds;
                    }
                    else
                    {
                        Collection<IndexHits<Long>> iterators = new ArrayList<IndexHits<Long>>();
                        iterators.add( searchedIds );
                        iterators.add( new ConstantScoreIterator<Long>( ids, Float.NaN ) );
                        idIterator = new CombinedIndexHits<Long>( iterators );
                    }
                }
            }
        }
        finally
        {
            // The DocToIdIterator closes the IndexSearchRef instance anyways,
            // or the LazyIterator if it's a lazy one. So no need here.
            service.dataSource().releaseReadLock();
        }

        idIterator = idIterator == null ? new ConstantScoreIterator<Long>( ids, 0 ) : idIterator;
        return new IdToEntityIterator<T>( idIterator )
        {
            @Override
            protected T underlyingObjectToObject( Long id )
            {
                return getById( id );
            }
           
            protected void itemDodged( Long item )
            {
                abandonedIds.add( item );
            }
        };
    }
   
    private boolean fillFromCache(
            LruCache<String, Collection<Long>> cachedNodesMap,
            List<Long> ids, String key, String valueAsString,
            Collection<Long> deletedNodes )
    {
        boolean found = false;
        if ( cachedNodesMap != null )
        {
            Collection<Long> cachedNodes = cachedNodesMap.get( valueAsString );
            if ( cachedNodes != null )
            {
                found = true;
                for ( Long cachedNodeId : cachedNodes )
                {
                    if ( deletedNodes == null ||
                            !deletedNodes.contains( cachedNodeId ) )
                    {
                        ids.add( cachedNodeId );
                    }
                }
            }
        }
        return found;
    }
   
    private IndexHits<Document> search( IndexSearcherRef searcherRef, Query query,
            QueryContext additionalParametersOrNull, Searcher additionsSearcher, Collection<Long> removed )
    {
        try
        {
            if ( additionsSearcher != null && !removed.isEmpty() )
            {
                letThroughAdditions( additionsSearcher, query, removed );
            }
           
            Searcher searcher = additionsSearcher == null ? searcherRef.getSearcher() :
                    new MultiSearcher( searcherRef.getSearcher(), additionsSearcher );
            IndexHits<Document> result = null;
            if ( additionalParametersOrNull != null && additionalParametersOrNull.getTop() > 0 )
            {
                result = new TopDocsIterator( query, additionalParametersOrNull, searcher );
            }
            else
            {
                Sort sorting = additionalParametersOrNull != null ?
                        additionalParametersOrNull.getSorting() : null;
                boolean forceScore = additionalParametersOrNull == null ||
                        !additionalParametersOrNull.getTradeCorrectnessForSpeed();
                Hits hits = new Hits( searcher, query, null, sorting, forceScore );
                result = new HitsIterator( hits );
            }
            return result;
        }
        catch ( IOException e )
        {
            throw new RuntimeException( "Unable to query " + this + " with "
                                        + query, e );
        }
    }
   
    private void letThroughAdditions( Searcher additionsSearcher, Query query, Collection<Long> removed )
            throws IOException
    {
        Hits hits = new Hits( additionsSearcher, query, null );
        HitsIterator iterator = new HitsIterator( hits );
        while ( iterator.hasNext() )
        {
            String idString = iterator.next().getField( KEY_DOC_ID ).stringValue();
            removed.remove( Long.parseLong( idString ) );
        }
    }

    public void setCacheCapacity( String key, int capacity )
    {
        service.dataSource().setCacheCapacity( identifier, key, capacity );
    }
   
    public Integer getCacheCapacity( String key )
    {
        return service.dataSource().getCacheCapacity( identifier, key );
    }
   
    protected abstract T getById( long id );
   
    protected abstract long getEntityId( T entity );
   
    protected abstract LuceneCommand newAddCommand( PropertyContainer entity,
            String key, Object value );
   
    protected abstract LuceneCommand newRemoveCommand( PropertyContainer entity,
            String key, Object value );
   
    IndexIdentifier getIdentifier()
    {
        return this.identifier;
    }

    static class NodeIndex extends LuceneIndex<Node>
    {
        NodeIndex( LuceneIndexImplementation service,
                IndexIdentifier identifier )
        {
            super( service, identifier );
        }

        @Override
        protected Node getById( long id )
        {
            return service.graphDb().getNodeById( id );
        }
       
        @Override
        protected long getEntityId( Node entity )
        {
            return entity.getId();
        }

        @Override
        protected LuceneCommand newAddCommand( PropertyContainer entity, String key, Object value )
        {
            return new LuceneCommand.AddCommand( getIdentifier(), LuceneCommand.NODE,
                    ((Node) entity).getId(), key, value );
        }

        @Override
        protected LuceneCommand newRemoveCommand( PropertyContainer entity, String key, Object value )
        {
            return new LuceneCommand.RemoveCommand( getIdentifier(), LuceneCommand.NODE,
                    ((Node) entity).getId(), key, value );
        }
       
        public Class<Node> getEntityType()
        {
            return Node.class;
        }
    }
   
    static class RelationshipIndex extends LuceneIndex<Relationship>
            implements org.neo4j.graphdb.index.RelationshipIndex
    {
        RelationshipIndex( LuceneIndexImplementation service,
                IndexIdentifier identifier )
        {
            super( service, identifier );
        }

        @Override
        protected Relationship getById( long id )
        {
            return service.graphDb().getRelationshipById( id );
        }
       
        @Override
        protected long getEntityId( Relationship entity )
        {
            return entity.getId();
        }
       
        public void add( Relationship relationship )
        {
           
        }

        public IndexHits<Relationship> get( String key, Object valueOrNull, Node startNodeOrNull,
                Node endNodeOrNull )
        {
            BooleanQuery query = new BooleanQuery();
            if ( key != null && valueOrNull != null )
            {
                query.add( type.get( key, valueOrNull ), Occur.MUST );
            }
            addIfNotNull( query, startNodeOrNull, KEY_START_NODE_ID );
            addIfNotNull( query, endNodeOrNull, KEY_END_NODE_ID );
            return query( query, (String) null, null, null );
        }

        public IndexHits<Relationship> query( String key, Object queryOrQueryObjectOrNull,
                Node startNodeOrNull, Node endNodeOrNull )
        {
            QueryContext context = queryOrQueryObjectOrNull != null &&
                    queryOrQueryObjectOrNull instanceof QueryContext ?
                            (QueryContext) queryOrQueryObjectOrNull : null;
                   
            BooleanQuery query = new BooleanQuery();
            if ( (context != null && context.getQueryOrQueryObject() != null) ||
                    (context == null && queryOrQueryObjectOrNull != null ) )
            {
                query.add( type.query( key, context != null ?
                        context.getQueryOrQueryObject() : queryOrQueryObjectOrNull, context ), Occur.MUST );
            }
            addIfNotNull( query, startNodeOrNull, KEY_START_NODE_ID );
            addIfNotNull( query, endNodeOrNull, KEY_END_NODE_ID );
            return query( query, (String) null, null, context );
        }
       
        private static void addIfNotNull( BooleanQuery query, Node nodeOrNull, String field )
        {
            if ( nodeOrNull != null )
            {
                query.add( new TermQuery( new Term( field, "" + nodeOrNull.getId() ) ),
                        Occur.MUST );
            }
        }

        public IndexHits<Relationship> query( Object queryOrQueryObjectOrNull,
                Node startNodeOrNull, Node endNodeOrNull )
        {
            return query( null, queryOrQueryObjectOrNull, startNodeOrNull, endNodeOrNull );
        }

        @Override
        protected LuceneCommand newAddCommand( PropertyContainer entity, String key, Object value )
        {
            Relationship rel = (Relationship) entity;
            return new LuceneCommand.AddRelationshipCommand( getIdentifier(), LuceneCommand.RELATIONSHIP,
                    RelationshipId.of( rel ), key, value );
        }

        @Override
        protected LuceneCommand newRemoveCommand( PropertyContainer entity, String key, Object value )
        {
            Relationship rel = (Relationship) entity;
            return new LuceneCommand.RemoveCommand( getIdentifier(), LuceneCommand.RELATIONSHIP,
                    RelationshipId.of( rel ), key, value );
        }
       
        public Class<Relationship> getEntityType()
        {
            return Relationship.class;
        }
    }
}
TOP

Related Classes of org.neo4j.index.impl.lucene.LuceneIndex$RelationshipIndex

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.