Package org.openbp.server.context

Source Code of org.openbp.server.context.PersistentTokenContextService$ContextIterator

/*
*   Licensed 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.openbp.server.context;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

import org.openbp.common.CollectionUtil;
import org.openbp.common.logger.LogUtil;
import org.openbp.common.util.iterator.WrappingIterator;
import org.openbp.core.model.ModelQualifier;
import org.openbp.core.model.item.ItemTypes;
import org.openbp.core.model.item.process.ProcessItem;
import org.openbp.core.model.modelmgr.ModelMgr;
import org.openbp.server.context.serializer.ContextObjectSerializerRegistry;
import org.openbp.server.engine.EngineUtil;
import org.openbp.server.persistence.PersistenceContext;
import org.openbp.server.persistence.PersistenceException;
import org.openbp.server.persistence.PersistenceQuery;
import org.openbp.server.persistence.PersistentObjectNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
* Token context service providing access to the persistent storage for context and workflow task management.
*
* @author Author: Heiko Erhardt
*/
public class PersistentTokenContextService extends TokenContextServiceBase
{
  /**
   * Isolation level none for the {@link #getExecutableContexts} method: No isolation
   * The context iterator returned by the method will contain all context that are currently in the state to be executed.
   * The iterator will not be synchronized with the database as you iterate through the context set.
   */
  public static final int ISOLATION_LEVEL_NONE = 0;

  /**
   * Isolation level none for the {@link #getExecutableContexts} method: No isolation
   * The context iterator returned by the method will contain all context that are currently in the state to be executed.
   * However, the iterator will synchronize the contexts with the database ('merge') as you iterate through the context set.
   * Contexts that appear not to be in LifecycleRequest.RESUME state will be skipped to the iterator.
   */
  public static final int ISOLATION_LEVEL_MERGE = 1;

  /**
   * Isolation level none for the {@link #getExecutableContexts} method: No isolation
   * The context iterator returned by the method will contain the first context that is currently in the state to be executed only.
   */
  public static final int ISOLATION_LEVEL_SINGLE = 2;

  /** Context execution isolation level */
  private int isolationLevel = ISOLATION_LEVEL_MERGE;

  @Autowired
  private transient ModelMgr modelMgr;

  @Autowired
  private transient ContextObjectSerializerRegistry contextObjectSerializerRegistry;

  /**
   * Default constructor.
   */
  public PersistentTokenContextService()
  {
  }

  /**
   * The shutdown method releases the persistence context.
   */
  @Override
  public void shutdown()
  {
    PersistenceContext pc = getExistingPersistenceContext();
    if (pc != null)
    {
      pc.release();
    }
    getPersistenceContextProvider().shutdown();
  }

  //////////////////////////////////////////////////
  // @@ General
  //////////////////////////////////////////////////

  /**
   * Gets the context execution isolation level.
   * @return ISOLATION_LEVEL_NONE/ISOLATION_LEVEL_MERGE/ISOLATION_LEVEL_SINGLE
   */
  public int getIsolationLevel()
  {
    return isolationLevel;
  }

  /**
   * Sets the context execution isolation level.
   * @param isolationLevel ISOLATION_LEVEL_NONE/ISOLATION_LEVEL_MERGE/ISOLATION_LEVEL_SINGLE
   */
  public void setIsolationLevel(final int isolationLevel)
  {
    this.isolationLevel = isolationLevel;
  }

  /**
   * Begins the transaction.
   */
  public void begin()
  {
    LogUtil.debug(getClass(), "Beginning transaction.");
    getPersistenceContext().beginTransaction();
  }

  /**
   * Flushes the recent changes (if supported).
   */
  public void flush()
  {
    getExistingPersistenceContext().flush();
  }

  /**
   * Commits the recent changes (if supported).
   */
  public void commit()
  {
    LogUtil.debug(getClass(), "Committing transaction.");
    getPersistenceContext().commitTransaction();
  }

  /**
   * Rolls back the recent changes (if supported).
   */
  public void rollback()
  {
    PersistenceContext pc = getPersistenceContext();
    pc.rollbackTransaction();
    pc.release();
  }

  /**
   * Clears any this token context service might have.
   */
  public void clearCache()
  {
    PersistenceContext pc = getExistingPersistenceContext();
    if (pc != null)
    {
      pc.release();
    }
  }

  //////////////////////////////////////////////////
  // @@ Context management
  //////////////////////////////////////////////////

  /**
   * Adds a context to the service.
   *
   * @param context Context to add @return The saved object
   */
  public TokenContext addContext(final TokenContext context)
  {
    LogUtil.debug(getClass(), "Creating token. [{0}]", context);
    return saveContext(context);
  }

  /**
   * Save the changes to a context.
   *
   * @param context Context to save @return The saved object
   */
  public TokenContext saveContext(final TokenContext context)
  {
    LogUtil.debug(getClass(), "Updating token. [{0}]", context);
    beforeSaveContext(context);
    return (TokenContext) getPersistenceContext().saveObject(context);
  }

  /**
   * Removes a context from the service.
   *
   * @param context Context to remove
   */
  public void deleteContext(final TokenContext context)
  {
    LogUtil.debug(getClass(), "Deleting token. [{0}]", context);
    getPersistenceContext().deleteObject(context);
  }

  /**
   * Evicts the context from the cache of the underlying persistence layer, if any.
   *
   * @param context Context to evict
   */
  public void evictContext(final TokenContext context)
  {
    getPersistenceContext().evict(context);
  }

  /**
   * Retrieves a token context by its id.
   *
   * @param id Context id @return The context or null if no such context
   * exists
   */
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public TokenContext getContextById(final Object id)
  {
    LogUtil.debug(getClass(), "Loading token having id $0.", id);
    TokenContext context = (TokenContext) getPersistenceContext().findById(id, TokenContext.class);
    context = handleContextAfterLoad(context, false, false);
    LogUtil.debug(getClass(), "Loaded token. [{0}]", context);
    return context;
  }

  /**
   * Returns an iterator of token contexts that match the given selection criteria.
   *
   * @param criteria Criteria to match @return An iterator of {@link TokenContext} objects
   * @param maxResults Maximum number of result records or 0 for all
   * @return An iterator of {@link TokenContext} objects.
   * The objects will be sorted by their priority (ascending).
   */
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public Iterator getContexts(final TokenContextCriteria criteria, int maxResults)
  {
    if (criteria != null)
    {
      LogUtil.debug(getClass(), "Performing token query (criteria $0).", criteria);
    }
    else
    {
      LogUtil.debug(getClass(), "Performing token query (all tokens).");
    }

    PersistenceContext pc = getPersistenceContext();
    PersistenceQuery query = pc.createQuery(TokenContext.class);

    if (criteria != null)
    {
      configureCriterion(query, criteria);
    }

    Iterator it = pc.runQuery(query);
    return new ContextIterator(it);
  }

  private void configureCriterion(final PersistenceQuery query, final TokenContextCriteria criteria)
  {
    if (criteria.getId() != null)
    {
      query.eq("id", criteria.getId());
    }

    if (criteria.getLifecycleState() != null)
    {
      query.eq("lifecycleState", criteria.getLifecycleState());
    }

    if (criteria.getModel() != null)
    {
      query.eq("executingModelQualifier", criteria.getModel().getQualifier().toString());
    }

    configureCriterionBase(query, criteria);
  }

  /**
   * Gets the token context objects that are ready to execute.
   *
   * @param maxResults Maximum number of result records or 0 for all
   * @return An iterator of {@link TokenContext} objects.
   * The objects will be sorted by their priority (ascending).
   */
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public Iterator getExecutableContexts(int maxResults)
  {
    LogUtil.debug(getClass(), "Performing query for executable tokens.");
    PersistenceContext pc = getPersistenceContext();

    // Construct search search criterion for executable token contexts;
    // don't cache this, depends on current session.
    PersistenceQuery query = pc.createQuery(TokenContext.class);
    query.eq("lifecycleRequest", new Integer(LifecycleRequest.RESUME));
    query.neq("lifecycleState", new Integer(LifecycleState.SELECTED));
    query.addOrdering("priority");

    int max = maxResults;
    if (getIsolationLevel() == ISOLATION_LEVEL_SINGLE)
    {
      max = 1;
    }
    if (max > 0)
    {
      query.setMaxResults(max);
    }

    // TODO Fix 2 A 'select for update' might be advisable; we won't need to refresh the context then...
    Iterator it = pc.runQuery(query);
    return new ExecutableContextIterator(it);
  }

  /**
   * Context iterator that Wraps the given iterator with an iterator class that will aggressively refresh any context and merge it with a possibly existing context
   * that is about to be executed in order to reflect the latest changes to the database.
   */
  private class ExecutableContextIterator extends ContextIterator
  {
    /**
     * Constructor.
     *
     * @param basis Contains the iterator we are based on
     */
    public ExecutableContextIterator(final Iterator basis)
    {
      super(basis);
      setMergeWithExistingContext(true);
    }

    /**
     * Retrieves current object by querying the underlying iterator.
     * @param basis The underlying iterator
     * @return The current object or null if the end of the underlying iterator has been reached.
     */
    protected Object retrieveCurrentObject(final Iterator basis)
    {
      TokenContext context = null;
      while((context = (TokenContext) super.retrieveCurrentObject(basis)) != null)
      {
        if (context.getLifecycleRequest() == LifecycleRequest.RESUME)
          return context;
      }
      return null;
    }
  }

  /**
   * Context iterator that Wraps the given iterator with an iterator class that will optionally aggressively refresh any context and/or merge it with a possibly existing context
   * that is about to be executed in order to reflect the latest changes to the database.
   */
  private class ContextIterator extends WrappingIterator
  {
    /** Refresh context */
    private boolean refreshContext = true;

    /** Merge with existing context */
    private boolean mergeWithExistingContext;

    /**
     * Constructor.
     *
     * @param basis Contains the iterator we are based on
     */
    public ContextIterator(final Iterator basis)
    {
      super(basis);
    }

    /**
     * Retrieves current object by querying the underlying iterator.
     * @param basis The underlying iterator
     * @return The current object or null if the end of the underlying iterator has been reached.
     */
    protected Object retrieveCurrentObject(final Iterator basis)
    {
      while (basis.hasNext())
      {
        TokenContext context = (TokenContext) basis.next();
        context = handleContextAfterLoad(context, isRefreshContext(), isMergeWithExistingContext());
        if (context != null)
          return context;
      }
      return null;
    }

    /**
     * Gets the refresh context flag.
     * Default: true
     * @return The refresh context flag
     */
    public boolean isRefreshContext()
    {
      return refreshContext;
    }

    /**
     * Sets the refresh context flag.
     * Default: true
     * @param refreshContext The refresh context flag
     */
    @SuppressWarnings("unused")
    public void setRefreshContext(boolean refreshContext)
    {
      this.refreshContext = refreshContext;
    }

    /**
     * Gets the merge with existing context flag.
     * @return The merge with existing context flag
     */
    public boolean isMergeWithExistingContext()
    {
      return mergeWithExistingContext;
    }

    /**
     * Sets the merge with existing context flag.
     * @param mergeWithExistingContext The merge with existing context flag
     */
    public void setMergeWithExistingContext(boolean mergeWithExistingContext)
    {
      this.mergeWithExistingContext = mergeWithExistingContext;
    }
  }

  /**
   * Gets the child contexts of the specified context.
   *
   * @param context Context
   * @return An iterator of child contexts
   */
  public Iterator getChildContexts(TokenContext context)
  {
    LogUtil.debug(getClass(), "Performing query for child tokens.");
    ArrayList children = new ArrayList();
    CollectionUtil.addAll(children, context.getChildContexts());
    return children.iterator();
  }

  /**
   * Changes the state of all matching token context objects in the context store.
   * This method can be used to fix the state of selected or running contexts after a system crash.
   *
   * @param fromLifecycleState Lifecycle state to search for
   * @param toLifecycleState New lifecycle state for matching context objects
   * @param toLifecycleRequest New lifecycle request for matching context objects
   * @param nodeId System name of the system these contexts have been assigned to or null for all context objects
   * @return The number of context objects that have been updated
   */
  public int changeContextState(int fromLifecycleState, int toLifecycleState, int toLifecycleRequest, String nodeId)
  {
    String sql = "update OPENBPTOKENCONTEXT set TC_LIFECYCLE_STATE = " + toLifecycleState + ", TC_LIFECYCLE_REQUEST = " + toLifecycleRequest +
                " where TC_LIFECYCLE_STATE = " + fromLifecycleState;
    if (nodeId != null)
    {
      sql += " and TC_NODE_ID = '" + nodeId + "'";
    }

    return getPersistenceContext().executeUpdateOrDelete(sql);
  }

  //////////////////////////////////////////////////
  // @@ Workflow task management
  //////////////////////////////////////////////////

  /**
   * Adds a workflow task to the service.
   *
   * @param workflowTask workflow task to add @return The saved object
   */
  public WorkflowTask addWorkflowTask(final WorkflowTask workflowTask)
  {
    LogUtil.debug(getClass(), "Creating workflow task $0.", workflowTask);
    return saveWorkflowTask(workflowTask);
  }

  /**
   * Save the changes to a workflow task.
   *
   * @param workflowTask workflow task to save @return The saved object
   */
  public WorkflowTask saveWorkflowTask(final WorkflowTask workflowTask)
  {
    LogUtil.debug(getClass(), "Updating workflow task $0.", workflowTask);
    return (WorkflowTask) getPersistenceContext().saveObject(workflowTask);
  }

  /**
   * Removes a workflow task from the service.
   *
   * @param workflowTask workflow task to remove
   */
  public void deleteWorkflowTask(final WorkflowTask workflowTask)
  {
    LogUtil.debug(getClass(), "Deleting workflow task $0.", workflowTask);
    getPersistenceContext().deleteObject(workflowTask);
  }

  /**
   * Returns an iterator of workflow tasks that match the given selection criteria.
   *
   * @param criteria Criteria to match @return An iterator of {@link WorkflowTask} objects
   */
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public Iterator getworkflowTasks(final WorkflowTaskCriteria criteria)
  {
    LogUtil.debug(getClass(), "Performing workflow task query $0.", criteria);
    PersistenceContext pc = getPersistenceContext();
    PersistenceQuery query = pc.createQuery(WorkflowTask.class);

    if (criteria != null)
    {
      configureCriterion(query, criteria);
    }

    Iterator it = pc.runQuery(query);
    return it;
  }

  private void configureCriterion(final PersistenceQuery query, final WorkflowTaskCriteria criteria)
  {
    if (criteria.getId() != null)
    {
      query.eq("id", criteria.getId());
    }

    if (criteria.getStatus() != null)
    {
      query.eq("status", criteria.getStatus());
    }

    if (criteria.getName() != null)
    {
      query.eq("name", criteria.getName());
    }

    if (criteria.getStepName() != null)
    {
      query.eq("stepName", criteria.getStepName());
    }

    if (criteria.getRoleId() != null)
    {
      query.eq("roleId", criteria.getRoleId());
    }

    if (criteria.getUserId() != null)
    {
      query.eq("userId", criteria.getUserId());
    }

    if (criteria.getTokenContext() != null)
    {
      query.eq("tokenContext", criteria.getTokenContext());
    }

    if (criteria.getModel() != null)
    {
      query.alias("tokenContext", "tc");
      query.eq("tc.executingModelQualifier", criteria.getModel().getQualifier().toString());
    }

    configureCriterionBase(query, criteria);
  }

  //////////////////////////////////////////////////
  // @@ Helpers
  //////////////////////////////////////////////////

  private void configureCriterionBase(final PersistenceQuery query, final CriteriaBase criteria)
  {
    for (Iterator it = criteria.getCustomCriteriaKeys(); it.hasNext();)
    {
      String key = (String) it.next();
      Object value = criteria.getCustomCriteriaValue(key);
      if (value != null)
      {
        query.eq(key, value);
      }
    }
  }

  protected TokenContext handleContextAfterLoad(TokenContext context, boolean refreshContext, boolean mergeWithExistingContext)
  {
    if (context != null)
    {
      if (refreshContext)
      {
        try
        {
          LogUtil.debug(getClass(), "Refreshing fetched token. [{0}]", context);
          context = (TokenContext) getPersistenceContext().refreshObject(context);
        }
        catch (PersistentObjectNotFoundException e)
        {
          // Object has been deleted meanwhile - continue with next one
          return null;
        }
        catch (PersistenceException e)
        {
          // We might be trying to access a new object that has not been committed to the database yet. Ignore the refesh error.
        }
      }

      if (mergeWithExistingContext && getIsolationLevel() == ISOLATION_LEVEL_MERGE)
      {
        // This will execute a 'merge', so we can be quite sure that
        // the lifecycle request comparison is up-to-date.
        context = mergeWithExistingContext(context);
      }

      postProcessContextAfterLoad(context);
    }
    return context;
  }

  protected TokenContext mergeWithExistingContext(TokenContext context)
  {
    TokenContext newContext = (TokenContext) getPersistenceContext().merge(context);
    if (context != newContext)
    {
      // The persistence store returned a new context that was read from the database.
      // We need to copy the transient attributes to the database-bound context in order not to loose
      // application-/caller-related processing information.
      Map ra = context.getRuntimeAttributes();
      if (ra != null)
      {
        for (Iterator it = ra.entrySet().iterator(); it.hasNext();)
        {
          Map.Entry entry = (Map.Entry) it.next();
          newContext.setRuntimeAttribute((String) entry.getKey(), entry.getValue());
        }
      }

      context = newContext;
    }
    return context;
  }

  /**
   * Determines the executing model and current socket from the context data and
   * ensures that the context is deserialized after being read from persistent storage.
   */
  public void postProcessContextAfterLoad(TokenContext context)
  {
    if (context.getExecutingModel() == null)
    {
      if (context.getExecutingModelQualifier() != null)
      {
        ModelQualifier mq = new ModelQualifier(context.getExecutingModelQualifier());
        context.setExecutingModel(getModelMgr().getModelByQualifier(mq));
      }
    }

    if (context.getCurrentSocket() == null)
    {
      if (context.getCurrentSocketQualifier() != null)
      {
        ModelQualifier qualifier = new  ModelQualifier(context.getCurrentSocketQualifier());
        qualifier.setItemType(ItemTypes.PROCESS);
        context.setCurrentSocket(EngineUtil.determineNodeSocketFromQualifier(qualifier, getModelMgr()));
      }
    }

    byte[] data = context.getContextData();
    if (data != null)
    {
      TokenContextUtil.fromByteArray(context, data, contextObjectSerializerRegistry);
    }

    if (context.getCallStack() != null)
    {
      for (Iterator it = context.getCallStack().iterator(); it.hasNext();)
      {
        CallStackImpl.StackItem stackItem = (CallStackImpl.StackItem) it.next();
        stackItem.resolveNodeSocket(modelMgr);

        ProcessItem process = stackItem.getNodeSocket().getProcess();
        EngineUtil.createProcessVariables(process, context);
      }
    }
  }

  /**
   * Updates the serialzied context data with actual context values.
   */
  protected void beforeSaveContext(TokenContext context)
  {
    context.setContextData(TokenContextUtil.toByteArray(context, contextObjectSerializerRegistry));
  }

  private PersistenceContext getPersistenceContext()
  {
    return getPersistenceContextProvider().obtainPersistenceContext();
  }

  private PersistenceContext getExistingPersistenceContext()
  {
    return getPersistenceContextProvider().obtainExistingPersistenceContext();
  }
}
TOP

Related Classes of org.openbp.server.context.PersistentTokenContextService$ContextIterator

TOP
Copyright © 2015 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.