Package er.extensions.eof

Source Code of er.extensions.eof.ERXDatabaseContextDelegate$ObjectNotAvailableException

/*
* Copyright (C) NetStruxr, Inc. All rights reserved.
*
* This software is published under the terms of the NetStruxr
* Public Software License version 0.5, a copy of which has been
* included with this distribution in the LICENSE.NPL file.  */
package er.extensions.eof;

import java.util.Enumeration;

import org.apache.log4j.Logger;

import com.webobjects.eoaccess.EOAccessArrayFaultHandler;
import com.webobjects.eoaccess.EOAdaptor;
import com.webobjects.eoaccess.EOAdaptorChannel;
import com.webobjects.eoaccess.EOAdaptorOperation;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EODatabaseChannel;
import com.webobjects.eoaccess.EODatabaseContext;
import com.webobjects.eoaccess.EODatabaseOperation;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOEntityClassDescription;
import com.webobjects.eoaccess.EOGeneralAdaptorException;
import com.webobjects.eoaccess.EOModel;
import com.webobjects.eoaccess.EOObjectNotAvailableException;
import com.webobjects.eoaccess.EORelationship;
import com.webobjects.eoaccess.EOSQLExpression;
import com.webobjects.eoaccess.EOSQLExpressionFactory;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.eocontrol.EOFaultHandler;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOGlobalID;
import com.webobjects.eocontrol.EOKeyGlobalID;
import com.webobjects.eocontrol.EOSharedEditingContext;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSMutableSet;
import com.webobjects.foundation.NSNotificationCenter;

import er.extensions.foundation.ERXArrayUtilities;
import er.extensions.foundation.ERXProperties;
import er.extensions.foundation.ERXThreadStorage;
import er.extensions.foundation.ERXUtilities;
import er.extensions.jdbc.ERXJDBCConnectionAnalyzer;
import er.extensions.jdbc.ERXSQLHelper;
import er.extensions.logging.ERXPatternLayout;
import er.extensions.statistics.ERXStats;
import er.extensions.statistics.ERXStats.Group;

/**
* This delegate implements several methods from the formal interface
* {@link com.webobjects.eoaccess.EODatabaseContext.Delegate EODatabaseContext.Delegate}.
* Of special note this class adds the ability
* for enterprise objects to generate their own primary keys, correctly throws an
* exception when a toOne relationship object is not found in the database and adds
* debugging abilities to tracking down when faults are fired. It also supports a cache for
* array fault that is checked before they are fetched from the database.
*
* @property er.extensions.ERXDatabaseContextDelegate.Exceptions.regex regular expression to
*           check the exception test for if database connection should be reopened
*/
public class ERXDatabaseContextDelegate {
 
  public static final String DatabaseContextFailedToFetchObject = "DatabaseContextFailedToFetchObject";
  public static final String ERX_ADAPTOR_EXCEPTIONS_REGEX = "er.extensions.ERXDatabaseContextDelegate.Exceptions.regex";
  public static final String ERX_ADAPTOR_EXCEPTIONS_REGEX_DEFAULT = ".*_obtainOpenChannel.*|.*Closed Connection.*|.*No more data to read from socket.*";
 
    public static class ObjectNotAvailableException extends EOObjectNotAvailableException {
      /**
       * Do I need to update serialVersionUID?
       * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the
       * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a>
       */
      private static final long serialVersionUID = 1L;

      private EOGlobalID globalID;
     
    public ObjectNotAvailableException(String message) {
      this(message, null);
    }
     
    public ObjectNotAvailableException(String message, EOGlobalID gid) {
      super(message);
      globalID = gid;
    }
   
    public EOGlobalID globalID() {
      return globalID;
    }
     
    }
 
    /** Basic logging support */
    public final static Logger log = Logger.getLogger(ERXDatabaseContextDelegate.class);
    /** Faulting logging support, logging category: <b>er.transaction.adaptor.FaultFiring</b> */
    public final static Logger dbLog = Logger.getLogger("er.transaction.adaptor.FaultFiring");
    /** Faulting logging support, logging category: <b>er.transaction.adaptor.Exceptions</b> */
    public final static Logger exLog = Logger.getLogger("er.transaction.adaptor.Exceptions");
    /** Faulting logging support, logging category: <b>er.transaction.adaptor.Batching</b> */
    public final static Logger batchLog = Logger.getLogger("er.transaction.adaptor.Batching");

    /** Holds onto the singleton of the default delegate */
    private static ERXDatabaseContextDelegate _defaultDelegate = new ERXDatabaseContextDelegate();
   
    private ERXArrayFaultCache _arrayFaultCache = null;
    private ERXFetchResultCache _fetchResultCache = null;
   
    /** Returns the singleton of the database context delegate */
    public static ERXDatabaseContextDelegate defaultDelegate() {
        return _defaultDelegate;
    }
   
    /**
     * @param delegate - the singleton database context delegate to set
     */
    public static void setDefaultDelegate(ERXDatabaseContextDelegate delegate) {
      _defaultDelegate = delegate;
  }
   
    public ERXArrayFaultCache arrayFaultCache() {
        return _arrayFaultCache;
    }

    public void setArrayFaultCache(ERXArrayFaultCache value) {
        _arrayFaultCache = value;
    }

    public ERXFetchResultCache fetchResultCache() {
        return _fetchResultCache;
    }
   
    public void setFetchResultCache(ERXFetchResultCache value) {
      _fetchResultCache = value;
    }
   
    /**
   * Returns an array of already fetched objects or null if they were not already fetched.
   * @param dbc
   * @param fs
   * @param ec
   */
  public NSArray databaseContextShouldFetchObjects(EODatabaseContext dbc, EOFetchSpecification fs, EOEditingContext ec) {
    NSArray result = null;
    ERXFetchResultCache fetchResultCache = fetchResultCache();
    if (fetchResultCache != null) {
      result = fetchResultCache.objectsForFetchSpecification(dbc, ec, fs);
    }
    return result;
  }

  /**
   * Sets the cache entry for the fetched objects and refreshes the timestamps
   * for fetched objects if batch faulting is enabled.
   *
   * @param dbc
   * @param eos
   * @param fs
   * @param ec
   */
  public void databaseContextDidFetchObjects(EODatabaseContext dbc, NSArray eos, EOFetchSpecification fs, EOEditingContext ec) {
    ERXFetchResultCache fetchResultCache = fetchResultCache();
    if (fetchResultCache != null) {
      fetchResultCache.setObjectsForFetchSpecification(dbc, ec, eos, fs);
    }
    if(autoBatchFetchSize() > 0 && eos.count() > 0) {
      //log.info("Freshen: " + fs.entityName() +  " " + eos.count());
      freshenFetchTimestamps(eos, ec.fetchTimestamp());
    }
  }

    /**
   * Provides for a hook to get at the original exceptions from the JDBC
   * driver, as opposed to the cooked EOGeneralAdaptorException you get from
   * EOF. To see the exceptions trace, set the logger
   * er.transaction.adaptor.Exceptions to DEBUG.
   *
   * @param databaseContext the current database context
   * @param throwable the original exception
   * @return <code>true</code> if the exception has been handled already
   */
  public boolean databaseContextShouldHandleDatabaseException(EODatabaseContext databaseContext, Throwable throwable) {
    if(!reportingError.canEnter(databaseContext)) return true;
    try {
      if(exLog.isDebugEnabled()) {
        exLog.debug("Database Exception occured: " + throwable, throwable);
      } else if(exLog.isInfoEnabled()) {
        exLog.info("Database Exception occured: " + throwable);
      }
      boolean handled = false;
      try {
        handled = ERXSQLHelper.newSQLHelper(databaseContext).handleDatabaseException(databaseContext, throwable);
      } catch(RuntimeException e) {
        databaseContext.rollbackChanges();
        throw e;
      }
      String exceptionsRegex = ERXProperties.stringForKeyWithDefault(ERX_ADAPTOR_EXCEPTIONS_REGEX, ERX_ADAPTOR_EXCEPTIONS_REGEX_DEFAULT);
      if(!handled && throwable.getMessage() != null && throwable.getMessage().matches(exceptionsRegex)) {
        NSArray models = databaseContext.database().models();
        for(Enumeration e = models.objectEnumerator(); e.hasMoreElements(); ) {
          EOModel model = (EOModel)e.nextElement();
          NSDictionary connectionDictionary = model.connectionDictionary();
          if (connectionDictionary != null) {
            NSMutableDictionary mutableConnectionDictionary = connectionDictionary.mutableClone();
            mutableConnectionDictionary.setObjectForKey("<password deleted for log>", "password");
            connectionDictionary = mutableConnectionDictionary;
          }
          log.info(model.name() + ": " + (connectionDictionary == null ? "No connection dictionary!" : connectionDictionary.toString()));
        }
        if ("JDBC".equals(databaseContext.adaptorContext().adaptor().name())) {
          new ERXJDBCConnectionAnalyzer(databaseContext.database().adaptor().connectionDictionary());
        }
      }
      //EOEditingContext ec = ERXEC.newEditingContext();
      //log.info(NSPropertyListSerialization.stringFromPropertyList(EOUtilities.modelGroup(ec).models().valueForKey("connectionDictionary")));
      return !handled;
    } finally {
      reportingError.leave(databaseContext);
    }
  }

    /**
     * Provides the ability for new enterprise objects that implement the interface {@link ERXGeneratesPrimaryKeyInterface}
     * to provide their own primary key dictionary. If the enterprise object implements the above interface then the
     * method <code>primaryKeyDictionary(true)</code> will be called on the object. If the object returns null then a
     * primary key will be generated for the object in the usual fashion.
     * @param databaseContext databasecontext
     * @param object the new enterprise object
     * @param entity the entity of the object
     * @return primary key dictionary to be used or null if a primary key should be generated for the object.
     */
    public NSDictionary databaseContextNewPrimaryKey(EODatabaseContext databaseContext, Object object, EOEntity entity) {
        return object instanceof ERXGeneratesPrimaryKeyInterface ? ((ERXGeneratesPrimaryKeyInterface)object).primaryKeyDictionary(true) : null;
    }

    /**
     * Allows custom handling of dropped connection exceptions. This was needed in WebObjects 4.5 because the
     * OracleEOAdaptor wouldn't correctly handle all exceptions of dropped connections. This may not be needed
     * now.
     * @param dbc current database context
     * @param e throw exception
     * @return if the exception is one of the bad ones that isn't handled then the method <code>handleDroppedConnection</code>
     *         is called directly on the database object of the context and <code>false</code> is returned otherwise <code>true</code>.
     */
    // CHECKME: Is this still needed now?
    public boolean databaseContextShouldHandleDatabaseException(EODatabaseContext dbc, Exception e) throws Throwable {
      if(!reportingError.canEnter(dbc)) return true;
      try {
        EOAdaptor adaptor=dbc.adaptorContext().adaptor();
        boolean shouldHandleConnection = false;
        if(e instanceof EOGeneralAdaptorException)
          log.error(((EOGeneralAdaptorException)e).userInfo());
        else
          log.error(e);
        if (adaptor.isDroppedConnectionException(e))
          shouldHandleConnection = true;
        // FIXME: Should provide api to extend the list of bad exceptions.
        else if (e.toString().indexOf("ORA-01041")!=-1) {
          // just returning true here does not seem to do the trick. why !?!?
          log.error("ORA-01041 detecting -- forcing reconnect");
          dbc.database().handleDroppedConnection();
          shouldHandleConnection = false;
        } else {
          if(e instanceof EOGeneralAdaptorException)
            log.info(((EOGeneralAdaptorException)e).userInfo());
          throw e;
        }
          return shouldHandleConnection;
      } finally {
        reportingError.leave(dbc);
      }
    }

    /**
     * This is Kelly Hawks' fix for the missing to one relationship.
     * Delegate on EODatabaseContext that gets called when a to-one fault cannot find its data in
     * the database. The object that is returned is a cleared fault.
     * We raise here to restore the functionality that existed prior to WebObjects 4.5.
     * Whenever a fault fails for a globalID (i.e. the object is NOT found in the database), we raise
     * an {@link com.webobjects.eoaccess.EOObjectNotAvailableException EOObjectNotAvailableException}. <br>
     * If you have entities you don't really care about, you can set the system property
     * <code>er.extensions.ERXDatabaseContextDelegate.tolerantEntityPattern</code> to a regular expression
     * that will be tested against the GID entity name. If it matches, then only an error will be logged
     * but no exception will be thrown.
     *
     * @param context database context
     * @param object object that is firing the fault for a given to-one relationship
     * @param gid global id that wasn't found in the database.
     */
    public boolean databaseContextFailedToFetchObject(EODatabaseContext context, Object object, EOGlobalID gid) {
      String tolerantEntityPattern = ERXProperties.stringForKey("er.extensions.ERXDatabaseContextDelegate.tolerantEntityPattern");
      boolean raiseException = true;
      if(tolerantEntityPattern != null && (gid instanceof EOKeyGlobalID)) {
        if(((EOKeyGlobalID)gid).entityName().matches(tolerantEntityPattern)) {
          raiseException = false;
        }
      }
        if (object!=null) {
            EOEditingContext ec = ((EOEnterpriseObject)object).editingContext();

            // we need to refault the object before raising, otherwise, if the caller traps
            // the exception, it will be a successful lookup the next time a fault with the
            // same global id fires.  NOTE: refaulting in a sharedEditingContext is illegal,
            // so we specifically check for that special case.

            if (!(ec instanceof EOSharedEditingContext) && raiseException) {
                context.refaultObject((EOEnterpriseObject)object, gid, ec);
            }
        }
        String gidString;
        if(gid instanceof EOKeyGlobalID) {
            // ak: when you use 24 byte PKs, the output is unreadable otherwise
            EOKeyGlobalID kgid = (EOKeyGlobalID)gid;
            gidString = "<" +  kgid.entityName() + ": [" ;
            EOEntity entity = ERXEOAccessUtilities.entityNamed(null, kgid.entityName());
            NSArray pks = entity.primaryKeyAttributes();
            NSArray values = kgid.keyValuesArray();
            EOSQLExpressionFactory expressionFactory = context.database().adaptor().expressionFactory();
            EOSQLExpression expression = null;
            if (expressionFactory != null) {
              expression = expressionFactory.expressionForEntity(entity);
            }
            for(int i = 0; i < pks.count(); i++) {
                Object value = values.objectAtIndex(i);
                EOAttribute attribute = (EOAttribute) pks.objectAtIndex(i);
                // ak: only Postgres seems to return reasonable values here...
                String stringValue = "" + value;
                if (expression != null) {
                  stringValue = expression.formatValueForAttribute(value, attribute);
                }
                if("NULL".equals(stringValue)) {
                    stringValue = "" + value;
                }
                gidString += attribute.name() + ": \'" +  stringValue + "\'"
                + (i == pks.count() - 1 ? "" : ", ");
            }
            gidString += "] >";
           
        } else {
            gidString = gid.toString();
        }
        NSNotificationCenter.defaultCenter().postNotification(DatabaseContextFailedToFetchObject, object);
        if(raiseException) {
          throw new ObjectNotAvailableException("No " + (object!=null ? object.getClass().getName() : "N/A") + " found with globalID: " + gidString, gid);
        } else if (ERXProperties.booleanForKeyWithDefault("er.extensions.ERXDatabaseContextDelegate.logTolerantEntityNotAvailable", true)) {
          log.error("No " + (object!=null ? object.getClass().getName() : "N/A") + " found with globalID: " + gidString + "\n" + ERXUtilities.stackTrace());
        }
        return false;
    }
   
    /**
     * This delegate method is called every time a fault is fired that needs
     * to go to the database. All we have added is logging statement of the
     * debug priority. This way during runtime a developer can toggle the
     * logger priority settting on and off to see what faults are firing. Also
     * note that when using {@link ERXPatternLayout} one can set the option to
     * see full backtraces to the calling method. With this option specified
     * a developer can see exactly which methods are firing faults.
     * @param dc the databasecontext
     * @param fs the fetchspecification
     * @param channel the databasechannel
     */
    public void databaseContextDidSelectObjects(EODatabaseContext dc,
                                                EOFetchSpecification fs,
                                                EODatabaseChannel channel) {
        if (dbLog.isDebugEnabled()) {
            dbLog.debug("databaseContextDidSelectObjects " + fs, new Exception());
        }
    }

  /**
   * This delegate method first checks the arrayFaultCache if it is set before
   * trying to resolve the fault from the DB. It can be a severe performance
   * optimization depending on your setup. Also, it support batch fetching of
   * to-many relationships of EOs that were fetched at the "same" time.
   *
   * @param dbc
   * @param obj
   */

    public boolean databaseContextShouldFetchArrayFault(EODatabaseContext dbc, Object obj) {
      if(_arrayFaultCache != null) {
        _arrayFaultCache.clearFault(obj);
        if(!EOFaultHandler.isFault(obj)) {
          return false;
        }
      }
      if(autoBatchFetchSize() > 0) {
        return batchFetchToManyFault(dbc, obj);
      }
      return true;
    }

    /**
     * Batch fetches to one relationships if enabled.
     * @param dbc
     * @param obj
     * @return true if the fault should get fetched
     */
  public boolean databaseContextShouldFetchObjectFault(EODatabaseContext dbc, Object obj) {
    if(autoBatchFetchSize() > 0 && obj instanceof AutoBatchFaultingEnterpriseObject) {
      return batchFetchToOneFault(dbc, (AutoBatchFaultingEnterpriseObject)obj);
    }
    return true;
  }

    /**
     * Overridden to remove inserts and deletes of the "same" row. When you
     * delete from a join table and then re-add the same object, then the
     * order of operations would be insert, then delete and you will get
     * an error because the delete would try to also delete the newly inserted
     * row. Here we just check every insert and see if the deleted contains the same
     * object. If they do, we just skip both operations,
     * @author chello team!
     * @param dbCtxt
     * @param adaptorOps
     * @param adChannel
     */
    public NSArray databaseContextWillPerformAdaptorOperations(EODatabaseContext dbCtxt,
        NSArray adaptorOps, EOAdaptorChannel adChannel) {
      NSMutableArray result = new NSMutableArray();
      NSDictionary groupedOps = ERXArrayUtilities.arrayGroupedByKeyPath(adaptorOps, "adaptorOperator")
      Integer insertKey = ERXConstant.integerForInt(EODatabaseOperation.AdaptorInsertOperator);
      NSArray insertOps = (NSArray) groupedOps.objectForKey(insertKey);
      Integer deleteKey = ERXConstant.integerForInt(EODatabaseOperation.AdaptorDeleteOperator);
      NSArray deleteOps = (NSArray) groupedOps.objectForKey(deleteKey);
      if (insertOps!=null && deleteOps!=null) {
        NSMutableSet skippedOps = new NSMutableSet();
        for(Enumeration e = insertOps.objectEnumerator(); e.hasMoreElements();) {
          EOAdaptorOperation insertOp = (EOAdaptorOperation)e.nextElement();
          for(Enumeration e1 = deleteOps.objectEnumerator(); e1.hasMoreElements();) {
            EOAdaptorOperation deleteOp = (EOAdaptorOperation)e1.nextElement();
            if(!skippedOps.containsObject(deleteOp)) {
              if(insertOp.entity() == deleteOp.entity()) {
                if(deleteOp.qualifier().evaluateWithObject(insertOp.changedValues())) {
                  if(false) {
                    // here we remove both the delete and the
                    // insert. this might fail if we didn't lock on all rows
                    // FIXME: check the current snapshot in the database and
                    // see if it is the same as the new insert

                    skippedOps.addObject(deleteOp);
                    skippedOps.addObject(insertOp);
                  } else {
                    // here we put the delete up front, this might fail if
                    // we have cascading delete rules in the database
                    result.addObject(deleteOp);
                    skippedOps.addObject(deleteOp);
                  }
                  log.warn("Skipped: " + insertOp + "\n" + deleteOp);
                }
              }
            }
          }
        }
          for(Enumeration e = adaptorOps.objectEnumerator(); e.hasMoreElements();) {
            EOAdaptorOperation op = (EOAdaptorOperation)e.nextElement();
            if(!skippedOps.containsObject(op)) {
              result.addObject(op);
            }
          }
      } else {
        result.addObjectsFromArray(adaptorOps);
      }
      return result;
    }

    /**
     * The delegate is not reentrant, so this marks whether we are already reporting an error (most probably due to a logging pattern)
     */
    private ReentranceProtector reportingError = new ReentranceProtector();

    /**
     * The delegate is not reentrant, so this marks whether we are already batch faulting a to-many relationship.
     */
    private ReentranceProtector fetchingToMany = new ReentranceProtector();

    /**
     * The delegate is not reentrant, so this marks whether we are already batch faulting a to-one relationship.
     */
    private ReentranceProtector fetchingToOne = new ReentranceProtector();
   
    /**
     * Holds the auto batch fetch size.
     */
    public static int autoBatchFetchSize = -1;
   
    /**
     * Batching thread key
     */
   
    public static final String THREAD_KEY = "ERXBatching";

    /**
     * Interface to provide auto-magic batch fetching. For an implementation (and the hack needed for to-one handling), see {@link ERXGenericRecord}.
     */
   
    public interface AutoBatchFaultingEnterpriseObject extends EOEnterpriseObject {
      /**
       * Last time fetched.
       * @return millis of last fetch.
       */
      public long batchFaultingTimeStamp();
     
      /**
       * Updates the last time this object was fetched.
       * @param fetchTimestamp
       */
      public void setBatchFaultingTimestamp(long fetchTimestamp);
     
      /**
       * Marks the object as touched from the specified source with the specified key.
       * @param toucher
       * @param relationship name of relationship
       */
      public void touchFromBatchFaultingSource(AutoBatchFaultingEnterpriseObject toucher, String relationship);
     
      /**
       * The GID of the object that last touched us, or null.
       * @return gid of touching object.
       */
    public EOGlobalID batchFaultingSourceGlobalID();
   
    /**
     * The key through which we were last accessed.
     * @return relationship name or null
     */
      public String batchFaultingRelationshipName();
    }
   
    private class ReentranceProtector {
      public ReentranceProtector() {}
     
      private NSMutableArray<EODatabaseContext> _accessing = new NSMutableArray<EODatabaseContext>();
     
      public synchronized boolean canEnter(EODatabaseContext dbc) {
        if(_accessing.containsObject(dbc)) {
          return false;
        }
        _accessing.addObject(dbc);
        return true;
      }
     
         public synchronized void leave(EODatabaseContext dbc) {
        _accessing.removeObject(dbc);
      }
    }

    public interface BatchHandler {

    /**
     * Override this to skip fetching for the given ec and relationship. The
     * default is to skip abstract destination entities of to-many
     * relationships. You might want to exclude very large destinations or
     * sources and so on.
     *
     * @param ec
     * @param rel
     * @return int batch size (0 for not batch fetch)
     */
      public int batchSizeForRelationship(EOEditingContext ec, EORelationship rel);
    }
   
    /**
     * Refreshes the fetch timestamp for the fetched objects.
     * @param eos
     * @param timestamp
     */
  private void freshenFetchTimestamps(NSArray eos, long timestamp) {
    for(Object eo: eos) {
      if (eo instanceof AutoBatchFaultingEnterpriseObject) {
        AutoBatchFaultingEnterpriseObject ft = (AutoBatchFaultingEnterpriseObject) eo;
        ft.setBatchFaultingTimestamp(timestamp);
      }
    }
  }
 
  private void markStart(String type, EOEnterpriseObject eo, String key) {
    if(ERXStats.isTrackingStatistics()) {
      ERXStats.markStart(Group.Batching, type + "." + eo.entityName()+"."+key);
    }
  }
 
  private void markEnd(String type, EOEnterpriseObject eo, String key) {
    if(ERXStats.isTrackingStatistics()) {
      ERXStats.markEnd(Group.Batching,  type + "." + eo.entityName()+"."+key);
    }
  }
   
    /**
     * Returns the batch size for automatic batch faulting from the System property <code>er.extensions.ERXDatabaseContextDelegate.autoBatchFetchSize</code>.
     * Default is 0, meaning it's disabled.
     * @return batch size
     */
    public static int autoBatchFetchSize() {
      if(autoBatchFetchSize == -1) {
        autoBatchFetchSize = ERXProperties.intForKeyWithDefault("er.extensions.ERXDatabaseContextDelegate.autoBatchFetchSize", 0);
      }
      //if(true) return 50;
      return autoBatchFetchSize;
    }

  /**
   * Fetches the to-many fault and the faults of all other objects in the EC
   * that have the same relationship, the fetch timestamp and the same class
   * description.<br>
   *
   * @param dbc
   *            database context
   * @param obj
   *            to-many fault
   * @return true if it's still a fault.
   */
  private boolean batchFetchToManyFault(EODatabaseContext dbc, Object obj) {
    if (fetchingToMany.canEnter(dbc)) {
      try {
        EOAccessArrayFaultHandler handler = (EOAccessArrayFaultHandler) EOFaultHandler.handlerForFault(obj);
        EOEditingContext ec = handler.editingContext();
        EOEnterpriseObject source = ec.faultForGlobalID(handler.sourceGlobalID(), ec);
        if (source instanceof AutoBatchFaultingEnterpriseObject) {
          String key = handler.relationshipName();
          EOEntityClassDescription cd = (EOEntityClassDescription) source.classDescription();
          EORelationship relationship = cd.entity().relationshipNamed(key);
          if (_handler.batchSizeForRelationship(ec, relationship) > 0) {
            markStart("ToMany.Calculation", source, key);
            NSArray<EOEnterpriseObject> candidates = null;
            NSArray currentObjects = (NSArray) ERXThreadStorage.valueForKey(THREAD_KEY);
            boolean fromThreadStorage = false;
            if (currentObjects != null) {
              NSMutableArray<EOEnterpriseObject> tmpList = new NSMutableArray<EOEnterpriseObject>();
              for (Object tmpItem : currentObjects) {
                if (tmpItem instanceof AutoBatchFaultingEnterpriseObject) {
                  tmpList.add((EOEnterpriseObject) tmpItem);
                }
              }
              if (tmpList.count() > 0) {
                candidates = tmpList;
                fromThreadStorage = true;
              }
            }
            if (candidates == null) {
              candidates = ec.registeredObjects();
            }
            long timestamp = ((AutoBatchFaultingEnterpriseObject) source).batchFaultingTimeStamp();
            NSMutableArray<EOEnterpriseObject> eos = new NSMutableArray<EOEnterpriseObject>();
            NSMutableArray faults = new NSMutableArray();
            for (EOEnterpriseObject current : candidates) {
              if (current instanceof AutoBatchFaultingEnterpriseObject) {
                AutoBatchFaultingEnterpriseObject currentEO = (AutoBatchFaultingEnterpriseObject) current;
                if (currentEO.batchFaultingTimeStamp() == timestamp || fromThreadStorage) {
                  if (!EOFaultHandler.isFault(currentEO) && currentEO.classDescription() == source.classDescription()) {
                    Object fault = currentEO.storedValueForKey(key);
                    if (EOFaultHandler.isFault(fault)) {
                      faults.addObject(fault);
                      eos.addObject(currentEO);
                      if (eos.count() == autoBatchFetchSize()) {
                        break;
                      }
                    }
                  }
                }
              }
            }
            markEnd("ToMany.Calculation", source, key);
            if (eos.count() > 1) {
              markStart("ToMany.Fetching", source, key);
              doFetch(dbc, ec, relationship, eos);
              int cnt = 0;
              for(Object fault: faults) {
                if(!EOFaultHandler.isFault(fault)) {
                  NSArray array = (NSArray)fault;
                  freshenFetchTimestamps(array, timestamp);
                  cnt += array.count();
                }
              }
              markEnd("ToMany.Fetching", source, key);
              if(batchLog.isDebugEnabled()) {
                batchLog.debug("Fetched " + cnt + " to-many " + relationship.destinationEntity().name() + " from " + eos.count() " " + source.entityName() + " for " + key);
              }
              return EOFaultHandler.isFault(obj);
            }
          }
        }
      }
      finally {
        fetchingToMany.leave(dbc);
      }
    }
    return true;
  }

  /**
   * Fetches the to-one fault and the faults of all other objects in the EC
   * that have the same relationship, the fetch timestamp and the same class
   * description.<br>
   *
   * @param dbc
   *            database context
   * @param eo
   *            to-one fault
   * @return true if it's still a fault.
   */

  private synchronized boolean batchFetchToOneFault(EODatabaseContext dbc, AutoBatchFaultingEnterpriseObject eo) {
    if(fetchingToOne.canEnter(dbc)) {
      try {
        EOGlobalID sourceGID = eo.batchFaultingSourceGlobalID();
        String key = eo.batchFaultingRelationshipName();
        if(sourceGID != null && key != null) {
          EOEditingContext ec = eo.editingContext();
          AutoBatchFaultingEnterpriseObject source = (AutoBatchFaultingEnterpriseObject) ec.faultForGlobalID(sourceGID, ec);
          EOEntityClassDescription cd = (EOEntityClassDescription)source.classDescription();
          EORelationship relationship = cd.entity().relationshipNamed(key);
          if(_handler.batchSizeForRelationship(ec, relationship) > 0 && !relationship.isToMany()) {
            markStart("ToOne.Calculation", source, key);
            long timestamp = source.batchFaultingTimeStamp();
            boolean fromThreadStorage = false;
            NSMutableArray<EOEnterpriseObject> eos = new NSMutableArray<EOEnterpriseObject>();
            NSMutableSet faults = new NSMutableSet();
            NSArray<EOEnterpriseObject> candidates = null;
            NSArray currentObjects = (NSArray) ERXThreadStorage.valueForKey(THREAD_KEY);
            if (currentObjects != null) {
              NSMutableArray<EOEnterpriseObject> tmpList = new NSMutableArray<EOEnterpriseObject>();
              for (Object tmpItem : currentObjects) {
                if (tmpItem instanceof AutoBatchFaultingEnterpriseObject) {
                  tmpList.add((EOEnterpriseObject) tmpItem);
                }
              }
              if (tmpList.count() > 0) {
                candidates = tmpList;
                fromThreadStorage = true;
              }
            }
            if (candidates == null) {
              candidates = ec.registeredObjects();
            }
            for (EOEnterpriseObject current : candidates) {
              if (current instanceof AutoBatchFaultingEnterpriseObject) {
                AutoBatchFaultingEnterpriseObject currentEO = (AutoBatchFaultingEnterpriseObject) current;
                if(currentEO.batchFaultingTimeStamp() == timestamp || fromThreadStorage) {
                  if(source.classDescription() == currentEO.classDescription()) {
                    if(!EOFaultHandler.isFault(currentEO)) {
                      Object fault = currentEO.storedValueForKey(key);
                      if(EOFaultHandler.isFault(fault)) {
                        faults.addObject(fault);
                        eos.addObject(currentEO);
                        if(eos.count() == autoBatchFetchSize()) {
                          break;
                        }
                      }
                    }
                  }
                }
              }
            }

            markEnd("ToOne.Calculation", source, key);
            if(eos.count() > 1) {
              markStart("ToOne.Fetching", source, key);
              doFetch(dbc, ec, relationship, eos);
              freshenFetchTimestamps(faults.allObjects(), timestamp);
              markEnd("ToOne.Fetching", source, key);
              if(batchLog.isDebugEnabled()) {
                batchLog.debug("Fetched " + faults.count() + " to-one " + relationship.destinationEntity().name() + " from " + eos.count() " " + source.entityName() + " for " + key);
              }
              return EOFaultHandler.isFault(eo);
            }
          }
        }
      } finally {
        fetchingToOne.leave(dbc);
      }
    }
    return true;
  }

  private void doFetch(EODatabaseContext dbc, EOEditingContext ec, EORelationship relationship, NSArray eos) {
    // dbc.batchFetchRelationship(relationship, eos, ec);
    ERXEOAccessUtilities.batchFetchRelationship(dbc, relationship, eos, ec, true);
    //ERXBatchFetchUtilities.batchFetch(eos, relationship.name());
  }
 
  private BatchHandler DEFAULT = new BatchHandler() {
    public int batchSizeForRelationship(EOEditingContext ec, EORelationship relationship) {
      return autoBatchFetchSize();
    }
   
  };
 
  private BatchHandler _handler = DEFAULT;
 
  /**
   * Sets the batch handler.
   * @param handler
   */
  public void setBatchHandler(BatchHandler handler) {
    if(handler == null) {
      handler = DEFAULT;
    }
    _handler = handler;
  }
 
  public static void setCurrentBatchObjects(NSArray arr) {
    if(ERXDatabaseContextDelegate.autoBatchFetchSize() > 0) {
      if(arr == null || arr.lastObject() instanceof EOEnterpriseObject) {
        ERXThreadStorage.takeValueForKey(arr, ERXDatabaseContextDelegate.THREAD_KEY);
      }
    }
  }
}
TOP

Related Classes of er.extensions.eof.ERXDatabaseContextDelegate$ObjectNotAvailableException

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.