Package org.exoplatform.services.jcr.impl.core.query.lucene

Source Code of org.exoplatform.services.jcr.impl.core.query.lucene.QueryResultImpl

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.exoplatform.services.jcr.impl.core.query.lucene;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.ValueFactory;
import javax.jcr.query.QueryResult;
import javax.jcr.query.RowIterator;

import org.exoplatform.services.log.Log;
import org.apache.lucene.search.Query;

import org.exoplatform.services.jcr.access.AccessManager;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.impl.core.LocationFactory;
import org.exoplatform.services.jcr.impl.core.NodeImpl;
import org.exoplatform.services.jcr.impl.core.SessionDataManager;
import org.exoplatform.services.log.ExoLogger;

/**
* Implements the <code>javax.jcr.query.QueryResult</code> interface.
*/
public class QueryResultImpl
   implements QueryResult
{

   /**
    * The logger instance for this class
    */
   private static final Log log = ExoLogger.getLogger(QueryResultImpl.class);

   /**
    * The search index to execute the query.
    */
   private final SearchIndex index;

   /**
    * The item manager of the session executing the query
    */
   private final SessionDataManager itemMgr;

   /**
    * The name and path resolver of the session executing the query
    */
   protected final LocationFactory resolver;

   /**
    * The access manager of the session that executes the query.
    */
   private final AccessManager accessMgr;

   /**
    * The query instance which created this query result.
    */
   protected final AbstractQueryImpl queryImpl;

   /**
    * The lucene query to execute.
    */
   protected final Query query;

   /**
    * The spell suggestion or <code>null</code> if not available.
    */
   protected final SpellSuggestion spellSuggestion;

   /**
    * The select properties
    */
   protected final InternalQName[] selectProps;

   /**
    * The names of properties to use for ordering the result set.
    */
   protected final InternalQName[] orderProps;

   /**
    * The order specifier for each of the order properties.
    */
   protected final boolean[] orderSpecs;

   /**
    * The result nodes including their score. This list is populated on a lazy
    * basis while a client iterates through the results.
    */
   private final List<ScoreNode> resultNodes = new ArrayList<ScoreNode>();

   /**
    * This is the raw number of results that matched the query. This number also
    * includes matches which will not be returned due to access restrictions.
    * This value is set when the query is executed the first time.
    */
   private int numResults = -1;

   /**
    * The number of results that are invalid, either because a node does not
    * exist anymore or because the session does not have access to the node.
    */
   private int invalid = 0;

   /**
    * If <code>true</code> nodes are returned in document order.
    */
   private final boolean docOrder;

   /**
    * The excerpt provider or <code>null</code> if none was created yet.
    */
   private ExcerptProvider excerptProvider;

   /**
    * The offset in the total result set
    */
   private final long offset;

   /**
    * The maximum size of this result if limit > 0
    */
   private final long limit;

   private final String userId;

   private final ValueFactory vFactory;

   /**
    * Creates a new query result.
    *
    * @param index the search index where the query is executed.
    * @param itemMgr the item manager of the session executing the query.
    * @param resolver the namespace resolver of the session executing the query.
    * @param accessMgr the access manager of the session executiong the query.
    * @param queryImpl the query instance which created this query result.
    * @param query the lucene query to execute on the index.
    * @param spellSuggestion the spell suggestion or <code>null</code> if none is
    *          available.
    * @param selectProps the select properties of the query.
    * @param orderProps the names of the order properties.
    * @param orderSpecs the order specs, one for each order property name.
    * @param documentOrder if <code>true</code> the result is returned in
    *          document order.
    * @param limit the maximum result size
    * @param offset the offset in the total result set
    */
   public QueryResultImpl(SearchIndex index, SessionDataManager itemMgr, LocationFactory resolver,
            ValueFactory vFactory, AccessManager accessMgr, String userId, AbstractQueryImpl queryImpl, Query query,
            SpellSuggestion spellSuggestion, InternalQName[] selectProps, InternalQName[] orderProps,
            boolean[] orderSpecs, boolean documentOrder, long offset, long limit) throws RepositoryException
   {

      this.index = index;
      this.itemMgr = itemMgr;
      this.userId = userId;
      this.resolver = resolver;
      this.vFactory = vFactory;
      this.accessMgr = accessMgr;
      this.queryImpl = queryImpl;
      this.query = query;
      this.spellSuggestion = spellSuggestion;
      this.selectProps = selectProps;
      this.orderProps = orderProps;
      this.orderSpecs = orderSpecs;
      this.docOrder = orderProps.length == 0 && documentOrder;
      this.offset = offset;
      this.limit = limit;
      // if document order is requested get all results right away
      getResults(docOrder ? Integer.MAX_VALUE : index.getQueryHandlerConfig().getResultFetchSize());
   }

   /**
    * {@inheritDoc}
    */
   public String[] getColumnNames() throws RepositoryException
   {
      try
      {
         String[] propNames = new String[selectProps.length];
         for (int i = 0; i < selectProps.length; i++)
         {
            propNames[i] = resolver.createJCRName(selectProps[i]).getAsString();
         }
         return propNames;
      }
      catch (NamespaceException npde)
      {
         String msg = "encountered invalid property name";
         log.debug(msg);
         throw new RepositoryException(msg, npde);
      }
   }

   /**
    * {@inheritDoc}
    */
   public NodeIterator getNodes() throws RepositoryException
   {
      return getNodeIterator();
   }

   /**
    * {@inheritDoc}
    */
   public RowIterator getRows() throws RepositoryException
   {
      if (excerptProvider == null)
      {
         try
         {
            excerptProvider = index.createExcerptProvider(query);
         }
         catch (IOException e)
         {
            throw new RepositoryException(e);
         }
      }
      return new RowIteratorImpl(getNodeIterator(), selectProps, resolver, excerptProvider, spellSuggestion, vFactory);
   }

   /**
    * Executes the query for this result and returns hits. The caller must close
    * the query hits when he is done using it.
    *
    * @return hits for this query result.
    * @throws IOException if an error occurs while executing the query.
    */
   protected QueryHits executeQuery() throws IOException
   {
      return index.executeQuery(query, queryImpl.needsSystemTree(), orderProps, orderSpecs);
   }

   // --------------------------------< internal >------------------------------

   /**
    * Creates a node iterator over the result nodes.
    *
    * @return a node iterator over the result nodes.
    */
   private ScoreNodeIterator getNodeIterator()
   {
      if (docOrder)
      {
         return new DocOrderNodeIteratorImpl(itemMgr, accessMgr, userId, resultNodes);
      }
      return new LazyScoreNodeIterator();

   }

   /**
    * Attempts to get <code>size</code> results and puts them into
    * {@link #resultNodes}. If the size of {@link #resultNodes} is less than
    * <code>size</code> then there are no more than
    * <code>resultNodes.size()</code> results for this query.
    *
    * @param size the number of results to fetch for the query.
    * @throws RepositoryException if an error occurs while executing the query.
    */
   private void getResults(long size) throws RepositoryException
   {
      if (log.isDebugEnabled())
      {
         log.debug("getResults(" + size + ")");
      }

      long maxResultSize = size;

      // is there any limit?
      if (limit > 0)
      {
         maxResultSize = limit;
      }

      if (resultNodes.size() >= maxResultSize)
      {
         // we already have them all
         return;
      }

      // execute it
      QueryHits result = null;
      try
      {
         result = executeQuery();

         // set num results with the first query execution
         if (numResults == -1)
         {
            numResults = result.length();
         }

         int start = resultNodes.size() + invalid + (int) offset;
         int max = Math.min(result.length(), numResults);
         for (int i = start; i < max && resultNodes.size() < maxResultSize; i++)
         {
            String id = result.getFieldContent(i, FieldNames.UUID);
            // check access
            resultNodes.add(new ScoreNode(id, result.score(i)));
         }

      }
      catch (IOException e)
      {
         log.error("Exception while executing query: ", e);
      }
      finally
      {
         if (result != null)
         {
            try
            {
               result.close();
            }
            catch (IOException e)
            {
               log.warn("Unable to close query result: " + e);
            }
         }
      }
   }

   /**
    * Returns the total number of hits. This is the number of results you will
    * get get if you don't set any limit or offset. Keep in mind that this number
    * may get smaller if nodes are found in the result set which the current
    * session has no permission to access.
    */
   public int getTotalSize()
   {
      return numResults - invalid;
   }

   private final class LazyScoreNodeIterator
      implements TwoWayRangeIterator, ScoreNodeIterator
   {

      private int position = -1;

      private boolean initialized = false;

      private NodeImpl next;

      /**
       * {@inheritDoc}
       */
      public float getScore()
      {
         initialize();
         if (!hasNext())
         {
            throw new NoSuchElementException();
         }
         return resultNodes.get(position).getScore();
      }

      /**
       * {@inheritDoc}
       */
      public NodeImpl nextNodeImpl()
      {
         initialize();
         if (next == null)
         {
            throw new NoSuchElementException();
         }
         NodeImpl n = next;
         fetchNext();
         return n;
      }

      /**
       * {@inheritDoc}
       */
      public Node nextNode()
      {
         return nextNodeImpl();
      }

      /**
       * {@inheritDoc}
       */
      public void skip(long skipNum)
      {
         initialize();
         if (skipNum < 0)
         {
            throw new IllegalArgumentException("skipNum must not be negative");
         }
         if ((position + invalid + skipNum) > numResults)
         {
            throw new NoSuchElementException();
         }
         if (skipNum == 0)
         {
            // do nothing
         }
         else
         {
            // attempt to get enough results
            try
            {
               getResults(position + invalid + (int) skipNum);
               if (resultNodes.size() >= position + skipNum)
               {
                  // skip within already fetched results
                  position += skipNum - 1;
                  fetchNext();
               }
               else
               {
                  // not enough results after getResults()
                  throw new NoSuchElementException();
               }
            }
            catch (RepositoryException e)
            {
               throw new NoSuchElementException(e.getMessage());
            }
         }
      }

      public void skipBack(long skipNum)
      {
         initialize();
         if (skipNum < 0)
         {
            throw new IllegalArgumentException("skipNum must not be negative");
         }
         if ((position - skipNum) < 0)
         {
            throw new NoSuchElementException();
         }
         if (skipNum == 0)
         {
            // do nothing
         }
         else
         {
            position -= skipNum + 1;
            fetchNext();
         }
      }

      /**
       * {@inheritDoc} <p/> This value may shrink when the query result encounters
       * non-existing nodes or the session does not have access to a node.
       */
      public long getSize()
      {
         long size = getTotalSize() - offset;
         if (limit > 0 && size > limit)
         {
            return limit;
         }
         else
         {
            return size;
         }
      }

      /**
       * {@inheritDoc}
       */
      public long getPosition()
      {
         initialize();
         return position;
      }

      /**
       * @throws UnsupportedOperationException always.
       */
      public void remove()
      {
         throw new UnsupportedOperationException("remove");
      }

      /**
       * {@inheritDoc}
       */
      public boolean hasNext()
      {
         initialize();
         return next != null;
      }

      /**
       * {@inheritDoc}
       */
      public Object next()
      {
         return nextNodeImpl();
      }

      /**
       * Initializes this iterator but only if it is not yet initialized.
       */
      private void initialize()
      {
         if (!initialized)
         {
            fetchNext();
            initialized = true;
         }
      }

      /**
       * Fetches the next node to return by this iterator. If this method returns
       * and {@link #next} is <code>null</code> then there is no next node.
       */
      private void fetchNext()
      {
         next = null;
         int nextPos = position + 1;
         while (next == null && (nextPos + invalid) < numResults)
         {
            if (nextPos >= resultNodes.size())
            {
               // fetch more results
               try
               {
                  int num;
                  if (resultNodes.size() == 0)
                  {
                     num = index.getQueryHandlerConfig().getResultFetchSize();
                  }
                  else
                  {
                     num = resultNodes.size() * 2;
                  }
                  getResults(num);
               }
               catch (RepositoryException e)
               {
                  log.warn("Exception getting more results: " + e);
               }
               // check again
               if (nextPos >= resultNodes.size())
               {
                  // no more valid results
                  return;
               }
            }
            ScoreNode sn = resultNodes.get(nextPos);
            try
            {
               next = (NodeImpl) itemMgr.getItemByIdentifier(sn.getNodeId(), true);
               if (next == null)
               {
                  resultNodes.remove(nextPos);
                  invalid++;
               }
            }
            catch (RepositoryException e)
            {
               log.warn("Exception retrieving Node with UUID: " + sn.getNodeId() + ": " + e.toString());
               // remove score node and try next
               resultNodes.remove(nextPos);
               invalid++;
            }
         }
         position++;
      }
   }
}
TOP

Related Classes of org.exoplatform.services.jcr.impl.core.query.lucene.QueryResultImpl

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.