Package nexj.core.persistence

Source Code of nexj.core.persistence.GenericCursor

// Copyright 2010-2011 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.persistence;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import nexj.core.meta.Attribute;
import nexj.core.meta.Metaclass;
import nexj.core.meta.Primitive;
import nexj.core.meta.persistence.DataSourceFragment;
import nexj.core.runtime.DataVolumeException;
import nexj.core.runtime.Instance;
import nexj.core.runtime.InstanceArrayList;
import nexj.core.runtime.InstanceList;
import nexj.core.runtime.InstanceRef;
import nexj.core.runtime.UnitOfWork;
import nexj.core.scripting.Function;
import nexj.core.scripting.Pair;
import nexj.core.util.HashTab2D;
import nexj.core.util.Logger;
import nexj.core.util.Lookup;
import nexj.core.util.Lookup2D;
import nexj.core.util.ObjUtil;
import nexj.core.util.PrintWriter;
import nexj.core.util.Printable;
import nexj.core.util.StackTrace;
import nexj.core.util.Undefined;

/**
* Generic reusable cursor implementation.
*/
public abstract class GenericCursor implements Cursor, Printable
{
   // attributes

   /**
    * The query performance warning timeout in milliseconds.
    */
   protected long m_lWarningTimeout;

   /**
    * The currently retrieved record count (needed for retrieving top-optimized plural queries using binary search).
    */
   protected int m_nRecordCount;

   /**
    * The last page offset.
    */
   protected int m_nRecordOffset;

   /**
    * For plural limited queries, this is the value that was used as max count during the last query.
    */
   protected int m_nLastMaxCount;

   /**
    * The total count of retrieved instances (without the skipped ones).
    */
   protected int m_nTotalCount;

   /**
    * The count of instances to skip before returning the result.
    */
   protected int m_nOffset;

   /**
    * The maximum count of instances to retrieve.
    */
   protected int m_nMaxCount;

   /**
    * The limit of the number of instances retrieved in a single unlimited next().
    */
   protected int m_nLimit;

   /**
    * The query timeout in seconds (0 for unlimited, negative to use the default).
    */
   protected int m_nTimeout;

   /**
    * The number of Cartesian product rows in a step(Lookup) page.
    */
   protected int m_nPageSize = 128;

   /**
    * True if the end of the recordset has been reached.
    */
   protected boolean m_bEOF;

   /**
    * True if the end of the current page has been reached.
    */
   protected boolean m_bEOP;

   /**
    * True if debug logging is enabled.
    */
   protected boolean m_bDebug;

   /**
    * True if the generated query has already been logged.
    */
   protected boolean m_bQueryLogged;

   /**
    * True if the stack trace has already been logged.
    */
   protected boolean m_bStackLogged;

   // associations

   /**
    * The root query.
    */
   protected Query m_query;

   /**
    * The root class object.
    */
   protected Metaclass m_metaclass;

   /**
    * Array of instances corresponding to the current record, indexed by query node ordinal number.
    */
   protected Instance[] m_instanceArray;

   /**
    * The root instance list, read by the cursor.
    */
   protected InstanceList m_instanceList;

   /**
    * The set of query-instance pairs retrieved by the cursor, used to ignore the already read instances.
    */
   protected Lookup2D m_instanceSet = new HashTab2D();

   /**
    * Map of instances and annotation names to functions: Function[Instance][String].
    */
   protected Lookup2D m_annotationMap;

   /**
    * The output map: Instance[Query].
    */
   protected Lookup m_outputMap;

   /**
    * The instance list retrieved from the cache.
    */
   protected InstanceList m_cachedInstanceList;

   /**
    * The UOW for read locking.
    */
   protected UnitOfWork m_uow;

   /**
    * Iterator over the Cartesian product of the heterogeneous joins.
    */
   protected QueryCartesianProductIterator m_stepIterator;

   /**
    * The class logger.
    */
   protected Logger m_logger;

   /**
    * The long-running query logger.
    */
   protected Logger m_lrqLogger;

   // constructors

   /**
    * Constructs the cursor and allocates the necessary resources.
    * @param query The query.
    */
   protected GenericCursor(Query query)
   {
      init(query);
   }

   /**
    * Constructs the cursor, performing no initialization.
    */
   protected GenericCursor()
   {
   }

   // operations

   /**
    * Template method to initialize the cursor.
    * @param query The query with which to initialize the cursor.
    */
   protected void init(Query query)
   {
      assert query.isRoot();

      m_query = query;
      m_metaclass = query.getMetaclass();

      if (!query.isIdentity())
      {
         while (m_metaclass.getBase() != null)
         {
            m_metaclass = m_metaclass.getBase();
         }
      }

      m_nTimeout = query.getTimeout();

      if (m_nTimeout < 0 && (query.isCursor() || query.isLocking()))
      {
         m_nTimeout = Query.TIMEOUT_UNLIMITED;
      }

      m_logger = getLogger();
      m_lrqLogger = getLRQLogger();
      m_bDebug = m_logger.isDebugEnabled();

      init(query.getFragment());

      if (m_lWarningTimeout > 0 && !m_bDebug)
      {
         if (m_lrqLogger != null && m_lrqLogger.isWarnEnabled())
         {
            if (m_lrqLogger.isInfoEnabled())
            {
               if (m_lrqLogger.isDebugEnabled())
               {
                  m_lWarningTimeout >>= 1;

                  if (m_lrqLogger.isDumpEnabled())
                  {
                     m_lWarningTimeout >>= 1;
                  }
               }

               if (m_lWarningTimeout == 0)
               {
                  m_lWarningTimeout = 1;
               }
            }
            else
            {
               m_lWarningTimeout <<= 1;
            }
         }
         else
         {
            m_lWarningTimeout = 0;
         }
      }

      m_nLimit = m_query.getLimit();

      if (m_query.getWhere() != null && m_query.getWhere().isConstant())
      {
         // True where clause has been folded by Query
         m_bEOF = true;
      }
      else
      {
         run();
      }
   }

   /**
    * The cursor type name, used e.g. in log messages.
    */
   protected abstract String getType();

   /**
    * @return The class logger.
    */
   protected abstract Logger getLogger();

   /**
    * @return The long-running query logger. Can be null.
    */
   protected abstract Logger getLRQLogger();

   /**
    * Template method to initialize the cursor, including the adapter and timeouts.
    * @param fragment The fragment to set. Can be null if the data source does not support fragments.
    */
   protected abstract void init(DataSourceFragment fragment);

   /**
    * @return True if the duration log is enabled.
    */
   private boolean isDurationLogEnabled()
   {
      return m_bDebug || m_lWarningTimeout > 0;
   }

   /**
    * @return The current time offset in milliseconds from 1-Jan-1970 UTC,
    * or 0 if the duration log is disabled.
    */
   protected long getCurrentTime()
   {
      if (isDurationLogEnabled())
      {
         return System.currentTimeMillis();
      }

      return 0;
   }

   /**
    * Logs the operation duration.
    * @param lStartTime The start time.
    * @param bQuery True for query, false for retrieval.
    */
   protected void logDuration(long lStartTime, boolean bQuery)
   {
      if (isDurationLogEnabled())
      {
         long lDuration = System.currentTimeMillis() - lStartTime;

         if (m_bDebug || lDuration > m_lWarningTimeout)
         {
            int nLevel;

            if (m_bDebug)
            {
               nLevel = Logger.DEBUG;
            }
            else
            {
               nLevel = Logger.WARN;

               if (!m_bQueryLogged)
               {
                  logQuery(nLevel);
                  m_bQueryLogged = true;
               }
            }

            if (m_logger.isLevelEnabled(nLevel))
            {
               StringBuilder buf = new StringBuilder(128);

               if (bQuery)
               {
                  buf.append(getType() + " execution time:");
               }
               else
               {
                  buf.append("Retrieved ");
                  buf.append(Math.max(m_instanceList.getCount() - m_nOffset, 0));

                  if (m_nOffset > 0)
                  {
                     buf.append(", skipped ");
                     buf.append(m_nOffset);
                  }

                  buf.append(" instance(s) of ");
                  buf.append(m_query.getMetaclass().getName());

                  int nRecordCount = m_nRecordCount - m_nRecordOffset;

                  if (m_instanceList.getCount() < nRecordCount && nRecordCount != 0)
                  {
                     buf.append(" from ");
                     buf.append(nRecordCount);
                     buf.append(" record(s)");
                  }

                  buf.append(" in");
               }

               buf.append(' ');
               buf.append(lDuration);
               buf.append(" ms");

               boolean bPerfProblem = (lDuration > ((m_lWarningTimeout > 0) ? m_lWarningTimeout : 100));

               if (bPerfProblem)
               {
                  buf.append(" (potential performance problem)");

                  if (m_bStackLogged)
                  {
                     m_logger.log(nLevel, buf);
                  }
                  else
                  {
                     StackTrace t = new StackTrace();

                     m_query.getInvocationContext().getMachine().updateStackTrace(t);
                     m_logger.log(nLevel, buf, t);
                     m_bStackLogged = true;
                  }
               }
               else
               {
                  m_logger.log(nLevel, buf);
               }
            }
         }
      }
   }

   /**
    * Logs a query timeout.
    * @param lStartTime The start time offset in milliseconds from 1-Jan-1970 UTC,
    * or 0 if the duration log is disabled.
    * @param bQuery True to log the query.
    */
   protected void logTimeout(long lStartTime, boolean bQuery)
   {
      if (lStartTime != 0 && !m_query.isLocking())
      {
         long lDuration = System.currentTimeMillis() - lStartTime;

         if (bQuery)
         {
            logQuery(Logger.WARN);
         }

         if (m_logger.isWarnEnabled())
         {
            StackTrace t = new StackTrace();

            m_query.getInvocationContext().getMachine().updateStackTrace(t);
            m_logger.warn(getType() + " execution time: " + lDuration + " ms (potential performance problem)", t);
         }
      }
   }

   /**
    * Logs the generated query.
    * @param nLevel The log level.
    * @see Logger
    */
   protected abstract void logQuery(int nLevel);

   /**
    * Executes the query.
    */
   protected abstract void query() throws PersistenceException;

   /**
    * Runs the query.
    */
   protected void run() throws PersistenceException
   {
      m_bQueryLogged = false;
      m_nRecordCount = 0;
      m_nRecordOffset = 0;
      m_nLastMaxCount = m_query.getMaxCount();
      m_nOffset = Math.max(0, m_query.getOffset());

      if (m_query.isCached())
      {
         m_cachedInstanceList = m_query.readCached();
         m_nOffset = 0;
      }
      else
      {
         m_instanceArray = new Instance[m_query.getOutputQueryCount()];
         m_cachedInstanceList = null;
         query();
      }
   }

   /**
    * Fetches the next record of data.
    * @return True if the row has been retrieved, false for EOF.
    */
   protected abstract boolean fetch() throws PersistenceException;

   /**
    * Gets an unconverted value corresponding to a field from the current record.
    * This is a template method invoked by GenericCursor.getValue(Field).
    * @param field The field for which to get the value.
    */
   protected abstract Object getRawValue(Field field) throws PersistenceException;

   /**
    * Gets a value corresponding to a field from the current record.
    * @param field The field for which to get the value.
    */
   protected final Object getValue(Field field) throws PersistenceException
   {
      assert field.isPersistent();

      Object value = getRawValue(field);

      if (field.getConverter() != null)
      {
         value = field.getConverter().getForwardFunction().invoke(value);
      }

      return value;
   }

   /**
    * Gets a OID represented by given fields.
    * This is a helper method that getKey() can use for convenience (i.e. if getKey() happens
    * to have available an array of Field that needs converting to an OID).
    * @param keyFieldArray The array of key fields.
    * @param The resulting OID, or null, if any of the field values is null.
    */
   protected final OID getKey(Field[] keyFieldArray) throws PersistenceException
   {
      Object[] keyValueArray = new Object[keyFieldArray.length];

      for (int i = 0; i < keyFieldArray.length; ++i)
      {
         if ((keyValueArray[i] = getValue(keyFieldArray[i])) == null)
         {
            return null;
         }
      }

      return new OID(keyValueArray);
   }

   /**
    * Gets a OID represented by given fields.
    * @param item The mapping item, supplied by Query.getItem(), Query.getParentItem(), or
    * Query.getChildItem().
    * @return The resulting OID, or null, if any of the field values is null.
    */
   protected abstract OID getKey(Object item);

   /**
    * Gets the class object corresponding to a query node for the current record.
    * @param query The query node.
    * @return The class object.
    */
   protected Metaclass getMetaclass(Query query)
   {
      if (query == m_query)
      {
         return m_metaclass;
      }

      return (Metaclass)m_instanceArray[query.getParent().getOrdinal()]
         .getMetaclass().getDerivedAttribute(query.getAttribute()).getType();
   }

   /**
    * Releases the data source connection.
    */
   protected abstract void disconnect();

   /**
    * Releases any resources needed before EOF.
    */
   protected void release()
   {
      disconnect();
      m_instanceArray = null;
      m_instanceSet = null;
      m_annotationMap = null;
      m_outputMap = null;
      m_cachedInstanceList = null;
      m_uow = null;
      m_stepIterator = null;
   }

   /**
    * Releases the resources allocated by the cursor.
    */
   public void close()
   {
      m_bEOF = true;
      release();
      m_annotationMap = null;
      m_instanceList = null;
   }

   /**
    * Discards an attribute value, which could change when an instance is updated.
    * @param instance The instance from which to discard the value.
    * @param attribute The attribute, which value to discard.
    */
   protected static void discard(Instance instance, Attribute attribute)
   {
      if (attribute.getMetaclass().isUpcast(instance.getMetaclass()))
      {
         instance.discard(attribute, false);
      }
   }

   /**
    * Discards the query associations, which could change when an instance is updated.
    * @param instance The instance from which to discard the value.
    * @param query The query from which to take the associations.
    */
   protected static void discard(Instance instance, Query query)
   {
      for (Iterator itr = query.getAssocIterator(Query.ASSOC_QUERY); itr.hasNext();)
      {
         Query assoc = (Query)itr.next();

         if (assoc.isOutput())
         {
            discard(instance, assoc.getAttribute());
         }
      }
   }

   /**
    * Makes one step through the instances.
    * @param bDiscardExtra True to discard the last instance if the maximum number of instances has been reached.
    * @return True if the step has been executed, false if the end of the data has been reached.
    */
   protected boolean internalStep(boolean bDiscardExtra)
   {
      if (m_bEOF)
      {
         return false;
      }

      if (m_bEOP)
      {
         m_bEOP = false;
      }
      else if (bDiscardExtra &&
         m_nMaxCount >= 0 &&
         !m_query.isPlural() &&
         m_nRecordCount - m_nRecordOffset == m_nOffset + m_nMaxCount)
      {
         m_bEOF = true;
         m_bEOP = false;

         return false;
      }
      else if (fetch())
      {
         ++m_nRecordCount;
      }
      else if (!m_query.isPlural() // Not a multi-row-per-instance query
         || m_nLastMaxCount <= 0 // No max requested or no rows actually expected
         || !m_query.isLimited() // Not limited by adapter
         || m_nRecordCount < m_nLastMaxCount) // Retrieved less records than requested, so this has to be the last possible iteration
      {
         // Original recordset finished and no need to initialize for plural query
         m_bEOF = true;

         return false;
      }
      else
      {
         // Save the query count so it could be reset to original state after SQL generation
         int nQueryMaxCountSaved = m_query.getMaxCount();

         try
         {
            // Increment the max count for this run (exponentially double).
            // On overflow, the resulting negative means unlimited count.
            m_query.setMaxCount(m_nLastMaxCount << 1);

            // Requery with the new max count
            run();
         }
         finally
         {
            // Reset to original request
            m_query.setMaxCount(nQueryMaxCountSaved);
         }

         if (!fetch())
         {
            // Unable to get more on the second try => we are definitely done
            m_bEOF = true;

            return false;
         }

         ++m_nRecordCount;
         bDiscardExtra = true;
         // Skip everything past max count no matter what was originally requested,
         // as this iteration has a manually set max count with requerying.
      }

      // The UOW for read locking
      m_uow = m_query.getInvocationContext().getUnitOfWork();

      if (m_uow != null && !m_uow.isDirty())
      {
         m_uow = null;
      }

      boolean bSkipInstance = (m_instanceList.getCount() < m_nOffset);

      for (int nQuery = 0, nQueryCount = m_query.getOutputQueryCount(); nQuery < nQueryCount; ++nQuery)
      {
         Query query = m_query.getOutputQuery(nQuery);
         Attribute assoc = query.getAttribute();
         boolean bIdentity = query.isIdentity();
         Instance container;
         Attribute reverse;
         OID oid;

         // First output query must be main query node.
         assert (nQuery == 0) == (query == m_query);

         // Query ordinal is the output query ordinal (unless query is joined heterogeneously).
         assert nQuery == query.getOrdinal() || query.getRoot() != m_query;

         // Query ordinal is, by definition, the output query ordinal of its root.
         assert query.getRoot().getOutputQuery(query.getOrdinal()) == query;

         // Get the query class instance OID

         if (query == m_query)
         {
            container = null;
            reverse = null;
            oid = (bIdentity) ? getKey(query.getItem()) : null;
         }
         else
         {
            assert bIdentity;

            container = m_instanceArray[query.getParent().getOrdinal()];

            if (container == null || !assoc.getMetaclass().isUpcast(container.getMetaclass()))
            {
               if (query.getRoot() == m_query)
               {
                  m_instanceArray[query.getOrdinal()] = null;
               }

               continue;
            }

            assoc = container.getMetaclass().getDerivedAttribute(assoc);
            reverse = assoc.getReverse();

            if (reverse != null && reverse.isCollection())
            {
               reverse = null;
            }

            oid = getKey(query.getParentItem());
         }

         InstanceList instanceList;
         boolean bAdd = true;

         if (container == null)
         {
            instanceList = m_instanceList;
         }
         else
         {
            Object value = container.getOldValueDirect(assoc.getOrdinal());

            if (value == Undefined.VALUE)
            {
               if (assoc.isCollection())
               {
                  instanceList = new InstanceArrayList();
                  instanceList.setAssociation(container, assoc, true);
                  instanceList.setLazy(false);
                  container.setOldValueDirect(assoc.getOrdinal(), instanceList);
               }
               else
               {
                  instanceList = null;

                  if (oid == null)
                  {
                     container.setOldValueDirect(assoc.getOrdinal(), null);
                  }
               }
            }
            else
            {
               if (assoc.isCollection() && !(value instanceof Undefined))
               {
                  instanceList = (InstanceList)value;
                  instanceList.setLazy(false);
               }
               else
               {
                  instanceList = null;
                  bAdd = false;
               }
            }
         }

         Metaclass metaclass;

         if (bIdentity)
         {
            if (oid == null)
            {
               if (query.getRoot() == m_query)
               {
                  m_instanceArray[query.getOrdinal()] = null;
               }

               continue;
            }

            if (query.getRoot() != m_query && !query.isLazy())
            {
               if (!bSkipInstance)
               {
                  query.addParentInstance(container, oid);
               }

               continue;
            }

            Field field = query.getTypeCodeField();

            if (field != null)
            {
               metaclass = query.getPersistenceMapping().findMetaclassByTypeCode(getValue(field));

               if (metaclass == null)
               {
                  m_instanceArray[query.getOrdinal()] = null;

                  continue;
               }
            }
            else
            {
               metaclass = getMetaclass(query);
            }
         }
         else
         {
            metaclass = getMetaclass(query);
         }

         InstanceRef ref = m_query.getInvocationContext().findInstanceRef(metaclass, oid);
         Instance instance = (ref == null) ? null : ref.getInstance();

         // Check if the instance has already been retrieved
         if (instance != null)
         {
            // Already retrieved instance
            if (instance.isLazy())
            {
               if (query.isLazy())
               {
                  if (!metaclass.isUpcast(instance.getLazyMetaclass()))
                  {
                     instance.setLazyMetaclass(metaclass);
                  }
               }
               else
               {
                  instance.setMetaclass(metaclass);
               }
            }

            if (instanceList != null)
            {
               bAdd = !instanceList.contains(instance);

               if (bAdd &&
                  container == null &&
                  m_nMaxCount >= 0 &&
                  instanceList.getCount() == m_nOffset + m_nMaxCount)
               {
                  m_bEOF = bDiscardExtra;
                  m_bEOP = !bDiscardExtra;

                  return false;
               }
            }

            if (bAdd)
            {
               switch (instance.getState())
               {
                  case Instance.DELETED:
                     bAdd = false;
                     break;

                  case Instance.DIRTY:
                     if (reverse != null)
                     {
                        Object reverseValue = instance.getValueDirect(reverse.getOrdinal());

                        if (reverseValue != Undefined.VALUE && reverseValue != container)
                        {
                           bAdd = false;
                        }
                     }

                     break;
               }

               if (!bAdd && instanceList == null)
               {
                  if (container.getValueDirect(assoc.getOrdinal()) == Undefined.VALUE)
                  {
                     // Set the new value to null, old value to the retrieved instance
                     container.setValueDirect(assoc.getOrdinal(), null);
                  }

                  bAdd = true;
               }
            }

            if (m_instanceSet.get(query, instance) == null)
            {
               // Merge the not yet retrieved attribute values

               if (!bSkipInstance)
               {
                  // Overwrite only if the instance is clean
                  boolean bOverwrite = (instance.getUnitOfWork() == null);
                  Field lockingField = query.getLockingField();

                  if (lockingField != null)
                  {
                     Attribute lockingAttribute = lockingField.getAttribute();
                     Object oldValue = instance.getNewValueDirect(lockingAttribute.getOrdinal());

                     if (oldValue != Undefined.VALUE)
                     {
                        // Check the old lock value
                        Primitive primitive = (Primitive)lockingAttribute.getType();
                        Object value = getValue(lockingField);

                        if (((Boolean)primitive.findNEFunction(primitive).invoke(oldValue, value)).booleanValue())
                        {
                           // The lock values do not match
                           // If the instance is dirty/deleted or share-locked, throw an exception
                           if (!bOverwrite || ref.isLocked())
                           {
                              throw new OptimisticLockException(instance);
                           }

                           // Discard all the instance attributes
                           for (int i = 0, n = metaclass.getInstanceAttributeCount(); i != n; ++i)
                           {
                              discard(instance, metaclass.getInstanceAttribute(i));
                           }

                           instance.setOldValueDirect(lockingAttribute.getOrdinal(), value);
                        }
                        else
                        {
                           if (bOverwrite)
                           {
                              discard(instance, query);

                              if (reverse != null && bAdd && !instance.isLazy())
                              {
                                 instance.setValueDirect(reverse.getOrdinal(), container);
                              }

                              bOverwrite = false;
                           }
                        }
                     }
                     else
                     {
                        // If the instance is clean, discard the
                        // instance assocs referenced in this query
                        if (bOverwrite)
                        {
                           discard(instance, query);
                        }
                     }
                  }
                  else
                  {
                     // If the instance is clean, discard the
                     // instance assocs referenced in this query
                     if (bOverwrite)
                     {
                        discard(instance, query);
                     }
                  }

                  if (bOverwrite && reverse != null && bAdd && !instance.isLazy())
                  {
                     instance.setValueDirect(reverse.getOrdinal(), container);
                  }

                  addInstance(instance, ref, metaclass, query, bOverwrite);
               }
            }
         }
         else
         {
            // New instance

            if (container == null &&
               m_nMaxCount >= 0 &&
               instanceList.getCount() == m_nOffset + m_nMaxCount)
            {
               m_bEOF = bDiscardExtra;
               m_bEOP = !bDiscardExtra;

               return false;
            }

            instance = new Instance(metaclass, query.isLazy(), m_query.getInvocationContext());
            ref = instance.cache(oid);

            if (!bSkipInstance)
            {
               addInstance(instance, ref, metaclass, query, true);
            }
         }

         if (bAdd)
         {
            if (instanceList != null)
            {
               instanceList.add(instance, InstanceList.DIRECT);
            }
            else
            {
               container.setOldValueDirect(assoc.getOrdinal(), instance);
            }

            if (reverse != null && !instance.isLazy() && instance.getOldValueDirect(reverse.getOrdinal()) == Undefined.VALUE)
            {
               instance.setOldValueDirect(reverse.getOrdinal(), container);
            }
         }

         // If query is heterogeneous then this node is finished--the lazy instance has been created.
         if (query.getRoot() != m_query)
         {
            continue;
         }

         if (bSkipInstance)
         {
            break;
         }

         m_instanceArray[query.getOrdinal()] = instance;

         if (m_outputMap != null)
         {
            m_outputMap.put(query, instance);
         }

         if (query.isJoin() && bAdd)
         {
            query.join(instance, getKey(query.getChildItem()));
         }
      }

      return true;
   }

   /**
    * Sets the primitive attribute values on the instance.
    * @param instance The instance to receive the values.
    * @param ref The instance reference.
    * @param metaclass The class being read.
    * @param query The query providing the values.
    * @param bOverwrite True to overwrite values in the instance.
    */
   protected void addInstance(Instance instance, InstanceRef ref, Metaclass metaclass, Query query, boolean bOverwrite)
   {
      for (Field field = query.getFirstOutputField(); field != null; field = field.getNext())
      {
         Attribute attribute = field.getAttribute();

         // Consider only this class and inherited attributes
         if (attribute != null)
         {
            if (attribute.getMetaclass().isUpcast(metaclass))
            {
               int nOrdinal = attribute.getOrdinal();

               if (bOverwrite || instance.getOldValueDirect(nOrdinal) == Undefined.VALUE)
               {
                  instance.setOldValueDirect(nOrdinal, getValue(field));
               }
            }
            else if (!query.isIdentity())
            {
               instance.setAnnotation(attribute.getName(), getValue(field));
            }
         }

         String sAnnotation = field.getAnnotation();

         if (sAnnotation != null)
         {
            if (field.isPersistent())
            {
               instance.setAnnotation(sAnnotation, getValue(field));
            }
            else
            {
               Object mapping = field.getMapping();

               if (mapping == null)
               {
                  instance.setAnnotation(sAnnotation, field.getItem());
               }
               else if (mapping instanceof Field)
               {
                  instance.setAnnotation(sAnnotation, getValue((Field)mapping));
               }
               else
               {
                  if (m_annotationMap == null)
                  {
                     m_annotationMap = new HashTab2D();
                  }

                  m_annotationMap.put(instance, sAnnotation, mapping);
               }
            }
         }
      }

      // Set the readable attribute value if it is already in the where clause
      if (query.isSecure() && metaclass.getReadAccessAttribute() != null && !instance.isLazy())
      {
         int nOrdinal = metaclass.getReadAccessAttribute().getOrdinal();

         if (bOverwrite || instance.getOldValueDirect(nOrdinal) == Undefined.VALUE)
         {
            instance.setOldValueDirect(nOrdinal, Boolean.TRUE);
         }
      }

      // Share-lock the instance
      if (m_uow != null)
      {
         m_uow.lock(ref, false);
      }

      m_instanceSet.put(query, instance, instance);
   }

   /**
    * Retrieves the next page of instances into m_instanceList.
    * @param nMaxCount The maximum number of instances to retrieve.
    * @param bStep True to execute only one internal step.
    */
   protected void internalNext(int nMaxCount, boolean bStep)
   {
      long lStartTime = (bStep) ? 0 : getCurrentTime();
      boolean bDiscardExtra = false;

      m_nRecordOffset = m_nRecordCount;

      if (m_query.getMaxCount() >= 0)
      {
         m_nMaxCount = m_query.getMaxCount() - m_nTotalCount;

         if (m_nMaxCount < 0)
         {
            m_nMaxCount = 0;
         }

         if (nMaxCount >= 0 && nMaxCount < m_nMaxCount)
         {
            m_nMaxCount = nMaxCount;
         }
         else
         {
            bDiscardExtra = true;
         }
      }
      else
      {
         m_nMaxCount = nMaxCount;
      }

      if (m_instanceList == null)
      {
         if (m_cachedInstanceList != null && (m_nMaxCount < 0 || m_nMaxCount > m_cachedInstanceList.size()))
         {
            m_instanceList = m_cachedInstanceList;
            m_bEOF = true;
         }
         else
         {
            m_instanceList = new InstanceArrayList((m_nMaxCount >= 0) ? m_nMaxCount + 1 : 9);
         }
      }
      else
      {
         m_instanceList.clear();
      }

      if (m_cachedInstanceList != null)
      {
         if (m_instanceList != m_cachedInstanceList)
         {
            int nCount = m_cachedInstanceList.size() - m_nTotalCount;

            if (nCount <= 0)
            {
               m_bEOF = true;
            }
            else
            {
               if (m_nMaxCount > 0 && m_nMaxCount < nCount)
               {
                  nCount = m_nMaxCount;
               }

               if (nCount > m_nLimit && m_nLimit > 0 && nMaxCount < 0)
               {
                  throw new DataVolumeException("err.persistence.instanceCount",
                     new Object[]{m_query.getMetaclass().getName(), Primitive.createInteger(m_nLimit)});
               }

               for (int i = m_nTotalCount; --nCount >= 0; ++i)
               {
                  m_instanceList.add(m_cachedInstanceList.getInstance(i), InstanceList.DIRECT);
               }
            }
         }
      }
      else
      {
         while (internalStep(bDiscardExtra) && !bStep)
         {
            int nCount = m_instanceList.getCount();

            if (nCount > m_nLimit && m_nLimit > 0 && nMaxCount < 0)
            {
               throw new DataVolumeException("err.persistence.instanceCount",
                  new Object[]{m_query.getMetaclass().getName(), Primitive.createInteger(m_nLimit)});
            }
         }
      }

      m_nTotalCount += Math.max(m_instanceList.getCount() - m_nOffset, 0);

      if (!bStep)
      {
         completeQuery();
         logDuration(lStartTime, false);
      }

      if (m_bEOF)
      {
         release();
      }
      else
      {
         if (m_instanceSet != null)
         {
            m_instanceSet.clear();
         }
      }

      // Remove the empty elements according to the query offset

      InstanceList instanceList;

      if (m_nOffset > 0)
      {
         int nCount = m_instanceList.getCount() - m_nOffset;

         if (nCount < 0)
         {
            nCount = 0;
         }

         instanceList = new InstanceArrayList(nCount);

         for (int i = m_nOffset; nCount > 0; ++i, --nCount)
         {
            instanceList.add(m_instanceList.getInstance(i), InstanceList.DIRECT);
         }

         m_instanceList = instanceList;
         m_nOffset = 0;
      }

      if (m_bDebug)
      {
         m_logger.dump(m_instanceList);
      }
   }

   /**
    * Completes the query.
    */
   protected void completeQuery()
   {
      m_query.complete();

      if (m_annotationMap != null)
      {
         for (Lookup2D.Iterator itr = m_annotationMap.valueIterator(); itr.hasNext();)
         {
            Function fun = (Function)itr.next();
            Instance instance = (Instance)itr.getKey1();

            instance.setAnnotation((String)itr.getKey2(),
               instance.getContext().getMachine().invoke(fun, instance, (Pair)null));
         }

         if (m_bEOF)
         {
            m_annotationMap = null;
         }
         else
         {
            m_annotationMap.clear();
         }
      }
   }

   /**
    * Retrieves the next page of instances.
    * @param nMaxCount The maximum number of instances to retrieve.
    * @return The list of the retrieved instances.
    */
   public InstanceList next(int nMaxCount)
   {
      if (m_bEOF)
      {
         return new InstanceArrayList(0);
      }

      internalNext(nMaxCount, false);

      InstanceList instanceList = m_instanceList;

      m_instanceList = null;

      return instanceList;
   }

   /**
    * Retrieves the next instance.
    * @return The next instance, or null if none.
    */
   public Instance next()
   {
      if (m_bEOF)
      {
         return null;
      }

      internalNext(1, false);

      if (m_instanceList.isEmpty())
      {
         return null;
      }

      return m_instanceList.remove(0, InstanceList.DIRECT);
   }

   /**
    * Makes one step through the instances.
    * @param map The query to instance output map.
    * @return True if the step has been made, or false if EOF has been reached.
    */
   public boolean step(Lookup map)
   {
      if (map != null)
      {
         map.clear();
      }

      // Advance to next row in Cartesian product using results stored in the Cartesian product iterator
      if (m_stepIterator != null && m_stepIterator.hasNext())
      {
         m_stepIterator.step(map);

         return true;
      }

      if (!m_query.isHeterogeneous() && map != null)
      {
         m_outputMap = map;
      }

      if (!m_bEOF)
      {
         internalNext(1, true);
      }

      // Store the new instance and its sub-collections in the Cartesian product iterator
      if (!m_bEOF)
      {
         if (m_query.isHeterogeneous())
         {
            ArrayList stepList = new ArrayList(m_nPageSize);

            // Read a page of steps
            for (int i = 1; i < m_nPageSize && !m_bEOF; i++)
            {
               stepList.add(m_instanceArray);
               m_instanceArray = new Instance[m_instanceArray.length];
               internalNext(1, true);
            }

            if (!m_bEOF)
            {
               stepList.add(m_instanceArray);
            }

            completeQuery()// aggregate heterogeneous joins across the page to reduce RPCs

            m_stepIterator = new QueryCartesianProductIterator(m_query, stepList);
            m_stepIterator.step(map);

            return true;
         }

         completeQuery();
      }

      m_outputMap = null;

      return !m_bEOF;
   }

   /**
    * @see java.lang.Object#finalize()
    */
   protected void finalize() throws Throwable
   {
      close();
   }

   /**
    * @see nexj.core.util.Printable#printOn(nexj.core.util.PrintWriter)
    */
   public void printOn(PrintWriter writer) throws IOException
   {
      writer.write(ObjUtil.getShortClassName(this));
      writer.write("(total=");
      writer.print(m_nTotalCount);
      writer.write(", query=");
      writer.print(m_query);
      writer.write(')');
   }

   /**
    * @see java.lang.Object#toString()
    */
   public String toString()
   {
      return PrintWriter.toString(this);
   }
}
TOP

Related Classes of nexj.core.persistence.GenericCursor

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.