Package nexj.core.integration.format.object

Source Code of nexj.core.integration.format.object.ObjectMessageFormatterStrategy$ConflictResolutionStrategy

// Copyright 2010 NexJ Systems Inc. This software is licensed under the terms of the Eclipse Public License 1.0
package nexj.core.integration.format.object;

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

import nexj.core.integration.CompoundIntegrationException;
import nexj.core.integration.IntegrationException;
import nexj.core.integration.Output;
import nexj.core.integration.sync.DefaultSyncEngine;
import nexj.core.meta.Attribute;
import nexj.core.meta.Metaclass;
import nexj.core.meta.Metadata;
import nexj.core.meta.Primitive;
import nexj.core.meta.integration.CompositeMessagePart;
import nexj.core.meta.integration.CompositeMessagePartRef;
import nexj.core.meta.integration.Message;
import nexj.core.meta.integration.MessagePart;
import nexj.core.meta.integration.PrimitiveMessagePart;
import nexj.core.meta.integration.format.object.ObjectMessagePartMapping;
import nexj.core.meta.integration.service.Sync;
import nexj.core.persistence.OID;
import nexj.core.persistence.Query;
import nexj.core.rpc.GenericServer;
import nexj.core.rpc.TransferObject;
import nexj.core.runtime.Instance;
import nexj.core.runtime.InstanceArrayList;
import nexj.core.runtime.InstanceList;
import nexj.core.runtime.InvocationContext;
import nexj.core.runtime.SecurityViolationException;
import nexj.core.runtime.UnitOfWork;
import nexj.core.scripting.Intrinsic;
import nexj.core.scripting.Pair;
import nexj.core.scripting.Symbol;
import nexj.core.util.Binary;
import nexj.core.util.HashTab;
import nexj.core.util.HashTab2D;
import nexj.core.util.Logger;
import nexj.core.util.Lookup;
import nexj.core.util.Lookup2D;
import nexj.core.util.ObjUtil;
import nexj.core.util.StringUtil;

/**
* Object message formatter strategy.
*/
public abstract class ObjectMessageFormatterStrategy
{
   // constants

   /**
    * Special constant to indicate an empty where clause.
    */
   protected final static Pair EMPTY = new Pair(null);

   /**
    * Maximum number of retry attempts in case of an Optimistic Lock
    */
   private final static int MAX_RETRIES = 5;

   // associations

   /**
    * The invocation context.
    */
   protected InvocationContext m_context;

   /**
    * Instance map: Instance[TransferObject].
    */
   protected Lookup m_instanceByTobjMap;

   /**
    * Invocation list: TransferObject[].
    */
   protected List m_invocationList;

   /**
    * Query result map: InstanceList[composite][where].
    */
   protected Lookup2D m_queryMap;

   /**
    * Map of info object, indexed by transfer object.
    */
   protected Lookup m_infoObjectByTobjMap;

   /**
    * The class logger.
    */
   protected final static Logger s_logger = Logger.getLogger(ObjectMessageFormatter.class);

   // operations

   /**
    * @see nexj.core.runtime.InvocationContextAware#setInvocationContext(nexj.core.runtime.InvocationContext)
    */
   public void setInvocationContext(InvocationContext context)
   {
      m_context = context;
   }

   /**
    * Sets the OIDHolder of the synchronization link - source of data used for this persist step.
    * @param syncLink The OIDHolder of the synchronization link - source of data used for this persist step to set.
    */
   public void setSyncLink(Sync.Link syncLink)
   {
   }

   /**
    * @return The Logger instance if it exists. Otherwise, return null.
    */
   public Logger getLinkLogger()
   {
      return null;
   }

   /**
    * Prepare state for incoming changes.
    * @param tobjRoot The transfer object or collection - root of the graph.
    * @param composite The message part metadata.
    */
   protected void prepareFind(Object tobjRoot, CompositeMessagePart composite)
   {
   }

   /**
    * Prepares instance state.
    */
   protected void prepareInstantiate()
   {
   }

   /**
    * Updates the instance map with deleted instances for auditing.
    */
   protected void prepareAudit()
   {
   }

   /**
    * Finalizes instance state.
    */
   protected void finalizeInstantiate()
   {
   }

   /**
    * Adjusts the transfer object's event name.
    * @param tobj The transfer object.
    * @param messageClass The message metaclass.
    */
   protected void adjustEventName(TransferObject tobj, Metaclass messageClass)
   {
   }

   /**
    * Sets event name on the transfer object.
    * @param tobj The root transfer object.
    * @param sEventName The event name to set.
    */
   protected void setEventName(TransferObject tobj, String sEventName)
   {
      tobj.setEventName(sEventName);
   }

   /**
    * Log inbound synchronization events if nLevel is enabled.
    * @param nLevel The logging level.
    * @param sCode The string identifier of the message.
    */
   protected void logEvent(int nLevel, String sCode)
   {
   }

   /**
    * @return True, if the instance read by sync key was already done in batch (if we did not find them, we won't find
    * them now either). Otherwise, false.
    * @param tobj The transfer object.
    * @param mapping The object mapping.
    */
   protected boolean isInstanceRead(TransferObject tobj, ObjectMessagePartMapping mapping)
   {
      return false;
   }

   /**
    * Deletes instance state.
    * @param tobj The transfer object.
    * @param messageClass The message metaclass.
    */
   protected void deleteInstanceState(TransferObject tobj, Metaclass messageClass)
   {
   }

   /**
    * Find all instances dependent on the instances that needs to be deleted
    * @param mapping The object message part mapping
    * @param allInstanceList List containing all instances.
    * @param instanceToKeepList List containing instance to keep.
    */
   protected void findDependentInstances(ObjectMessagePartMapping mapping, InstanceList allInstanceList,
      List instanceToKeepList)
   {
   }

   /**
    * Adds the instance to delete to the list.
    * @param instance The instance to delete.
    * @param composite The message part metadata corresponding to the instance to delete.
    * @param parentTobj The parent transfer object.
    */
   protected void addInstanceToDelete(Instance instance, CompositeMessagePart composite, TransferObject parentTobj)
   {
      ObjectMessageInfo infoObject = (ObjectMessageInfo)m_infoObjectByTobjMap.get(parentTobj);

      if (infoObject == null)
      {
         infoObject = new ObjectMessageInfo();
         m_infoObjectByTobjMap.put(parentTobj, infoObject);
      }

      infoObject.addInstanceToDelete(composite, instance);
   }

   /**
    * @return The message information object corresponding to the transfer object.
    * @param tobj The transfer object.
    */
   protected ObjectMessageInfo getInfoObject(TransferObject tobj)
   {
      ObjectMessageInfo infoObject = (ObjectMessageInfo)m_infoObjectByTobjMap.get(tobj);

      if (infoObject == null)
      {
         infoObject = ObjectMessageInfo.EMPTY_INFO;
      }

      return infoObject;
   }

   /**
    * @return True, if the root instance has already been synchronize.
    * @param tobj The transfer object.
    */
   protected boolean isRootSynchronized(TransferObject tobj)
   {
      return true;
   }

   /**
    * @see nexj.core.integration.MessageFormatter#format(TransferObject, Message, Output)
    */
   public void format(TransferObject tobj, Message message, Output out) throws IntegrationException
   {
      out.setObject(format(tobj, message.getRoot()));
   }

   /**
    * Formats an object message.
    * @param tobj The message to format.
    * @param composite The message part metadata.
    * @return The formatted instance.
    */
   private Object format(TransferObject tobj, CompositeMessagePart composite) throws IntegrationException
   {
      boolean bAuditedSaved = m_context.isAudited();

      try
      {
         m_context.setAudited(false);

         ObjectMessagePartMapping mapping = (ObjectMessagePartMapping)composite.getMapping();

         if (mapping != null)
         {
            String sDerivedMessageName = tobj.getClassName();
            Message message = mapping.getMessage();

            if (!StringUtil.isEmpty(sDerivedMessageName) && !sDerivedMessageName.equals(message.getName()))
            {
               Message derivedMessage = m_context.getMetadata().getMessage(sDerivedMessageName);
               Message.validatePolymorphism(message, derivedMessage, derivedMessage.getRoot());

               composite = derivedMessage.getRoot();
            }
         }

         if (s_logger.isDebugEnabled())
         {
            s_logger.debug("Formatting object message \"" + composite.getName() + "\"");
            s_logger.dump(tobj);
         }

         m_instanceByTobjMap = new HashTab();
         m_invocationList = new ArrayList();
         m_queryMap = new HashTab2D();
         m_infoObjectByTobjMap = new HashTab();

         Object obj = (composite.isCollection()) ? Collections.singletonList(tobj) : (Object)tobj;

         prepareFind(obj, composite);
         logEvent(Logger.DUMP, "ids.sync.formattingStarted");
         findInstance1(tobj, composite, null, null);
         prepareInstantiate();

         Object instance = instantiate(obj, composite);

         prepareAudit();
         GenericServer.auditRequest(m_instanceByTobjMap, null, m_context);

         for (int i = 0, n = m_invocationList.size(); i != n; ++i)
         {
            TransferObject nextTobj = (TransferObject)m_invocationList.get(i);
            Instance inst = (Instance)m_instanceByTobjMap.get(nextTobj);
            boolean bInvoke = true;

            if (inst.isEventPending())
            {
               // For a change from an external system, the event could be set to create. Additionally, the internal
               // instance may already exist. In such a case, the instance should simply be updated and not create
               // any duplicates in the internal system.
               bInvoke = (("create".equals(nextTobj.getEventName()) && inst.getState() == Instance.DIRTY) &&
                  "update".equals(inst.getPendingEventName()))
                  ? false
                  : !inst.getPendingEventName().equals(nextTobj.getEventName());

               inst.invokePendingEvent();
            }

            if (bInvoke && nextTobj.getEventName() != null
               // Ignore pending "update" events - they should only run if there has been an actual update.
               && (!nextTobj.getEventName().equals("update") || inst.getState() == Instance.DIRTY)
               // Instance update or insert was rejected (e.g. by sync engine)
               && inst.getState() != Instance.DELETED)
            {
               inst.invoke(nextTobj.getEventName());
            }
         }

         finalizeInstantiate();
         s_logger.debug("Completed formatting the object message");
         logEvent(Logger.DEBUG, "ids.sync.formattingCompleted");

         if (composite.isCollection() && instance instanceof List)
         {
            List instanceList = (List)instance;

            return (instanceList.isEmpty()) ? null : instanceList.get(0);
         }

         return instance;
      }
      catch (Exception e)
      {
         s_logger.debug("Object formatting error", e);

         throw new IntegrationException("err.integration.format",
            new Object[]{composite.getName()}, e);
      }
      finally
      {
         m_context.setAudited(bAuditedSaved);
      }
   }

   /**
    * Formats the object message to the specified output.
    * Attempts to retry the entire message several times before splitting it into chunks.
    * 
    * @param tobj The object message to format.
    * @param message The message metadata.
    * @param out The message output. It will be populated even if this method throws an exception.
    * @param bCommit flag whether results of successful formatting should be committed
    * in case when some parts of the message raise an exception
    * @param nRetry number of a retry attempt
    * @throws a collection of all the exceptions that occur during formatting.
    */
   public void formatFailSafe(TransferObject tobj, Message message, Output out, boolean bCommit, int nRetry)
      throws CompoundIntegrationException
   {
      UnitOfWork oldUOW = m_context.beginTransaction(); // We use unit of work to recover from database failures
      String sOriginalEvent = tobj.getEventName();

      try
      {
         format(tobj, message, out);
         m_context.commitAndResume(oldUOW);
      }
      catch (Throwable e)
      {
         m_context.rollbackAndResume(oldUOW);

         if ((nRetry < MAX_RETRIES) && DefaultSyncEngine.isTransient(e))
         {
            if (s_logger.isDebugEnabled())
            {
               s_logger.debug("Error processing the composite message on attempt " + nRetry + "; trying again.", e);
            }

            Logger logger = getLinkLogger();

            if (logger != null && logger.isWarnEnabled())
            {
               logger.log(Logger.WARN, "err.sync.recoverableFormatting", new Object[] {tobj.getClassName()}, e);
            }

            tobj.setEventName(sOriginalEvent);
            formatFailSafe(tobj, message, out, bCommit, nRetry + 1);
         }
         else
         {
            s_logger.debug("Error processing the composite message; trying to process it in chunks", e);

            Lookup objectParts = getObjectParts(tobj, message.getRoot(), new HashTab());
            CompoundIntegrationException ex = new CompoundIntegrationException(e.getMessage());

            out.setObject(new FailSafeFormatter(ex, bCommit).divideAndFormat(objectParts));

            if (ex.getExceptionCount() > 0)
            {
               throw ex;
            }
         }
      }
   }

   /**
    * Find all top-level transfer objects, contained within a given transfer object or a collection thereof.
    * @param tobj The transfer object.
    * @param composite The message part metadata.
    * @param objectParts object part messages mapped by corresponding transfer objects: Message[TransferObject]
    * @return objectParts map
    */
   protected Lookup getObjectParts(Object obj, CompositeMessagePart composite, Lookup objectParts)
   {
      if (composite.isCollection())
      {
         List list = getList(obj, composite);

         for (int i = 0, n = list.size(); i != n; ++i)
         {
            getObjectParts1((TransferObject)list.get(i), composite, objectParts);
         }
      }
      else
      {
         getObjectParts1((TransferObject)obj, composite, objectParts);
      }

      return objectParts;
   }

   /**
    * Finds all top-level transfer objects, contained within a given transfer object .
    * @param tobj The transfer object.
    * @param composite The message part metadata.
    * @param objectParts object part messages mapped by corresponding transfer objects: Message[TransferObject]
    * @return objectParts map
    */
   protected void getObjectParts1(TransferObject tobj, CompositeMessagePart composite, Lookup objectParts)
   {
      if (tobj == null)
      {
         return;
      }

      composite = getPolymorphic(composite, tobj);

      if (composite.getMapping() != null)
      {
         if (composite instanceof CompositeMessagePartRef)
         {
            composite = ((CompositeMessagePartRef)composite).getRefPart();
         }

         objectParts.put(tobj, composite);
      }
      else
      {
         for (int i = 0, n = composite.getPartCount(); i != n; ++i)
         {
            MessagePart part = composite.getPart(i);
  
            if (part instanceof CompositeMessagePart)
            {
               getObjectParts(tobj.findValue(part.getName()), (CompositeMessagePart)part, objectParts);
            }
         }
      }
   }

   /**
    * Finds an instance for a given transfer object or a collection thereof.
    * @param tobj The transfer object.
    * @param composite The message part metadata.
    * @param parentTobj The parent transfer object, if any.
    * @param parentComposite composite's parent metadata, if any.
    */
   protected void findInstance(Object obj, CompositeMessagePart composite, TransferObject parentTobj,
      CompositeMessagePart parentComposite)
   {
      if (composite.isCollection())
      {
         List list = getList(obj, composite);
         InstanceList foundInstances = new InstanceArrayList(list.size());
         ObjectMessagePartMapping mapping = (ObjectMessagePartMapping)composite.getMapping();

         for (int i = 0, n = list.size(); i != n; ++i)
         {
            Instance instance = findInstance1((TransferObject)list.get(i), composite, parentTobj, parentComposite);

            if (instance != null)
            {
               foundInstances.add(instance, InstanceList.REPLACE | InstanceList.DIRECT);
            }
         }

         if (mapping.isDelete() && obj != null) // Delete all unmatched instances
         {
            Object where = mapping.getWhere();

            if (mapping.isLocal() && parentComposite.getMapping() != null)
            {
               Pair eq = Pair.list(Symbol.ATAT, ((ObjectMessagePartMapping)
                  parentComposite.getMapping()).getMetaclass().getSymbol(),
                  mapping.getAttribute().getSymbol()).eq(m_instanceByTobjMap.get(parentTobj));

               where = eq.and(where);
            }

            InstanceList allInstances = Query.createRead(mapping.getMetaclass(), getAttributes(composite),
               where, null, -1, 0, false, Query.SEC_NONE, m_context).read();

            if (!allInstances.isEmpty())
            {
               List instToKeepList = new ArrayList();

               for (Iterator itr = allInstances.iterator(); itr.hasNext();)
               {
                  Instance instance = (Instance)itr.next();

                  if (!foundInstances.contains(instance))
                  {
                     addInstanceToDelete(instance, composite, parentTobj);
                  }
                  else
                  {
                     instToKeepList.add(instance);
                  }
               }

               // Find all instances dependent on the instances that need to be deleted
               findDependentInstances(mapping, allInstances, instToKeepList);
            }
         }
      }
      else
      {
         findInstance1((TransferObject)obj, composite, parentTobj, parentComposite);
      }
   }

   /**
    * Finds an instance for a given transfer object.
    * @param tobj The transfer object.
    * @param composite The message part metadata.
    * @param parentTobj The parent transfer object, if any.
    * @param parentComposite composite's parent metadata, if any.
    */
   protected Instance findInstance1(TransferObject tobj, CompositeMessagePart composite, TransferObject parentTobj,
      CompositeMessagePart parentComposite)
   {
      if (tobj == null)
      {
         return null;
      }

      ObjectMessagePartMapping mapping = (ObjectMessagePartMapping)composite.getMapping();
      CompositeMessagePart derivedComposite = getPolymorphic(composite, tobj);
      ObjectMessagePartMapping derivedMapping = (ObjectMessagePartMapping)derivedComposite.getMapping();
      Metaclass derivedClass = (derivedMapping == null) ? null : derivedMapping.getMetaclass();

      // If any message part is mapped to special attributes ATTR_EVENT and ATTR_OID,
      // it overrides the event or oid set on message.
      String sEventName = (String)getSystemAttributeValue(derivedComposite, tobj, ObjectMessagePartMapping.ATTR_EVENT);
      OID oid = (OID)getSystemAttributeValue(derivedComposite, tobj, ObjectMessagePartMapping.ATTR_OID);

      if (sEventName != null)
      {
         setEventName(tobj, sEventName);
      }

      if (oid != null)
      {
         tobj.setOID(oid);
      }

      boolean bDelete = "delete".equals(tobj.getEventName());

      adjustEventName(tobj, derivedClass);

      Instance instance = (Instance)m_instanceByTobjMap.get(tobj);

      if (instance == null)
      {
         deleteInstanceState(tobj, derivedClass);

         if ("create".equals(tobj.getEventName())
            && (!derivedMapping.hasAlternativeSyncKey() || !isRootSynchronized(tobj)))
         {
            // The internal instance corresponding to the transfer object will be created during instantiation.
            m_instanceByTobjMap.put(tobj, null);
         }
         else
         {
            if (derivedClass != null)
            {
               if (derivedClass.getPersistenceMapping() == null)
               {
                  if (tobj.getEventName() == null)
                  {
                     if (parentTobj == null)
                     {
                        throw new IntegrationException("err.integration.object.missingEvent",
                           new Object[]{derivedComposite.getFullPath()});
                     }

                     setEventName(tobj, "create");
                  }

                  m_instanceByTobjMap.put(tobj, null);
               }
               else if (!mapping.isLocal() || m_instanceByTobjMap.contains(parentTobj))
               {
                  Pair where = getWhere1(tobj, composite, parentTobj, parentComposite, null, true);
                  InstanceList list = null;

                  if (where != EMPTY && !(mapping.isLocal() && m_instanceByTobjMap.get(parentTobj) == null))
                  {
                     if (mapping.isLocal())
                     {
                        Pair eq = Pair.list(Symbol.ATAT, ((ObjectMessagePartMapping)
                           parentComposite.getMapping()).getMetaclass().getSymbol(),
                           mapping.getAttribute().getSymbol()).eq(m_instanceByTobjMap.get(parentTobj));

                        if (where != null)
                        {
                           where = where.and(eq);
                        }
                        else
                        {
                           where = eq;
                        }
                     }

                     if (where != null)
                     {
                        list = (InstanceList)m_queryMap.get(derivedComposite, where);

                        if (list == null)
                        {
                           list = Query.createRead(derivedClass, getAttributes(derivedComposite), where, null,
                              2, 0, false, Query.SEC_NONE, m_context).read();

                           m_queryMap.put(derivedComposite, where, list);
                        }
                     }
                     else
                     {
                        where = EMPTY;
                     }
                  }

                  if (where != EMPTY && list != null && list.size() != 0)
                  {
                     if (list.size() > 1)
                     {
                        throw new IntegrationException("err.integration.object.ambiguousMatch",
                           new Object[]{derivedComposite.getFullPath()});
                     }

                     instance = list.getInstance(0);
                     m_instanceByTobjMap.put(tobj, instance);
                  }
                  else if (!bDelete)
                  {
                     if (tobj.getEventName() != null && (!mapping.isLocal() || m_instanceByTobjMap.get(parentTobj) == null) ||
                        !mapping.isCreate() && !mapping.isKey())
                     {
                        throw new IntegrationException("err.integration.object.noMatch",
                           new Object[]{derivedComposite.getFullPath()});
                     }

                     setEventName(tobj, (!mapping.isCreate() ||
                        where == EMPTY && !isKeyAvailable(tobj, derivedComposite)) ? "delete" : "create");

                     m_instanceByTobjMap.put(tobj, null);
                  }
               }
            }
         }
      }

      for (int i = 0, n = derivedComposite.getPartCount(); i != n; ++i)
      {
         MessagePart part = derivedComposite.getPart(i);

         if (part instanceof CompositeMessagePart)
         {
            findInstance(tobj.findValue(part.getName()), (CompositeMessagePart)part, tobj, derivedComposite);
         }
      }

      return instance;
   }

   /**
    * @return True, if the child key is available to the parent.
    */
   private boolean isKeyAvailable(TransferObject tobj, CompositeMessagePart derivedComposite)
   {
      for (int i = 0, n = derivedComposite.getPartCount(); i < n; ++i)
      {
         MessagePart part = derivedComposite.getPart(i);

         if (part instanceof CompositeMessagePart && !part.isCollection())
         {
            ObjectMessagePartMapping mapping = (ObjectMessagePartMapping)part.getMapping();

            if (mapping != null && mapping.isKey())
            {
               Object childObj = tobj.findValue(part.getName());

               if (!(childObj instanceof TransferObject))
               {
                  return false;
               }

               TransferObject childTobj = (TransferObject)childObj;

               if (m_instanceByTobjMap.get(childTobj) == null && !"create".equals(childTobj.getEventName()))
               {
                  return false;
               }
            }
         }
      }

      return true;
   }

   /**
    * Returns object attributes included in the given object message part, excluding collection attributes.
    * @param composite The message part metadata.
    * @return a list of attribute to be read from the datasource
    */
   private Pair getAttributes(CompositeMessagePart composite)
   {
      ObjectMessagePartMapping mapping = (ObjectMessagePartMapping)composite.getMapping();
      Pair attributes = null;

      for (int i = composite.getPartCount() - 1; i >= 0; --i)
      {
         ObjectMessagePartMapping partMapping = ((ObjectMessagePartMapping)composite.getPart(i).getMapping());
         Attribute attr = partMapping.getAttribute();
        
         if (attr != null && !attr.isCollection())
         {
            attributes = new Pair(attr.getSymbol(), attributes);

            if (partMapping.getAccessAttribute() != null)
            {
               attributes = new Pair(partMapping.getAccessAttribute().getSymbol(), attributes);
            }
         }
      }

      if (composite.getParent() == null && mapping.getAccessAttribute() != null)
      {
         attributes = new Pair(mapping.getAccessAttribute().getSymbol(), attributes);
      }

      if (m_context.isSecure())
      {
         Metaclass metaclass = mapping.getMetaclass();

         if (metaclass.getReadAccessAttribute() != null)
         {
            attributes = new Pair(metaclass.getReadAccessAttribute().getSymbol(), attributes);
         }

         if (mapping.isUpdate() && metaclass.getUpdateAccessAttribute() != null)
         {
            attributes = new Pair(metaclass.getUpdateAccessAttribute().getSymbol(), attributes);
         }
      }

      return attributes;
   }

   /**
    * Generates a where clause for looking up one object.
    * @param obj The object for which to generate the where clause. Can be a list.
    * @param composite The message part metadata.
    * @param parent The parent transfer object, if any.
    * @param parentComposite composite's parent metadata, if any.
    * @param assoc The reverse association symbol list.
    * @param bRoot True if this is the object to which the where clause applies.
    */
   protected Pair getWhere(Object obj, CompositeMessagePart composite, TransferObject parent,
      CompositeMessagePart parentComposite, Pair assoc, boolean bRoot)
   {
      if (composite.isCollection())
      {
         Pair where = null;
         List list = getList(obj, composite);

         for (int i = 0, n = list.size(); i != n; ++i)
         {
            Pair expr = getWhere1((TransferObject)list.get(i), composite, parent, parentComposite, assoc, bRoot);

            if (expr != EMPTY && expr != null)
            {
               where = new Pair(expr, where);
            }
         }

         if (where != null)
         {
            if (where.getTail() == null)
            {
               return (Pair)where.getHead();
            }

            return new Pair(Symbol.OR, where);
         }

         return EMPTY;
      }

      return getWhere1((TransferObject)obj, composite, parent, parentComposite, assoc, bRoot);
   }

   /**
    * Generates a where clause for looking up one object.
    * @param tobj The object for which to generate the where clause.
    * @param composite The message part metadata.
    * @param parent The parent transfer object, if any.
    * @param parentComposite composite's parent metadata, if any.
    * @param assoc The reverse association symbol list.
    * @param bRoot True if this is the object to which the where clause applies.
    */
   protected Pair getWhere1(TransferObject tobj, CompositeMessagePart composite, TransferObject parent,
      CompositeMessagePart parentComposite, Pair assoc, boolean bRoot)
   {
      ObjectMessagePartMapping mapping = (ObjectMessagePartMapping)composite.getMapping();
      CompositeMessagePart derivedComposite = getPolymorphic(composite, tobj);
      ObjectMessagePartMapping derivedMapping = ((ObjectMessagePartMapping)derivedComposite.getMapping());
      Metaclass derivedMetaclass = derivedMapping.getMetaclass();
      Pair where = null;

      if (!bRoot)
      {
         assoc = new Pair(mapping.getAttribute().getSymbol(), assoc);

         if (!mapping.isLocal())
         {
            Instance instance = (Instance)m_instanceByTobjMap.get(tobj);

            if (instance == null)
            {
               if (!m_instanceByTobjMap.contains(tobj) && !mapping.isSubKeyParent())
               {
                  instance = findInstance1(tobj, composite, parent, parentComposite);
               }

               if (instance == null && !mapping.isSubKeyParent())
               {
                  return EMPTY;
               }
            }

            if (instance != null && !mapping.isSubKeyParent())
            {
               return getComparison(assoc, instance);
            }
         }
      }

      if (tobj == null || isInstanceRead(tobj, derivedMapping))
      {
         return EMPTY;
      }

      if (tobj.getOID() != null)
      {
         where = new Pair(getComparison(assoc, tobj.getOID()), where);
      }
      else
      {
         for (int i = 0, n = derivedComposite.getPartCount(); i != n; ++i)
         {
            MessagePart part = derivedComposite.getPart(i);
            ObjectMessagePartMapping partMapping = (ObjectMessagePartMapping)part.getMapping();

            if (bRoot && partMapping.isKey() || !bRoot && partMapping.isSubKey())
            {
               if (!tobj.hasValue(part.getName()))
               {
                  return EMPTY;
               }

               Object obj = tobj.getValue(part.getName());
               Pair expr = null;

               if (part instanceof CompositeMessagePart)
               {
                  expr = getWhere(obj, (CompositeMessagePart)part, tobj, composite, assoc, false);

                  if (expr == EMPTY)
                  {
                     return EMPTY;
                  }
               }
               else if (partMapping.getSystemAttribute() == ObjectMessagePartMapping.ATTR_OID)
               {
                  expr = getComparison(assoc, obj);
               }
               else if (partMapping.getAttribute() != null)
               {
                  expr = getComparison(new Pair(partMapping.getAttribute().getSymbol(), assoc),
                     partMapping.getAttribute().getType().convert(obj));
               }

               if (expr != null)
               {
                  where = new Pair(expr, where);
               }
            }
         }

         if (!bRoot && derivedMetaclass != mapping.getAttribute().getType())
         {
            where = new Pair(Pair.binary(Symbol.INSTANCE_P, getAssociation(assoc),
               derivedMetaclass.getSymbol()), where);
         }

         if (mapping.getWhere() != Boolean.TRUE)
         {
            where = new Pair(mapping.getWhere(), where);
         }
      }

      if (where != null)
      {
         if (where.getTail() == null)
         {
            return (Pair)where.getHead();
         }

         where = new Pair(Symbol.AND, where);
      }

      return where;
   }

   /**
    * Creates an association operator based on an association list.
    * @param assoc The reversed association symbol list.
    */
   protected static Pair getAssociation(Pair assoc)
   {
      Pair pair = null;

      while (assoc != null)
      {
         pair = new Pair(assoc.getHead(), pair);
         assoc = assoc.getNext();
      }

      return new Pair(Symbol.AT, pair);
   }

   /**
    * Creates a comparison operator based on an association list and a constant.
    * @param assoc The reversed association symbol list.
    * @param obj The constant value.
    */
   protected static Pair getComparison(Pair assoc, Object obj)
   {
      return getAssociation(assoc).eq(obj);
   }

   /**
    * Instantiates an object or collection thereof.
    * @param obj The object to instantiate.
    * @param msg The message part metadata.
    */
   protected Object instantiate(Object obj, MessagePart msg)
   {
      if (msg.isCollection())
      {
         if (obj == null)
         {
            if (msg.isRequired())
            {
               throw new IntegrationException("err.integration.minPartCount", new Object[]{msg.getFullPath()});
            }

            return new InstanceArrayList(0);
         }

         List list = (List)obj;
         int nCount = list.size();

         if (nCount < msg.getMinCount())
         {
            throw new IntegrationException("err.integration.minPartCount", new Object[]{msg.getFullPath()});
         }

         if (nCount > msg.getMaxCount())
         {
            throw new IntegrationException("err.integration.maxPartCount", new Object[]{msg.getFullPath()});
         }

         InstanceList instanceList = new InstanceArrayList(list.size());

         for (int i = 0; i < nCount; ++i)
         {
            Object instance = instantiate1(list.get(i), msg);

            if (instance != null && ! instanceList.contains(instance))
            {
               instanceList.add(instance);
            }
         }

         return instanceList;
      }

      return instantiate1(obj, msg);
   }

   /**
    * Given a composite part and the TransferObject for it, return the composite part to use
    * taking into account inheritance on a reference.
    * @param composite The composite part.
    * @param tobj The TransferObject.
    * @return The derived message part, if composite part is a reference. Otherwise, returns
    * composite.
    */
   protected CompositeMessagePart getPolymorphic(CompositeMessagePart composite, TransferObject tobj)
   {
      return getPolymorphic(composite, tobj, m_context);
   }

   /**
    * Given a composite part and the TransferObject for it, return the composite part to use
    * taking into account inheritance on a reference.
    * @param composite The composite part.
    * @param tobj The TransferObject.
    * @param context The invocation context.
    * @return The derived message part, if composite part is a reference. Otherwise, returns
    * composite.
    */
   protected static CompositeMessagePart getPolymorphic(CompositeMessagePart composite, TransferObject tobj,
      InvocationContext context)
   {
      if (composite instanceof CompositeMessagePartRef)
      {
         String sDerivedMessageName = tobj.getClassName();
         CompositeMessagePart referencedPart = ((CompositeMessagePartRef)composite).getRefPart();

         if (!StringUtil.isEmpty(sDerivedMessageName))
         {
            Metadata metadata = context.getMetadata();
            Message derivedMessage = metadata.getMessage(sDerivedMessageName);
            Message baseMessage = metadata.getMessage(referencedPart.getRoot().getName());

            Message.validatePolymorphism(baseMessage, derivedMessage, composite);

            return derivedMessage.getRoot();
         }
      }

      return composite;
   }

   /**
    * Instantiates one object.
    * @param obj The object to instantiate.
    * @param msg The message part metadata.
    */
   protected Object instantiate1(Object obj, MessagePart part)
   {
      ObjectMessagePartMapping mapping = (ObjectMessagePartMapping)part.getMapping();
      Metaclass derivedClass = (mapping == null) ? null : mapping.getMetaclass();
      MessagePart derivedPart = part;

      if (part instanceof CompositeMessagePart)
      {
         derivedPart = getPolymorphic((CompositeMessagePart)part, (TransferObject)obj);

         if (derivedPart != part)
         {
            ObjectMessagePartMapping derivedMapping = (ObjectMessagePartMapping)derivedPart.getMapping();

            if (mapping == null)
            {
               mapping = derivedMapping;
            }

            derivedClass = derivedMapping.getMetaclass();
         }
      }

      Metaclass metaclass = null;

      if (mapping != null)
      {
         Attribute attribute = mapping.getAttribute();

         if (obj == null)
         {
            if (part.isRequired())
            {
               throw new IntegrationException("err.integration.minPartCount", new Object[]{part.getFullPath()});
            }

            return null;
         }

         if (derivedPart instanceof CompositeMessagePart)
         {
            metaclass = (Metaclass)getSystemAttributeValue((CompositeMessagePart)derivedPart,
               (TransferObject)obj, ObjectMessagePartMapping.ATTR_CLASS);
         }

         if (m_context.isSecure())
         {
            if (attribute != null)
            {
               attribute.checkReadAccess(m_context.getPrivilegeSet());
            }

            if (metaclass != null)
            {
               metaclass.checkReadAccess(m_context.getPrivilegeSet());
            }
         }

         if (derivedPart instanceof PrimitiveMessagePart)
         {
            Object validatedValue = ((PrimitiveMessagePart)derivedPart).validateValue(obj);
           
            if (attribute == null)
            {
               return validatedValue;
            }

            validatedValue = attribute.getType().convert(validatedValue);

            int nMaxLength = attribute.getMaxLength();

            if (nMaxLength > 0 && mapping.isTruncated())
            {
               if (validatedValue instanceof String)
               {
                  String sValue = (String) validatedValue;

                  if (sValue.length() > nMaxLength)
                  {
                     validatedValue = sValue.substring(0, nMaxLength);

                     if (s_logger.isDebugEnabled())
                     {
                        s_logger.debug("Value of attribute " + attribute.getName()
                           + " has been truncated to maximum allowed length " + nMaxLength
                           + ". The original value is \"" + sValue + "\".");
                     }
                  }
               }
               else if (validatedValue instanceof Binary)
               {
                  byte[] binaryValue = ((Binary) validatedValue).getData();

                  if (binaryValue.length > nMaxLength)
                  {
                     byte[] truncatedValue = new byte[nMaxLength];

                     System.arraycopy(binaryValue, 0, truncatedValue, 0, nMaxLength);
                     validatedValue = new Binary(truncatedValue);

                     if (s_logger.isDebugEnabled())
                     {
                        s_logger.debug("Value of attribute " + attribute.getName()
                           + " has been truncated to maximum allowed length " + nMaxLength
                           + ". The original value is \"" + binaryValue + "\".");
                     }
                  }
               }
            }

            return validatedValue;
         }
      }

      CompositeMessagePart composite = (CompositeMessagePart)derivedPart;
      TransferObject tobj = (TransferObject)obj;
      Instance instance = (Instance)m_instanceByTobjMap.get(tobj);

      if (instance == null && "delete".equals(tobj.getEventName())) // Obsolete delete
      {
         if (part.isRequired())
         {
            throw new IntegrationException("err.integration.minPartCount", new Object[]{part.getFullPath()});
         }

         return null;
      }

      boolean bNew = (instance == null);

      if (bNew && derivedClass != null)
      {
         instance = new Instance(metaclass, m_context);
         instance.setNew();
         m_instanceByTobjMap.put(tobj, instance);
      }
      else
      {
         if (m_context.isSecure() && instance != null && !instance.isReadable())
         {
            throw new SecurityViolationException("err.rpc.instanceAccess",
               new Object[]{instance.getLazyCaption(), instance.getLazyClassName()});
         }
      }

      if (instance != null && ! m_invocationList.contains(tobj))
      {
         m_invocationList.add(tobj);
      }

      if ((bNew ||
         part.getParent() != null ||
         mapping.isUpdate() &&
         (mapping.getAccessAttribute() == null ||
          Intrinsic.isTrue(instance.getValue(mapping.getAccessAttribute().getOrdinal())))))
      {
         updateInstance(bNew, tobj, instance, composite, mapping);
      }

      return instance;
   }

   /**
    * Gets a system attribute value from a message.
    * @param composite The message part metadata.
    * @param tobj The transfer object containing the data.
    * @param nAttr One of the ObjectMessagePartMapping.ATTR_* constants.
    * @return The system attribute value, or null if not found.
    */
   protected Object getSystemAttributeValue(CompositeMessagePart composite, TransferObject tobj, int nAttr)
   {
      ObjectMessagePartMapping mapping = (ObjectMessagePartMapping)composite.getMapping();
      Object value = null;

      if (mapping != null)
      {
         MessagePart part = mapping.getSystemPart(nAttr);

         if (part != null)
         {
            value =  tobj.findValue(part.getName());
         }

         switch (nAttr)
         {
            case ObjectMessagePartMapping.ATTR_CLASS:
               String sClass = (String)value;

               if (StringUtil.isEmpty(sClass))
               {
                  return mapping.getMetaclass();
               }

               return m_context.getMetadata().getMetaclass(sClass);

            case ObjectMessagePartMapping.ATTR_OID:
               if (value != null && !(value instanceof OID))
               {
                  value = OID.fromBinary(Primitive.toBinary(value));
               }

               return value;
         }
      }

      return value;
   }

   /**
    * Updates attributes on the instance
    * @param composite The message part metadata.
    * @param tobj The object containing update data.
    * @param instance The instance to be updated
    * @param bNew flag whether this is a new instance
    * @param conflictResolution conflict resolution strategy
    */
   protected void updateInstance(CompositeMessagePart composite, TransferObject tobj, Instance instance,
      boolean bNew, ConflictResolutionStrategy conflictResolution)
   {
      for (int i = 0, n = composite.getPartCount(); i != n; ++i)
      {
         MessagePart part = composite.getPart(i);

         if (tobj.hasValue(part.getName()))
         {
            ObjectMessagePartMapping partMapping = (ObjectMessagePartMapping)part.getMapping();

            if (isUpdateRequired(partMapping, instance, bNew))
            {
               Object value = instantiate(tobj.getValue(part.getName()), part);
               Attribute attribute = partMapping.getAttribute();

               if (attribute == null || instance == null)
               {
                  if (partMapping.getSystemAttribute() == ObjectMessagePartMapping.ATTR_OID)
                  {
                     if (value != null && !(value instanceof OID))
                     {
                        instance.setOID(OID.fromBinary(Primitive.toBinary(value)));
                     }
                     else
                     {
                        instance.setOID((OID)value);
                     }
                  }

                  continue;
               }

               if (bNew)
               {
                  instance.setValue(attribute.getOrdinal(), value);
               }
               else
               {
                  if (conflictResolution != null &&
                     !conflictResolution.shouldUpdate(instance, attribute, value))
                  {
                     continue;
                  }

                  if (attribute.isCollection())
                  {
                     InstanceList list = (InstanceList)instance.getValue(attribute.getOrdinal());
                     InstanceList valueList = (InstanceList)value;

                     for (int l = 0; l < valueList.size(); ++l)
                     {
                        Instance newInst = valueList.getInstance(l);

                        if (!list.contains(newInst))
                        {
                           list.add(newInst);
                        }
                     }
                  }
                  else if (!partMapping.isKey() && !ObjUtil.equal(instance.getValue(attribute.getOrdinal()), value))
                  {
                     instance.setValue(attribute.getOrdinal(), value);
                  }
               }
            }
         }
      }
   }

   /**
    * Determine whether an update to this instance is required
    * @param partMapping object message mapping used for the instance
    * @param instance instance being updated
    * @param bNew flag indicating whether the instance is new
    * @return whether an update to this instance is required
    */
   protected boolean isUpdateRequired(ObjectMessagePartMapping partMapping, Instance instance, boolean bNew)
   {
      return bNew ||
          partMapping.isUpdate() &&
          (partMapping.getAccessAttribute() == null ||
           Intrinsic.isTrue(instance.getValue(partMapping.getAccessAttribute().getOrdinal())));
   }

   /**
    * Gets a list from an object and validates it against message part metadata.
    * @param obj The source object.
    * @param msg The message part metadata.
    */
   public static List getList(Object obj, MessagePart msg)
   {
      if (obj == null)
      {
         if (msg.isRequired())
         {
            throw new IntegrationException("err.integration.minPartCount", new Object[]{msg.getFullPath()});
         }

         return Collections.EMPTY_LIST;
      }

      List list = (List)obj;
      int nCount = list.size();

      if (nCount < msg.getMinCount())
      {
         throw new IntegrationException("err.integration.minPartCount", new Object[]{msg.getFullPath()});
      }

      if (nCount > msg.getMaxCount())
      {
         throw new IntegrationException("err.integration.maxPartCount", new Object[]{msg.getFullPath()});
      }

      return list;
   }

   /**
    * @param oldTobj Old transfer object
    * @param newTobj New transfer object
    * @param composite a message part that determines structure of both oldTobj and newTobj
    * @return whether the two given transfer objects have the same key,
    * as defined in the object mapping of the message part
    */
   public static boolean isSameKey(TransferObject oldTobj, TransferObject newTobj,
      CompositeMessagePart composite, InvocationContext context)
   {
      if (oldTobj == null)
      {
         return (newTobj == null);
      }

      if (oldTobj.getOID() != null && newTobj.getOID() != null)
      {
         // An image of a freshly created instance could lack OID
         return oldTobj.getOID().equals(newTobj.getOID());
      }

      boolean bKeyIsPresent = false;
      boolean bSameKey = true;
      boolean bSameValues = true;

      for (int nPartIndex = 0, nPartCount = composite.getPartCount(); nPartIndex != nPartCount; ++nPartIndex)
      {
         MessagePart part = composite.getPart(nPartIndex);
         ObjectMessagePartMapping mapping = (ObjectMessagePartMapping)part.getMapping();
         String sName = part.getName();

         if (newTobj.hasValue(sName) || oldTobj.hasValue(sName))
         {
            boolean bDifferent = newTobj.hasValue(sName) != oldTobj.hasValue(sName);

            if (!bDifferent)
            {
               if (part instanceof CompositeMessagePart && !part.isCollection())
               {
                  CompositeMessagePart derivedMessagePart = getPolymorphic((CompositeMessagePart)part, newTobj,
                     context);

                  bDifferent = !isSameKey((TransferObject)oldTobj.getValue(sName),
                     (TransferObject)newTobj.getValue(sName), derivedMessagePart, context);
               }
               else
               {
                  bDifferent = !ObjUtil.equal(oldTobj.getValue(sName), newTobj.getValue(sName));
               }
            }

            // OID attribute might not be populated;
            bSameValues = bSameValues && (!bDifferent
               || mapping.getSystemAttribute() == ObjectMessagePartMapping.ATTR_OID);

            if (mapping.isKey())
            {
               bKeyIsPresent = true;
               bSameKey = bSameKey && !bDifferent;
            }
         }
      }

      if (bKeyIsPresent)
      {
         return bSameKey;
      }

      return bSameValues && "create".equals(oldTobj.getEventName())
         && "create".equals(newTobj.getEventName());
   }

   // abstract methods

   /**
    * @param bNew True, if it is a new instance.
    * @param tobj The transfer object.
    * @param instance The instance.
    * @param composite The message part metadata.
    * @param mapping The message mapping.
    */
   protected abstract Object updateInstance(boolean bNew, TransferObject tobj, Instance instance,
      CompositeMessagePart composite, ObjectMessagePartMapping mapping);

   // inner classes

   /**
    * Formats a collection of messages in a fail safe way by formatting collection chunks
    * on separate units of work and collecting all exceptions
    */
   private class FailSafeFormatter
   {
      /**
       * Defines into how many chunks a collection is split on each recursion.
       * Formatting of each chunk is attempted on a single unit of work.
       */
      // Our chunking uses few queries for each instance anyway - better to process objects one-by-one
      private final static int CHUNK_COUNT = 10000;
      // TODO: either optimize chunking SQL,
      // or remove chunking code and always process instances one-by-one in a case of failure.

      /**
       * Holder that accumulates all the exceptions raised during formatter execution
       */
      private final CompoundIntegrationException m_ex;

      /**
       * Flag whether transaction should be committed in case when some parts of the message raise an exception
       */
      private final boolean m_bCommit;

      /**
       * Constructor
       */
      private FailSafeFormatter(CompoundIntegrationException ex, boolean bCommit)
      {
         m_ex = ex;
         m_bCommit = bCommit;
      }

      /**
       * Format the given map of object messages by dividing it into chunks
       * and formatting each chunk on a separate unit of work
       * @param objectParts object part messages mapped by corresponding transfer objects:
       * Message[TransferObject]
       * @return collection of results of successful formatting
       */
      public Collection divideAndFormat(Lookup objectParts)
      {
         int nMapSize = objectParts.size();
         Collection result = new ArrayList(nMapSize);
         int nChunkSize = (nMapSize + CHUNK_COUNT - 1) / CHUNK_COUNT;
         Lookup chunk = new HashTab(nChunkSize);

         if (s_logger.isDebugEnabled())
         {
            s_logger.debug("Dividing message of size " + objectParts.size() + " into chunks of size " + nChunkSize);
         }

         for (Lookup.Iterator itr = objectParts.iterator(); itr.hasNext(); )
         {
            chunk.put(itr.next(), itr.getValue());
           
            if (chunk.size() == nChunkSize)
            {
               result.addAll(formatChunk(chunk, 0));
               chunk.clear();
            }
         }

         result.addAll(formatChunk(chunk, 0));

         return result;
      }

      /**
       * Format the given map of object messages on a separate unit of work
       * @param chunk object part messages mapped by corresponding transfer objects: Message[TransferObject]
       * @return collection of results of successful formatting
       */
      public Collection formatChunk(Lookup chunk, int nRetry)
      {
         if (chunk.size() == 0)
         {
            return Collections.EMPTY_LIST;
         }

         if (s_logger.isDebugEnabled())
         {
            s_logger.debug("Formatting chunk of size " + chunk.size() + (nRetry == 0 ? "" : ", retry #" + nRetry));
         }

         // We use unit of work to recover from database failures
         UnitOfWork oldUOW = m_context.beginTransaction();

         try
         {
            Collection result = new ArrayList(chunk.size());

            for (Lookup.Iterator itr = chunk.iterator(); itr.hasNext();)
            {
               result.add(format((TransferObject)itr.next(), (CompositeMessagePart)itr.getValue()));
            }

            if (m_bCommit)
            {
               m_context.commitAndResume(oldUOW);
            }
            else
            {
               m_context.rollbackAndResume(oldUOW);
            }

            if (s_logger.isDebugEnabled())
            {
               s_logger.debug("Successfully " + (m_bCommit ? "committed" : "rolled back")
                  + " chunk of size " + chunk.size());

               s_logger.dump(chunk);
            }

            return result;
         }
         catch (Throwable e)
         {
            m_context.rollbackAndResume(oldUOW);

            if ((nRetry < MAX_RETRIES) && DefaultSyncEngine.isTransient(e))
            {
               return formatChunk(chunk, nRetry + 1);
            }

            if (chunk.size() == 1)
            {
               TransferObject tobj = (TransferObject) chunk.iterator().next();

               m_ex.addException(e, tobj);

               if (s_logger.isDebugEnabled())
               {
                  s_logger.debug("Caught single exception ", e);
                  s_logger.dump(tobj);
               }

               return Collections.EMPTY_LIST;
            }
            else
            {
               return divideAndFormat(chunk);
            }
         }
      }
   }

   /**
    * Interface implemented by conflict resolution strategies.
    */
   protected interface ConflictResolutionStrategy
   {
      /**
       * Advises whether NexJ hub should update a particular attribute to a value received from a spoke,
       * in case when there is a conflict with the current value of this attribute on the hub.
       * @param attribute The attribute in conflict.
       * @return true if this conflict resolution algorithm decides that spoke should win this conflict.
       */
      boolean isUpdateRequired(Attribute attribute);

      /**
       * @return whether there was a conflict win for the hub on the given attribute value.
       * @param attribute The attribute in conflict.
       */
      boolean isConflictWon(Attribute attribute);

      /**
       * @return True, if this conflict resolution algorithm decides that external system wins this conflict.
       * Otherwise, false.
       * @param instance The internal instance.
       * @param attribute The attribute.
       * @param value The attribute value.
       */
      boolean shouldUpdate(Instance instance, Attribute attribute, Object value);
   }
}
TOP

Related Classes of nexj.core.integration.format.object.ObjectMessageFormatterStrategy$ConflictResolutionStrategy

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.