Package nexj.core.meta

Source Code of nexj.core.meta.Event

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import nexj.core.meta.workflow.Handler;
import nexj.core.runtime.Context;
import nexj.core.runtime.InvocationContext;
import nexj.core.runtime.SecurityViolationException;
import nexj.core.scripting.Compiler;
import nexj.core.scripting.CompilerException;
import nexj.core.scripting.ConstPair;
import nexj.core.scripting.Function;
import nexj.core.scripting.Intrinsic;
import nexj.core.scripting.JavaAction;
import nexj.core.scripting.Machine;
import nexj.core.scripting.Pair;
import nexj.core.scripting.Symbol;
import nexj.core.util.EmptyIterator;
import nexj.core.util.ExceptionHolder;
import nexj.core.util.HashTab;
import nexj.core.util.IdentityHashTab;
import nexj.core.util.Logger;
import nexj.core.util.Lookup;
import nexj.core.util.Named;
import nexj.core.util.TextPosition;
import nexj.core.util.UncheckedException;

/**
* Event handler or method metadata. When an event is triggered or method
* is invoked, a series of actions associated with it is executed.
*
* The following events are generated automatically by the framework:
*
* new(class, values) - invoked to check the creation
*    privileges and initialize the instance with default values.
*
* create(obj) - invoked after the object has been created.
*
* read(class, attributes, where, orderBy, maxCount, offset, xlock) -
*    invoked to read an object collection.
*
* load(obj, attributes) - invoked to read or compute a field
*    on-demand.
*
* update(obj) - invoked to update an instance.
*
* delete(obj) - invoked to delete an instance.
*
* lock(obj) - invoked to lock an instance in the persistent storage.
*/
public final class Event extends Member
{
   // constants

   // transaction constants

   /**
    * Use the default or inherited transaction attribute.
    */
   public final static byte TX_DEFAULT = 0;

   /**
    * Join a transaction, if available, otherwise run transactionless.
    */
   public final static byte TX_SUPPORTED = 1;

   /**
    * Join a transaction, if available, otherwise start a new transaction.
    * Do not auto-commit on return.
    */
   public final static byte TX_REQUIRED = 2;

   /**
    * Always suspend the current transaction, if any, and start a new one.
    * Auto-commit on return.
    */
   public final static byte TX_NEW = 3;

   /**
    * Always suspend the current transaction, if any.
    * Restore on return.
    */
   public final static byte TX_NONE = 4;
  
   /**
    * Fail if there is no transaction.
    */
   public final static byte TX_MANDATORY = 5;
  
   /**
    * Fail if there is a transaction.
    */
   public final static byte TX_UNSUPPORTED = 6;

   // other constants

   /**
    * Empty action body, returns null.
    */
   public final static Object EMPTY_BODY = Pair.quote(null);

   /**
    * Closure that returns null.
    */
   protected final static Object EMPTY_CLOSURE = Pair.list(Symbol.LAMBDA, null, EMPTY_BODY);

   /**
    * Empty Symbol[].
    */
   protected final static Symbol[] EMPTY_SYMBOL_ARRAY = new Symbol[0];

   /**
    * The flow action name.
    */
   public final static String FLOW_ACTION_NAME = "workflow";

   /**
    * The create flow action name.
    */
   public final static String CREATE_FLOW_ACTION_NAME = "create-workflow-";

   /**
    * The trigger flow action name.
    */
   public final static String TRIGGER_FLOW_ACTION_NAME = "trigger-workflow";

   /**
    * The delete flow action name.
    */
   public final static String DELETE_FLOW_ACTION_NAME = "delete-workflow";

   /**
    * The add dependency change action name.
    */
   public final static String SYNC_DEPENDENCY_ACTION_NAME = "add-dependency-change";
  
   /**
    * The symbol that names the addDependencyChange event.
    */
   public final static Symbol SYNC_DEPENDENCY_EVENT_SYMBOL = Symbol.define("addDependencyChange");

   /**
    * The symbol that names the DependencySynchronizationCommand class.
    */
   public final static Symbol DEPENDENCY_SYNC_COMMAND = Symbol.define("nexj.core.integration.sync.DependencySynchronizationCommand");

   // attributes

   /**
    * The variable argument number flag.
    */
   protected boolean m_bVarArg;

   /**
    * True if this event should be invoked in a transient context.
    */
   protected boolean m_bTransient;

   /**
    * The auditing flag.
    */
   protected byte m_nAudited = -1;

   /**
    * The transaction mode, one of Metaclass.TX_* constants.
    */
   protected byte m_nTransactionMode;

   // associations

   /**
    * The action map.
    */
   protected Lookup m_actionMap = new HashTab(4); // of type Action[String]

   /**
    * The list of actions associated with the event.
    */
   protected List m_actionList = new ArrayList(4); // of type Action
  
   /**
    * The event argument map.
    */
   protected Lookup m_argMap = new HashTab(4); // of type Argument[String]
  
   /**
    * The event argument list.
    */
   protected List m_argList = new ArrayList(4); // of type Argument

   /**
    * The event result type. (null == unknown result type)
    * The name of the object is ignored and should be null.
    */
   protected Argument m_result;

   /**
    * The selector corresponding to the event.
    */
   protected Selector m_selector;

   /**
    * The local variable map.
    * Maps a variable symbol to a Scheme expression: Object[Symbol].
    */
   protected Lookup m_varMap; // of type Object[Symbol]

   /**
    * The flow association symbol path list: Pair[].
    * These associations indicate the instance to query and
    * invoke a flow handler when this event is triggered.
    */
   protected List m_flowAssocList;

   /**
    * The flow starter collection: Handler[].
    */
   protected List m_flowStarterList;

   /**
    * The invocation privilege.
    */
   protected PrimitivePrivilege m_privilege;

   /**
    * The event function.
    */
   protected Function m_function;

   // constructor

   /**
    * Constructs an event with a given name.
    * @param sName The name of the event.
    */
   public Event(String sName)
   {
      super(sName);
   }

   // operations

   /**
    * @see nexj.core.meta.Member#isAttribute()
    */
   public boolean isAttribute()
   {
      return false;
   }

   /**
    * Sets the variable argument number flag.
    * @param bVarArg The variable argument number flag to set.
    */
   public void setVarArg(boolean bVarArg)
   {
      verifyNotReadOnly();
      m_bVarArg = bVarArg;
   }

   /**
    * @return The variable argument number flag.
    */
   public boolean isVarArg()
   {
      return m_bVarArg;
   }
  
   /**
    * Adds a new event action to the event.
    * @param action The event action to add.
    * @throws MetadataException if an action with the same name already exists.
    */
   public void addAction(Action action)
   {
      verifyNotReadOnly();

      Object oldAction = m_actionMap.put(action.getName(), action);

      if (oldAction != null)
      {
         m_actionMap.put(action.getName(), oldAction);

         throw new MetadataException("err.meta.actionDup",
            new Object[] {action.getName(), getName(), getMetaclass().getName()});
      }

      m_actionList.add(action);
      action.setEvent(this);
   }

   /**
    * Adds an action preceding a specified action (if it exists).
    * @param action The action to add.
    * @param sNextName The name of the next action.
    */
   public void addAction(Action action, String sNextName)
   {
      action.setNextAction(findAction(sNextName));
      addAction(action);
   }
  
   /**
    * Gets an event action by name.
    * @param sName The action name.
    * @return The action object.
    * @throws MetadataLookupException if the action does not exist.
    */
   public Action getAction(String sName)
   {
      Action action = (Action)m_actionMap.get(sName);

      if (action != null)
      {
         return action;
      }

      throw new MetadataLookupException("err.meta.actionLookup", sName, this);
   }

   /**
    * Finds an event action by name.
    * @param sName The action name.
    * @return The action object, or null if not found.
    */
   public Action findAction(String sName)
   {
      return (Action)m_actionMap.get(sName);
   }

   /**
    * Gets an event action by ordinal number.
    * @param nOrdinal The event action ordinal number (0-based).
    * @return The event action object.
    */
   public Action getAction(int nOrdinal)
   {
      return (Action)m_actionList.get(nOrdinal);
   }

   /**
    * @return The event action count.
    */
   public int getActionCount()
   {
      return m_actionList.size();
   }

   /**
    * @return An iterator for the contained event action objects.
    */
   public Iterator getActionIterator()
   {
      return m_actionList.iterator();
   }

   /**
    * Rotates forward the action list by a given number of elements.
    * @param nCount The action count (distance) by which to rotate.
    */
   protected void rotateActions(int nCount)
   {
      Collections.rotate(m_actionList, nCount);
   }

   /**
    * Adds a new event argument to the event.
    * @param arg The event argument to add.
    * @throws MetadataException if an argument with the same name already exists.
    */
   public void addArgument(Argument arg)
   {
      verifyNotReadOnly();

      Object oldArg = m_argMap.put(arg.getName(), arg);

      if (oldArg != null)
      {
         m_argMap.put(arg.getName(), oldArg);

         throw new MetadataException("err.meta.argDup",
            new Object[] {arg.getName(), getName(), m_metaclass.getName()});
      }

      arg.setOrdinal(m_argList.size());
      arg.setEvent(this);
      m_argList.add(arg);
   }

   /**
    * Finds an argument by name.
    * @param sName The argument name.
    * @return The argument object, or null if not found.
    */
   public Argument findArgument(String sName)
   {
      return (Argument)m_argMap.get(sName);
   }

   /**
    * Gets an argument by name.
    * @param sName The argument name.
    * @return The argument object.
    * @throws MetadataLookupException if the argument does not exist.
    */
   public Argument getArgument(String sName)
   {
      Argument arg = (Argument)m_argMap.get(sName);

      if (arg != null)
      {
         return arg;
      }

      throw new MetadataLookupException("err.meta.argLookup", sName, this);
   }

   /**
    * Gets a event argument by ordinal number.
    * @param nOrdinal The event argument ordinal number (0-based).
    * @return The event argument object.
    */
   public Argument getArgument(int nOrdinal)
   {
      return (Argument)m_argList.get(nOrdinal);
   }

   /**
    * @return The event argument count.
    */
   public int getArgumentCount()
   {
      return m_argList.size();
   }

   /**
    * @return An iterator for the contained event argument objects.
    */
   public Iterator getArgumentIterator()
   {
      return m_argList.iterator();
   }

   /**
    * Adds a local variable to the event.
    * @param symbol The variable symbol.
    * @param init The initializer.
    * @throws MetadataException if a variable with the same name already exists and bSstrict is true.
    */
   public void addVariable(Symbol symbol, Object init)
   {
      verifyNotReadOnly();
     
      if (m_varMap == null)
      {
         m_varMap = new HashTab();
      }

      if (init == null)
      {
         init = EMPTY_BODY;
      }

      Object oldInit = m_varMap.put(symbol, init);

      if (oldInit != null || findArgument(symbol.getName()) != null)
      {
         m_varMap.put(symbol, oldInit);

         throw new MetadataException("err.meta.varDup",
            new Object[] {symbol, getName(), m_metaclass.getName()});
      }
   }

   /**
    * Adds the local variables specified in a list to the event.
    * The list should contain variable symbols and/or (symbol expr) pairs.
    * @param list The list of local variables.
    * @throws MetadataException if the syntax is incorrect.
    */
   public void addVariables(Pair list)
   {
      for (; list != null; list = list.getNext())
      {
         if (list.getHead() instanceof Symbol)
         {
            addVariable((Symbol)list.getHead(), null);
         }
         else if (list.getHead() instanceof Pair)
         {
            Pair pair = (Pair)list.getHead();
           
            if (pair.getHead() instanceof Symbol &&
                  pair.getTail() instanceof Pair &&
                  ((Pair)pair.getTail()).getTail() == null)
            {
               addVariable((Symbol)pair.getHead(), ((Pair)pair.getTail()).getHead());
            }
            else
            {
               throw new MetadataException("err.meta.invalidVarList", new Object[]{getName(), m_metaclass.getName()});
            }
         }
         else
         {
            throw new MetadataException("err.meta.invalidVarList", new Object[]{getName(), m_metaclass.getName()});
         }
      }
   }

   /**
    * Inherits the attribute and result type definitions from the specified event.
    * The most restrictive definition is chosen.
    * @param event The event from which to inherit the arguments.
    */
   protected void inheritEventTypes(Event event)
   {
      Argument result = getResult();
      Argument template = event.getResult();

      if (result == null && template != null)
      {
         setResult((Argument)template.clone());
      }
      else if (template != null)
      {
         result.inherit(template);
      }

      for (int i = 0, nCount = getArgumentCount(); i < nCount; ++i)
      {
         Argument arg = getArgument(i);

         template = event.findArgument(arg.getName());

         if (template != null)
         {
            arg.inherit(template);
         }
      }
   }

   /**
    * Inherits the local variables from the specified event.
    * @param event The event from which to inherit the variables.
    */
   public void inheritVariables(Event event)
   {
      if (event.m_varMap != null)
      {
         if (m_varMap == null)
         {
            m_varMap = new HashTab(event.m_varMap.size());
         }

         for (Lookup.Iterator itr = event.m_varMap.iterator(); itr.hasNext();)
         {
            Object symbol = itr.next();

            if (!m_varMap.contains(symbol))
            {
               m_varMap.put(symbol, itr.getValue());
            }
         }
      }
   }

   /**
    * Sets the event selector.
    * @param selector The event selector to set.
    */
   public void setSelector(Selector selector)
   {
      verifyNotReadOnly();
      m_selector = selector;
   }

   /**
    * @return The event selector.
    */
   public Selector getSelector()
   {
      return m_selector;
   }

   /**
    * Sets the invocation privilege.
    * @param privilege The invocation privilege to set.
    */
   public void setPrivilege(PrimitivePrivilege privilege)
   {
      verifyNotReadOnly();
      m_privilege = privilege;
   }

   /**
    * @return The invocation privilege.
    */
   public PrimitivePrivilege getPrivilege()
   {
      return m_privilege;
   }

   /**
    * @see Event#m_result
    */
   public Argument getResult()
   {
      return m_result;
   }

   /**
    * @type The event result type.
    * @see Event#m_result
    */
   public void setResult(Argument result)
   {
      verifyNotReadOnly();
      m_result = result;
   }

   /**
    * Sets the transaction mode.
    * @param nTransactionMode The transaction mode to set, one of the TX_* constants.
    */
   public void setTransactionMode(byte nTransactionMode)
   {
      verifyNotReadOnly();
      m_nTransactionMode = nTransactionMode;
   }

   /**
    * @return The transaction mode, one of the TX_* constants.
    */
   public byte getTransactionMode()
   {
      return m_nTransactionMode;
   }

   /**
    * Gets whether the event should be invoked in a transient context.
    * @param bTransient The transient flag determined from the Request.
    * @return True if the event should be invoked in a transient context; false otherwise.
    */
   public boolean isTransient(boolean bTransient)
   {
      return bTransient && m_bTransient;
   }

   /**
    * Sets the auditing flag.
    * @param audited The auditing flag to set.
    */
   public void setAudited(Boolean audited)
   {
      verifyNotReadOnly();

      if (audited == null)
      {
         m_nAudited = -1;
      }
      else
      {
         setAudited(audited.booleanValue());
      }
   }

   /**
    * Sets the auditing flag.
    * @param bAudited The auditing flag to set.
    */
   public void setAudited(boolean bAudited)
   {
      verifyNotReadOnly();
      m_nAudited = (bAudited) ? (byte)1 : (byte)0;
   }

   /**
    * @return The auditing flag.
    */
   public boolean isAudited()
   {
      return m_nAudited > 0;
   }

   /**
    * @return The auditing flag.
    */
   public Boolean getAudited()
   {
      switch (m_nAudited)
      {
         case 0:
            return Boolean.FALSE;

         case 1:
            return Boolean.TRUE;

         default:
            return null;
      }
   }

   /**
    * Sets the event function.
    * @param function The event function to set.
    */
   public void setFunction(Function function)
   {
      verifyNotReadOnly();
      m_function = function;
   }

   /**
    * @return The event function.
    */
   public Function getFunction()
   {
      return m_function;
   }

   /**
    * Adds a new flow starter to the event.
    * @param flowStarter The flow starter to add.
    */
   public void addFlowStarter(Handler flowStarter)
   {
      verifyNotReadOnly();
     
      if (m_flowStarterList == null)
      {
         m_flowStarterList = new ArrayList(4);
      }

      m_flowStarterList.add(flowStarter);
   }

   /**
    * Gets a flow starter by ordinal number.
    * @param nOrdinal The flow starter ordinal number (0-based).
    * @return The flow starter object.
    */
   public Handler getFlowStarter(int nOrdinal)
   {
      return (Handler)m_flowStarterList.get(nOrdinal);
   }

   /**
    * @return The flow starter count.
    */
   public int getFlowStarterCount()
   {
      if (m_flowStarterList == null)
      {
         return 0;
      }
     
      return m_flowStarterList.size();
   }

   /**
    * @return An iterator for the contained flow starter objects.
    */
   public Iterator getFlowStarterIterator()
   {
      if (m_flowStarterList == null)
      {
         return EmptyIterator.getInstance();
      }

      return m_flowStarterList.iterator();
   }

   /**
    * Adds a flow association.
    * @param assoc The association patch to add. Can be null to indicate the event instance.
    */
   public void addFlowAssoc(Pair assoc)
   {
      if (m_flowAssocList == null)
      {
         m_flowAssocList = new ArrayList(4);
      }

      if (!m_flowAssocList.contains(assoc))
      {
         m_flowAssocList.add(assoc);
      }
   }
  
   /**
    * Gets a flow association by ordinal number.
    * @param nOrdinal The flow association ordinal number.
    * @return The association path.
    */
   public Pair getFlowAssoc(int nOrdinal)
   {
      return (Pair)m_flowAssocList.get(nOrdinal);
   }

   /**
    * @return The flow association count.
    */
   public int getFlowAssocCount()
   {
      if (m_flowAssocList == null)
      {
         return 0;
      }
     
      return m_flowAssocList.size();
   }

   /**
    * @return The flow association iterator.
    */
   public Iterator getFlowAssocIterator()
   {
      if (m_flowAssocList == null)
      {
         return EmptyIterator.getInstance();
      }
     
      return m_flowAssocList.iterator();
   }

   /**
    * Generates an action for creating a flow.
    * Must be called first on the base event, then on its subclasses and so on.
    */
   public void generateCreateFlowAction()
   {
      verifyNotReadOnly();

      if (m_metaclass.getBase() != null)
      {
         Event baseEvent = m_metaclass.getBase().findEvent(getName(), getArgumentCount());

         if (baseEvent != null)
         {
            for (int i = 0, nCount = baseEvent.getFlowStarterCount(); i < nCount; ++i)
            {
               addFlowStarter(baseEvent.getFlowStarter(i));
            }
         }
      }

      for (int nFlow = 0, nFlowCount = getFlowStarterCount(); nFlow < nFlowCount; ++nFlow)
      {
         Handler flow = getFlowStarter(nFlow);
         Action action = new Action(CREATE_FLOW_ACTION_NAME + ((Named)flow).getName());

         action.setType(Action.BEFORE);
         action.setCondition(flow.getCondition());
        
         Pair assoc = null;

         for (int i = flow.getAssociationCount() - 1; i >= 0; --i)
         {
            assoc = new Pair(flow.getAssociation(i).getReverse().getSymbol(), assoc);
         }

         action.setBody(
            Pair.list(
               Pair.list(
                  Pair.quote(m_metaclass.getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME)),
                  Pair.quote(Symbol.START),
                  new Pair(Symbol.AT, assoc),
                  ((Named)flow).getName(),
                  Boolean.TRUE
               )
            )
         );
        
         addAction(action, FLOW_ACTION_NAME);
         m_declarator = m_metaclass;
      }
   }

   /**
    * Generates an action for triggering a flow state change.
    * Must be called first on the base event, then on its subclasses and so on.
    */
   public void generateTriggerFlowAction()
   {
      verifyNotReadOnly();

      if (m_metaclass.getBase() != null)
      {
         Event baseEvent = m_metaclass.getBase().findEvent(getName(), getArgumentCount());
        
         if (baseEvent != null)
         {
            Action action = baseEvent.findAction(TRIGGER_FLOW_ACTION_NAME);
           
            if (action != null)
            {
               if (getFlowAssocCount() == 0)
               {
                  m_flowAssocList = baseEvent.m_flowAssocList;
                  action = (Action)action.clone();
                  addAction(action, FLOW_ACTION_NAME);
                 
                  return;
               }
               else
               {
                  for (int i = 0, nCount = baseEvent.getFlowAssocCount(); i < nCount; ++i)
                  {
                     addFlowAssoc(baseEvent.getFlowAssoc(i));
                  }
               }
            }
         }
      }

      if (getFlowAssocCount() == 0)
      {
         return;
      }

      Action action = new Action(TRIGGER_FLOW_ACTION_NAME);

      action.setType(Action.BEFORE);
      action.setCondition(Boolean.TRUE);
     
      Pair body = null;
     
      for (int i = getFlowAssocCount() - 1; i >= 0; --i)
      {
         Pair assoc = getFlowAssoc(i);

         // (SysWorkflow'trigger (@ <reverse-assoc>) this '<event> <args>)
         body = new Pair(
            new Pair(
               Pair.quote(m_metaclass.getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME)),
               new Pair(
                  Pair.quote(Symbol.TRIGGER),
                  new Pair(
                     new Pair(Symbol.AT, assoc),
                     new Pair(
                        Symbol.THIS,
                        new Pair(Pair.quote(assoc),
                           new Pair(
                              Pair.quote(this),
                              getArguments(false)
                              )
                           )
                        )
                     )
                  )
               ),
            body);
      }

      action.setBody(body);
      addAction(action, FLOW_ACTION_NAME);
      m_declarator = m_metaclass;
   }

   /**
    * Generates an action for deleting all the flows associated with the instance.
    * @return True if the action has been generated, false if already exists.
    */
   public boolean generateDeleteFlowAction()
   {
      Action action = findAction(DELETE_FLOW_ACTION_NAME);
     
      if (action != null)
      {
         return false;
      }
     
      action = new Action(DELETE_FLOW_ACTION_NAME);
      action.setType(Action.AFTER);
      action.setCondition(Boolean.TRUE);
      action.setBody(
         Pair.list(
            Pair.list(
               Pair.quote(m_metaclass.getMetadata().getMetaclass(Metadata.WORKFLOW_CLASS_NAME)),
               Pair.quote(Symbol.DELETE),
               Symbol.THIS
               )
            )
         );

      addAction(action, FLOW_ACTION_NAME);
      m_declarator = m_metaclass;

      return true;
   }

   /**
    * Generates an action for adding changes that affects synchronization to the unit of work transient cache, to be
    * processed by the synchronization engine.
    * @param pair A Scheme list of Pairs, in which head is a name of the metaclass that affects synchronization, and
    * tail is a list of attributes of this metaclass, affecting synchronization.
    */
   public void generateSyncDependencyAction(Pair attributes)
   {
      verifyNotReadOnly();

      Action action = findAction(SYNC_DEPENDENCY_ACTION_NAME);

      if (action != null)
      {
         return;
      }

      action = new Action(SYNC_DEPENDENCY_ACTION_NAME);
      action.setType(Action.BEFORE);
      action.setCondition(Boolean.TRUE);

      // (DEPENDENCY_SYNC_COMMAND'addDependencyChange this '<attributes>)
      Pair body = Pair.list(Pair.list(DEPENDENCY_SYNC_COMMAND, Pair.quote(SYNC_DEPENDENCY_EVENT_SYMBOL), Symbol.THIS,
         Pair.quote(attributes)));

      action.setBody(body);
      addAction(action);
      m_declarator = m_metaclass;
   }

   /**
    * Determines if this event is derivation compatible with a base event.
    * @param base The base event.
    * @return True if it is compatible.
    */
   public boolean isCompatibleWith(Event base)
   {
      int nCount = getArgumentCount();
     
      if (m_bStatic != base.m_bStatic ||
         m_bVarArg != base.m_bVarArg ||
         nCount != base.getArgumentCount())
      {
         return false;
      }

      if (getResult() != null && !getResult().isCompatibleWith(base.getResult()))
      {
         return false; // results not compatible
      }

      for (int i = 0; i < nCount; ++i)
      {
         Argument derivedArg = getArgument(i);
         Argument baseArg = base.getArgument(i);

         if (derivedArg.getSymbol() != baseArg.getSymbol() || !derivedArg.isCompatibleWith(baseArg))
         {
            return false;
         }
      }

      return true;
   }

   /**
    * @see java.lang.Object#clone()
    */
   public Object clone()
   {
      Event event = (Event)super.clone();

      if (event.getResult() != null)
      {
         event.setResult((Argument)event.getResult().clone());
      }

      event.m_argList = new ArrayList(getArgumentCount());
      event.m_argMap = new HashTab(getArgumentCount());

      for (int i = 0; i < getArgumentCount(); ++i)
      {
         event.addArgument((Argument)getArgument(i).clone());
      }

      event.m_actionList = new ArrayList(getActionCount());
      event.m_actionMap = new HashTab(getActionCount());

      for (int i = 0; i < getActionCount(); ++i)
      {
         event.addAction((Action)getAction(i).clone());
      }

      if (m_varMap != null)
      {
         event.m_varMap = (Lookup)((HashTab)m_varMap).clone();
      }

      return event;
   }

   /**
    * Resolves the action references against the aspects and the base class.
    */
   public void resolveActions()
   {
      verifyNotReadOnly();

      for (int nAction = 0, nActionCount = m_actionList.size();
         nAction < nActionCount; ++nAction)
      {
         Action action = (Action)m_actionList.get(nAction);

         if (action.getNextAction() == null || action.getNextAction().getEvent() != this)
         {
            Action baseAction = null;

            for (int nAspect = 0, nAspectCount = m_metaclass.getDirectAspectCount();
               nAspect < nAspectCount; ++nAspect)
            {
               Event baseEvent = ((ClassAspect)m_metaclass.getAspect(nAspect))
                  .findEvent(m_sName, getArgumentCount());

               if (baseEvent != null)
               {
                  baseAction = baseEvent.findAction(action.getName());

                  if (baseAction != null)
                  {
                     break;
                  }
               }
            }

            if (baseAction == null && m_rootDeclarator != m_metaclass)
            {
               if (m_metaclass.getBase() != null)
               {
                  Event baseEvent = m_metaclass.getBase().findEvent(m_sName, getArgumentCount());

                  if (baseEvent != null)
                  {
                     baseAction = baseEvent.findAction(action.getName());
                  }
               }
            }

            if (baseAction != null && baseAction.getNextAction() != null)
            {
               Action nextAction = findAction(baseAction.getNextAction().getName());

               if (nextAction != null)
               {
                  action.setNextAction(nextAction);
               }
            }
         }
      }
   }

   /**
    * Sorts the actions based on their precedence established by
    * getNextAction() and their declaration order.
    * @throws MetadataException if a cycle has been discovered in the action graph.
    */
   public void sortActions()
   {
      verifyNotReadOnly();
     
      try
      {
         int nActionCount = m_actionList.size();

         if (nActionCount == 0)
         {
            return;
         }

         // Assign ordinal numbers
         for (int i = 0; i < nActionCount; ++i)
         {
            ((Action)m_actionList.get(i)).m_nOrdinal = i;
         }

         Action main = findAction(Action.MAIN_ACTION_NAME);

         for (int nSortCount = 0; nSortCount < nActionCount; ++nSortCount)
         {
            boolean bModified = false;

            for (int i = 0; i < nActionCount; ++i)
            {
               Action action = (Action)m_actionList.get(i);

               if (action.getNextAction() != null && action.getNextAction().m_nOrdinal <= i)
               {
                  for (int k = action.getNextAction().m_nOrdinal; k < i; ++k)
                  {
                     Action tmp = (Action)m_actionList.get(k + 1);

                     tmp.m_nOrdinal = k;
                     m_actionList.set(k, tmp);
                  }

                  action.getNextAction().m_nOrdinal = i;
                  m_actionList.set(i, action.getNextAction());

                  bModified = true;
               }
            }

            if (!bModified)
            {
               if (main != null)
               {
                  for (int k = main.m_nOrdinal; k < nActionCount - 1; ++k)
                  {
                     m_actionList.set(k, m_actionList.get(k + 1));
                  }

                  m_actionList.set(nActionCount - 1, main);
               }

               for (int i = 0; i < nActionCount - 1; ++i)
               {
                  Action action = (Action)m_actionList.get(i);

                  if (action.getNextAction() == null)
                  {
                     action.setNextAction((Action)m_actionList.get(i + 1));
                  }

                  // Verify that there are no gaps between the grouped actions
                  if (action.getGroupName() != null)
                  {
                     boolean bGap = false;

                     for (int k = i - 1; k >= 0; --k)
                     {
                        Action prev = (Action)m_actionList.get(k);

                        if (action.getGroupName().equals(prev.getGroupName()))
                        {
                           if (prev.getType() != action.getType())
                           {
                              MetadataValidationException e = new MetadataValidationException("err.meta.actionGroupTypeMismatch",
                                 new Object[]{action.getName(), prev.getName(), action.getGroupName()});

                              action.setProperties(e);

                              throw e;
                           }

                           if (bGap)
                           {
                              MetadataValidationException e = new MetadataValidationException("err.meta.actionGroupGap",
                                 new Object[]{action.getName(), action.getGroupName()});

                              action.setProperties(e);

                              throw e;
                           }

                           break;
                        }
                        else
                        {
                           if (prev.getType() != ((action.getType() == Action.BEFORE) ? Action.AFTER : Action.BEFORE))
                           {
                              bGap = true;
                           }
                        }
                     }
                  }
               }

               return;
            }
         }

         throw new MetadataException("err.meta.actionCycle",
            new Object[]{getName(), m_metaclass.getName()});
      }
      catch (MetadataException e)
      {
         MetadataValidationException x = new MetadataValidationException(e);

         setProperties(x);

         throw x;
      }
   }

   /**
    * @return The base (declared) event. Never returns null.
    */
   public Event getBase()
   {
      if (m_declarator == m_metaclass)
      {
         return this;
      }

      return (Event)m_declarator.getSelector(getName()).getMember(getArgumentCount());
   }

   /**
    * @return The root declared event.
    */
   public Event getRoot()
   {
      if (m_rootDeclarator == m_metaclass)
      {
         return this;
      }
     
      return (Event)m_rootDeclarator.getSelector(getName()).getMember(getArgumentCount());
   }
  
   /**
    * Compiles the ECA code.
    * @param machine The VM for the compilation.
    */
   public void compile(Machine machine)
   {
      assert m_function == null;

      if (m_declarator != m_metaclass)
      {
         m_function = getBase().getFunction();

         assert m_function != null;
      }
      else
      {
         int nActionCount = getActionCount();
         final int nArgOffset = 7; // number of non-vararg arguments
         Object[] args = new Object[Math.max(nActionCount, 1) + nArgOffset];
         Lookup posMap = new IdentityHashTab(64);
         Lookup urlMap = new IdentityHashTab(64);
         String sEventURL = getURL(m_metaclass);
         String sURLPrefix = sEventURL + "$";

         // When updating the event generator invocation code,
         // also update the plugin action compilation validation
         args[0] = this;
         args[1] = getArguments(true);
         args[2] = getVariables();
         args[3] = getPrivilege();

         if (m_accessAttribute == null)
         {
            args[4] = null;
         }
         else if (m_bStatic)
         {
            if (m_accessAttribute.isStatic())
            {
               args[4] = Primitive.createInteger(m_accessAttribute.getOrdinal());
            }
            else
            {
               args[4] = null;
            }
         }
         else
         {
            args[4] = Primitive.createInteger(
               (m_accessAttribute.isStatic()) ?
                  -1 -m_accessAttribute.getOrdinal() :
                  m_accessAttribute.getOrdinal());
         }

         args[5] = Boolean.valueOf(getArgumentCount() == 0 && "delete".equals(m_sName));

         byte nTransactionMode = ((getSymbol() == Symbol.CREATE || getSymbol() == Symbol.UPDATE) &&
            getArgumentCount() == 0) ? TX_SUPPORTED : getTransactionMode();

         args[6] = Primitive.createInteger(nTransactionMode);
         m_bTransient = (nTransactionMode != TX_SUPPORTED);

         for (int i = 0; i < nActionCount; ++i)
         {
            Action action = getAction(i);
            Object condition = action.getCondition();
            String sURL;
            Object body;

            if (action.getDeclarator() != null)
            {
               sURL = getURL(action.getDeclarator()) + "$" + action.getFullName();
            }
            else
            {
               sURL = sURLPrefix + action.getFullName();
            }

            if (action.getMethod() == null)
            {
               body = action.getBody();

               if (body == null)
               {
                  body = EMPTY_BODY;
               }
            }
            else
            {
               body = Pair.list(Pair.list(Pair.quote(new JavaAction(action)),
                  (action.getType() == Action.AROUND) ? Symbol.CALL_NEXT : EMPTY_CLOSURE));
            }

            Lookup map = action.getTextPositionMap();

            if (map != null)
            {
               for (Lookup.Iterator itr = map.iterator(); itr.hasNext();)
               {
                  itr.next();
                  posMap.put(itr.getKey(), itr.getValue());
                  urlMap.put(itr.getValue(), sURL);
               }
            }

            if (condition != null)
            {
               Compiler.setPosURLs(condition, sURL + "$condition", posMap, urlMap);
            }

            Boolean grouped = Boolean.FALSE;

            // Set grouped for all but the first action in a group
            if (action.getGroupName() != null)
            {
               if (action.getType() == Action.BEFORE)
               {
                  for (int k = i - 1; k >= 0; --k)
                  {
                     if (action.getGroupName().equals(getAction(k).getGroupName()))
                     {
                        grouped = Boolean.TRUE;
                        break;
                     }
                  }
               }
               else
               {
                  for (int k = i + 1; k < nActionCount; ++k)
                  {
                     if (action.getGroupName().equals(getAction(k).getGroupName()))
                     {
                        grouped = Boolean.TRUE;
                        break;
                     }
                  }
               }
            }
           
            args[i + nArgOffset] = new Pair(action.getTypeSymbol(), new Pair(grouped, new Pair(condition, body)));

            action.setBody(null);
            action.setTextPositionMap(null);
         }

         // Ensure there is a main action
         if (nActionCount == 0 || ((Pair)args[args.length - 1]).getHead() != Symbol.MAIN)
         {
            if (nActionCount > 0)
            {
               Object[] argsSaved = args;

               args = new Object[args.length + 1];
               System.arraycopy(argsSaved, 0, args, 0, argsSaved.length);
            }

            args[args.length - 1] new Pair(Symbol.MAIN, new Pair(Boolean.FALSE, new Pair(Boolean.TRUE, EMPTY_BODY)));
         }

         try
         {
            // Invoke the sys:generate-event macro that generates the ECA code and compile the result
            Pair code = (Pair)machine.invoke((Function)machine.getGlobalEnvironment().getVariable(Symbol.SYS_GENERATE_EVENT), args);
            TextPosition pos = new TextPosition(0, 0);

            posMap.put(code, pos);
            urlMap.put(pos, sEventURL);

            for (Pair pair = code; pair != null; pair = pair.getNext())
            {
               if (pair.getHead() instanceof Pair && !posMap.contains(pair.getHead()))
               {
                  pos = new TextPosition(0, 0);
                  posMap.put(pair.getHead(), pos);
                  urlMap.put(pos, sEventURL);
               }
            }
           
            m_function = new Compiler().compile(code, posMap, urlMap, machine, false);
         }
         catch (Exception e)
         {
            MetadataValidationException x;

            if (e instanceof UncheckedException)
            {
               x = new MetadataValidationException((UncheckedException)e);
            }
            else
            {
               x = new MetadataValidationException("err.meta.eventCompilation", e);
            }

            setProperties(x);

            if (e instanceof CompilerException)
            {
               String sURL = ((CompilerException)e).getURL();

               if (sURL != null)
               {
                  int i = sURL.indexOf('$');

                  if (i > 0)
                  {
                     int k = sURL.indexOf('$', i + 1);
                    
                     if (k < 0)
                     {
                        x.setProperty("action", sURL.substring(i + 1));
                     }
                     else
                     {
                        x.setProperty("action", sURL.subSequence(i + 1, k));
                        x.setProperty("condition", Boolean.TRUE);
                     }
                  }
               }
            }

            throw x;
         }
      }
   }

   /**
    * Creates an argument symbol list S-expression.
    * @param bThis True to include the this keyword.
    * @return The argument list S-expression.
    */
   public Pair getArguments(boolean bThis)
   {
      Object obj = null;
      int i = getArgumentCount() - 1;
     
      if (m_bVarArg)
      {
         obj = getArgument(i--).getSymbol();
      }

      while (i >= 0)
      {
         obj = new ConstPair(getArgument(i--).getSymbol(), obj);
      }

      if (bThis)
      {
         return new ConstPair(Symbol.THIS, obj);
      }
     
      return (Pair)obj;
   }
  
   /**
    * @return The variable initializer in let format S-expression.
    */
   private Pair getVariables()
   {
      Pair pair = null;

      if (m_varMap != null)
      {
         for (Lookup.Iterator itr = m_varMap.iterator(); itr.hasNext();)
         {
            itr.next();
            pair = new ConstPair(new ConstPair(itr.getKey(), new ConstPair(itr.getValue())), pair);
         }
      }

      return pair;
   }

   /**
    * Checks the event visibility and access against a privilege set.
    * @param privilegeSet The privilege set containing the allowed privileges.
    * @throws SecurityViolationException if the visibility is not public or the access is denied.
    */
   public void checkAccess(PrivilegeSet privilegeSet) throws SecurityViolationException
   {
      if (m_nVisibility != Metaclass.PUBLIC)
      {
         throw new SecurityViolationException("err.rpc.eventVisibility",
            new Object[]{getName(), m_metaclass.getName()});
      }

      if (m_privilege != null && !privilegeSet.contains(m_privilege))
      {
         throw new SecurityViolationException("err.rpc.eventPrivilege",
            new Object[]{getName(), m_metaclass.getName(), m_privilege.getName()});
      }
   }

   /**
    * @see nexj.core.meta.MetadataObject#makeReadOnly()
    */
   public void makeReadOnly()
   {
      assert m_nTransactionMode != TX_DEFAULT || m_metaclass instanceof Aspect;

      for (Iterator itr = getArgumentIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      for (Iterator itr = getActionIterator(); itr.hasNext();)
      {
         ((MetadataObject)itr.next()).makeReadOnly();
      }

      super.makeReadOnly();

      ((ArrayList)m_actionList).trimToSize(); // free memory not used after compile()
      ((ArrayList)m_argList).trimToSize(); // free memory not used after compile()
   }

   /**
    * Invokes the event in a given context.
    * @param args The event arguments. The first one is the object instance.
    * @param machine The VM.
    * @return The event return value.
    */
   public Object invoke(Object[] args, Machine machine)
   {
      if (m_metaclass.getLogger().isDebugEnabled())
      {
         dump(EMPTY_CLOSURE, args, machine.getContext());
      }

      return machine.invoke(m_function, args);
   }

   /**
    * Invokes the event in a given context.
    * @param accessor The accessor.
    * @param args The event arguments.
    * @param machine The VM.
    * @return The event return value.
    */
   public Object invoke(Accessor accessor, Object[] args, Machine machine)
   {
      if (m_metaclass.getLogger().isDebugEnabled())
      {
         dump(accessor, args, machine.getContext());
      }

      return machine.invoke(m_function, accessor, args);
   }

   /**
    * Invokes the event in a given context.
    * @param args The event arguments.
    * @param machine The VM.
    * @return The event return value.
    */
   public Object invoke(Accessor accessor, Pair args, Machine machine)
   {
      if (m_metaclass.getLogger().isDebugEnabled())
      {
         dump(accessor, Pair.toArray(args), machine.getContext());
      }

      return machine.invoke(m_function, accessor, args);
   }

   /**
    * Dumps the event to the logger.
    * @param nArgCount The argument count.
    * @param machine The VM on which stack are the arguments.
    */
   public void dump(int nArgCount, Machine machine)
   {
      Object[] args = new Object[nArgCount];

      machine.getArgs(args);
      dump(EMPTY_CLOSURE, args, machine.getContext());
   }

   /**
    * Dumps the event to the logger.
    * @param obj The event object.
    * @param args The event argument values.
    * @param ctx The context
    */
   public void dump(Object obj, Object[] args, Context ctx)
   {
      if (ctx instanceof InvocationContext && ((InvocationContext)ctx).isStealth())
      {
         return;
      }

      Logger logger = m_metaclass.getLogger();

      logger.debug("Invoking " + this);
           
      if (logger.isDumpEnabled())
      {
         int nOfs = (obj == EMPTY_CLOSURE) ? 1 : 0;
        
         if (args == null)
         {
            args = EMPTY_SYMBOL_ARRAY;
         }

         logger.dump("this = " + Intrinsic.toString((nOfs == 0) ? obj : args[0]));

         for (int i = 0, n = getArgumentCount() - ((m_bVarArg) ? 1 : 0); i < n; ++i)
         {
            if (i + nOfs >= args.length)
            {
               break;
            }

            logger.dump(getArgument(i).getName() + " = " + Intrinsic.toString(args[i + nOfs]));
         }

         if (m_bVarArg)
         {
            StringBuffer buf = new StringBuffer(128);

            buf.append(getArgument(getArgumentCount() - 1).getName());
            buf.append(" = (");

            for (int i = getArgumentCount() - 1, n = args.length - nOfs; i < n; ++i)
            {
               if (i >= getArgumentCount())
               {
                  buf.append(' ');
               }

               buf.append(Intrinsic.toString(args[i + nOfs]));
            }

            buf.append(')');

            logger.dump(buf.toString());
         }
      }
   }

   /**
    * Append this event arguments to the given buffer in the format
    * (arg1,arg2,..,argN)
    *
    * @param buf The buffer to which the arguments should be appended
    * @param sSeparator String to separate arguments
    * @param bTyped Include argument/result types
    */
   protected void appendArguments(StringBuilder buf, String sSeparator, boolean bTyped)
   {
      buf.append('(');

      for (int i = 0; i < getArgumentCount(); ++i)
      {
         if (i != 0)
         {
            buf.append(sSeparator);
         }

         if (bTyped)
         {
            getArgument(i).appendTo(buf);
         }
         else
         {
            buf.append(getArgument(i).getName());
         }
      }

      buf.append(')');
   }

   /**
    * Format the URL with the given metaclass in the second field. For example,
    * class:ClassName.eventName(arg1,arg2,..,argN).
    * @param metaclass The class this URL should refer to
    * @return The scripting URL for this event
    */
   protected String getURL(Metaclass metaclass)
   {
      StringBuilder buf = new StringBuilder(64);

      buf.append("class:");
      buf.append(metaclass.getName());
      buf.append(".");
      buf.append(getName());
      appendArguments(buf, ",", false);

      return buf.toString();
   }

   /**
    * @see nexj.core.meta.MetadataObject#validate(nexj.core.meta.ContextMetadata, ExceptionHolder)
    */
   public void validate(ContextMetadata metadata, ExceptionHolder warnings)
   {
      if (warnings != null &&
         m_nVisibility == Metaclass.PUBLIC &&
         m_metaclass.getVisibility() == Metaclass.PUBLIC &&
         m_privilege == null &&
         m_accessAttribute == null)
      {
         StringBuilder buf = new StringBuilder(64);

         buf.append(getName());
         appendArguments(buf, ", ", false);

         MetadataValidationException e = new MetadataValidationException(
            "err.meta.missingEventSecurity", new Object[]{buf.toString(), m_metaclass.getName()});

         setProperties(e);
         warnings.addException(e);
      }
   }
  
   /**
    * Creates an incompatible event exception.
    * @param metaclass The metaclass.
    * @param sErrCode The error string identifier.
    * @param argArray The error string arguments.
    */
   private MetadataValidationException createCompatibilityException(Metaclass metaclass, String sErrCode, Object[] argArray)
   {
      MetadataValidationException mve = metaclass.createCompatibilityException(sErrCode, argArray);
     
      mve.setTypeName("Event");
      mve.setProperty("event", getName());
     
      return mve;
   }
  
   /**
    * @see nexj.core.meta.Member#checkCompatibility(nexj.core.meta.Metaclass, nexj.core.util.ExceptionHolder)
    */
   public void checkCompatibility(Metaclass metaclass, ExceptionHolder eh)
   {
      Event event = metaclass.findEvent(getName(), getArgumentCount());
     
      if (event == null)
      {
         eh.addException(createCompatibilityException(metaclass, "err.meta.missingCompatibleEvent",
            new Object[] {getName(), metaclass.getName()}));

         return;
      }

      if (!event.isCompatible())
      {
         eh.addException(createCompatibilityException(metaclass, "err.meta.incompatibleEventProperty",
            new Object[] {"compatible", event.getName(), metaclass.getName()}));
      }
      else if (getVisibility() != event.getVisibility() && event.getVisibility() != Metaclass.PUBLIC)
      {
         eh.addException(createCompatibilityException(metaclass, "err.meta.incompatibleEventProperty",
            new Object[] {"visibility", event.getName(), metaclass.getName()}));
      }    
      else if (event.isVarArg() != isVarArg())
      {
         eh.addException(createCompatibilityException(metaclass, "err.meta.incompatibleEventProperty",
            new Object[] {"vararg", event.getName(), metaclass.getName()}));
      }
      else if (event.isStatic() != isStatic())
      {
         eh.addException(createCompatibilityException(metaclass, "err.meta.incompatibleEventProperty",
            new Object[] {"static", event.getName(), metaclass.getName()}));
      }

      for (int i = 0; i < event.getArgumentCount(); ++i)
      {
         if (!event.getArgument(i).getName().equals(getArgument(i).getName()))
         {
            eh.addException(createCompatibilityException(metaclass, "err.meta.incompatibleEventArgName",
               new Object[] {event.getArgument(i).getName(), event.getName(), metaclass.getName()}));
           
            break;
         }
      }
   }

   /**
    * @see nexj.core.meta.MetadataObject#setProperties(nexj.core.meta.MetadataMarker)
    */
   public void setProperties(MetadataMarker marker)
   {
      m_metaclass.setProperties(marker);
      marker.setTypeName("Event");
      marker.setProperty("event", m_sName);
      marker.setProperty("args", Primitive.createInteger(getArgumentCount()));
   }

   /**
    * @see java.lang.Object#toString()
    */
   public String toString()
   {
      StringBuilder buf = new StringBuilder(64);

      buf.append("Event ");

      if (getMetaclass() != null)
      {
         buf.append(getMetaclass().getName());
         buf.append('.');
      }

      buf.append(getName());
      appendArguments(buf, ", ", true);

      Argument result = getResult();

      if (result != null)
      {
         buf.append(" :");
         result.appendTo(buf);
      }

      return buf.toString();
   }
}
TOP

Related Classes of nexj.core.meta.Event

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.