Package org.hibernate.search.query

Source Code of org.hibernate.search.query.ScrollableResultsImpl$LoadedObject

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.  All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA  02110-1301  USA
*/
package org.hibernate.search.query;

import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Blob;
import java.sql.Clob;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

import org.apache.lucene.search.IndexSearcher;
import org.slf4j.Logger;

import org.hibernate.HibernateException;
import org.hibernate.ScrollableResults;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.search.SearchException;
import org.hibernate.search.SearchFactory;
import org.hibernate.search.engine.DocumentExtractor;
import org.hibernate.search.engine.EntityInfo;
import org.hibernate.search.engine.Loader;
import org.hibernate.search.util.LoggerFactory;
import org.hibernate.type.Type;

/**
* Implements scrollable and paginated resultsets.
* Contrary to Query#iterate() or Query#list(), this implementation is
* exposed to returned null objects (if the index is out of date).
* <p/>
* <p/>
* The following methods that change the value of 'current' will check
* and set its value to either 'afterLast' or 'beforeFirst' depending
* on direction. This is to prevent rogue values from setting it outside
* the boundaries of the results.
* <ul>
* <li>next()</li>
* <li>previous()</li>
* <li>scroll(i)</li>
* <li>last()</li>
* <li>first()</li>
* </ul>
*
* @see org.hibernate.Query
*
* @author Emmanuel Bernard
* @author John Griffin
* @author Sanne Grinovero
*/
public class ScrollableResultsImpl implements ScrollableResults {
 
  private static final Logger log = LoggerFactory.make();
 
  private final SearchFactory searchFactory;
  private final IndexSearcher searcher;
  private final int first;
  private final int max;
  private final int fetchSize;
  private final Loader loader;
  private final DocumentExtractor documentExtractor;
  private final SessionImplementor session;
 
  /**
   * Caches result rows and EntityInfo from
   * <code>first</code> to <code>max</code>
   */
  private final LoadedObject[] resultsContext;
 
  private int current;

  public ScrollableResultsImpl( IndexSearcher searcher, int first, int max, int fetchSize, DocumentExtractor extractor,
      Loader loader, SearchFactory searchFactory, SessionImplementor sessionImplementor
  ) {
    this.searchFactory = searchFactory;
    this.searcher = searcher;
    this.first = first;
    this.max = max;
    this.loader = loader;
    this.documentExtractor = extractor;
    this.fetchSize = fetchSize;
    this.session = sessionImplementor;
    int size = Math.max( max - first + 1, 0 );
    this.resultsContext = new LoadedObject[size];
    beforeFirst();
  }

  private LoadedObject ensureCurrentLoaded() {
    LoadedObject currentCacheRef = resultsContext[current - first];
    if ( currentCacheRef != null ) {
      return currentCacheRef;
    }
    // the loading window is optimized for scrolling in both directions:
    int windowStop = Math.min( max + 1 , current + fetchSize );
    int windowStart = Math.max( first, current - fetchSize + 1 );
    List<EntityInfo> entityInfosToLoad = new ArrayList<EntityInfo>( fetchSize );
    int sizeToLoad = 0;
    for (int x = windowStart; x < windowStop; x++) {
      int arrayIdx = x - first;
      LoadedObject lo = resultsContext[arrayIdx];
      if ( lo == null ) {
        lo = new LoadedObject();
        // makes hard references and extract EntityInfos:
        entityInfosToLoad.add( lo.getEntityInfo( x ) );
        resultsContext[arrayIdx] = lo;
        sizeToLoad++;
        if ( sizeToLoad >= fetchSize )
          break;
      }
    }
    //preload efficiently by batches:
    if ( sizeToLoad > 1 ) {
      loader.load( entityInfosToLoad.toArray( new EntityInfo[sizeToLoad] ) );
      //(no references stored at this point: they still need to be loaded one by one to inject null results)
    }
    return resultsContext[ current - first ];
  }
 
  /**
   * {@inheritDoc}
   */
  public boolean next() {
    //  Increases cursor pointer by one. If this places it >
    //  max + 1 (afterLast) then set it to afterLast and return
    //  false.
    if ( ++current > max ) {
      afterLast();
      return false;
    }
    return true;
  }

  public boolean previous() {
    //  Decreases cursor pointer by one. If this places it <
    //  first - 1 (beforeFirst) then set it to beforeFirst and
    //  return false.
    if ( --current < first ) {
      beforeFirst();
      return false;
    }
    return true;
  }

  public boolean scroll(int i) {
    //  Since we have to take into account that we can scroll any
    //  amount positive or negative, we perform the same tests that
    //  we performed in next() and previous().
    current = current + i;
    if ( current > max ) {
      afterLast();
      return false;
    }
    else if ( current < first ) {
      beforeFirst();
      return false;
    }
    else {
      return true;
    }
  }

  public boolean last() {
    current = max;
    if ( current < first ) {
      beforeFirst();
      return false;
    }
    return max >= first;
  }

  public boolean first() {
    current = first;
    if ( current > max ) {
      afterLast();
      return false;
    }
    return max >= first;
  }

  public void beforeFirst() {
    current = first - 1;
  }

  public void afterLast() {
    current = max + 1;
    //TODO help gc by clearing all structures when using forwardonly scrollmode.
  }

  public boolean isFirst() {
    return current == first;
  }

  public boolean isLast() {
    return current == max;
  }

  public void close() {
    try {
      searchFactory.getReaderProvider().closeReader( searcher.getIndexReader() );
    }
    catch (SearchException e) {
      log.warn( "Unable to properly close searcher in ScrollableResults", e );
    }
  }

  public Object[] get() throws HibernateException {
    // don't throw an exception here just
    // return 'null' this is similar to the
    // RowSet spec in JDBC. It returns false
    // (or 0 I can't remember) but we can't
    // do that since we have to make up for
    // an Object[]. J.G
    if ( current < first || current > max ) return null;
    LoadedObject cacheEntry = ensureCurrentLoaded();
    return cacheEntry.getManagedResult( current );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Object get(int i) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Type getType(int i) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Integer getInteger(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Long getLong(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Float getFloat(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Boolean getBoolean(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Double getDouble(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Short getShort(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Byte getByte(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Character getCharacter(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public byte[] getBinary(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public String getText(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Blob getBlob(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Clob getClob(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public String getString(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public BigDecimal getBigDecimal(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public BigInteger getBigInteger(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Date getDate(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Locale getLocale(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public Calendar getCalendar(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  /**
   * This method is not supported on Lucene based queries
   * @throws UnsupportedOperationException always thrown
   */
  public TimeZone getTimeZone(int col) {
    throw new UnsupportedOperationException( "Lucene does not work on columns" );
  }

  public int getRowNumber() {
    if ( max < first ) return -1;
    return current - first;
  }

  public boolean setRowNumber(int rowNumber) {
    if ( rowNumber >= 0 ) {
      current = first + rowNumber;
    }
    else {
      current = max + rowNumber + 1; //max row start at -1
    }
    return current >= first && current <= max;
  }
 
  private final class LoadedObject {
   
    private Reference<Object[]> entity; //never==null but Reference.get can return null
    private Reference<EntityInfo> einfo; //never==null but Reference.get can return null
   
    /**
     * Gets the objects from cache if it is available and attached to session,
     * or reload them and update the cache entry.
     * @param x absolute position in fulltext result.
     * @return the managed objects
     */
    private Object[] getManagedResult(int x) {
      EntityInfo entityInfo = getEntityInfo( x );
      Object[] objects = entity==null ? null : entity.get();
      if ( objects!=null && areAllEntitiesManaged( objects, entityInfo ) ) {
        return objects;
      }
      else {
        Object loaded = loader.load( entityInfo );
        if ( ! loaded.getClass().isArray() ) loaded = new Object[] { loaded };
        objects = (Object[]) loaded;
        this.entity = new SoftReference<Object[]>( objects );
        return objects;
      }
    }

    /**
     * Extract an entityInfo, either from cache or from the index.
     * @param x the position in the index.
     * @return
     */
    private EntityInfo getEntityInfo(int x) {
      EntityInfo entityInfo = einfo==null ? null : einfo.get();
      if ( entityInfo==null ) {
        try {
          entityInfo = documentExtractor.extract( x );
        }
        catch (IOException e) {
          throw new SearchException( "Unable to read Lucene topDocs[" + x + "]", e );
        }
        einfo = new SoftReference<EntityInfo>( entityInfo );
      }
      return entityInfo;
    }

  }
 
  private boolean areAllEntitiesManaged(Object[] objects,  EntityInfo entityInfo) {
    //check if all entities are session-managed and skip the check on projected values
    org.hibernate.Session hibSession = (org.hibernate.Session) session;
    if ( entityInfo.projection != null ) {
      // using projection: test only for entities
      for ( int idx : entityInfo.indexesOfThis ) {
        Object o = objects[idx];
        //TODO improve: is it useful to check for proxies and have them reassociated to persistence context?
        if ( ! hibSession.contains( o ) )
          return false;
      }
      return true;
    }
    else {
      return hibSession.contains( objects[0] );
    }
  }
 
}
TOP

Related Classes of org.hibernate.search.query.ScrollableResultsImpl$LoadedObject

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.