]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-7083: Adding tenant bindings for declaring Nuxeo event listeners.
authorremillet <remillet@yahoo.com>
Fri, 10 Mar 2017 20:12:02 +0000 (12:12 -0800)
committerremillet <remillet@yahoo.com>
Fri, 10 Mar 2017 20:12:02 +0000 (12:12 -0800)
3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/AbstractUpdateObjectLocationValues.java
3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationAndCrateOnMove.java [new file with mode: 0644]
3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationOnMove.java
services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml
services/common/src/main/cspace/config/services/tenants/testsci/testsci-tenant-bindings.delta.xml
services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java
services/common/src/main/java/org/collectionspace/services/nuxeo/listener/AbstractCSEventListenerImpl.java [new file with mode: 0644]
services/common/src/main/java/org/collectionspace/services/nuxeo/listener/CSEventListener.java [new file with mode: 0644]
services/common/src/main/java/org/collectionspace/services/nuxeo/util/CSEventServiceComponent.java [new file with mode: 0644]
services/common/src/main/java/org/collectionspace/services/nuxeo/util/CSEventServiceImpl.java [new file with mode: 0644]
services/config/src/main/resources/tenant.xsd

index 0f4826ae9ccf72965f7d47c4c3edbe5cac542a52..9430b3e925f364f3026684e75a393665181ba95a 100644 (file)
@@ -1,9 +1,8 @@
 package org.collectionspace.services.listener;
 
-import java.util.Calendar;
 import java.util.GregorianCalendar;
 import java.util.HashSet;
-import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.commons.logging.Log;
@@ -17,6 +16,7 @@ import org.collectionspace.services.common.api.RefName;
 import org.collectionspace.services.movement.nuxeo.MovementConstants;
 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
 import org.collectionspace.services.nuxeo.client.java.CoreSessionWrapper;
+import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl;
 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
 import org.nuxeo.common.collections.ScopeType;
 import org.nuxeo.common.collections.ScopedMap;
@@ -24,14 +24,11 @@ import org.nuxeo.ecm.core.api.ClientException;
 import org.nuxeo.ecm.core.api.DocumentModel;
 import org.nuxeo.ecm.core.api.DocumentModelList;
 import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
-import org.nuxeo.ecm.core.api.impl.DocumentModelImpl;
 import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
 import org.nuxeo.ecm.core.event.Event;
-import org.nuxeo.ecm.core.event.EventListener;
 import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
-import org.nuxeo.ecm.core.versioning.VersioningService;
 
-public abstract class AbstractUpdateObjectLocationValues implements EventListener {
+public abstract class AbstractUpdateObjectLocationValues extends AbstractCSEventListenerImpl {
 
     // FIXME: We might experiment here with using log4j instead of Apache Commons Logging;
     // am using the latter to follow Ray's pattern for now
@@ -75,8 +72,8 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
             "AND (ecm:currentLifeCycleState <> 'deleted') "
             + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
 
+    // Used to set/get temp values in a DocumentModel instance
        private static final String IGNORE_LOCATION_UPDATE_EVENT_LABEL = "IGNORE_LOCATION_UPDATE_EVENT";
-       private static final String IGNORE_LOCATION_UPDATE_EVENT_OPTION = ScopeType.DEFAULT.getScopePrefix() + "IGNORE_LOCATION_UPDATE_EVENT";
     
     public enum EventNotificationDocumentType {
         // Document type about which we've received a notification
@@ -84,8 +81,12 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
         MOVEMENT, RELATION, COLLECTIONOBJECT;
     }
     
-    private static void dumpEvent(Event event, String message) {
-       if (logger.isDebugEnabled()) {
+    private static void logEvent(Event event, String message) {
+       logEvent(event, message, false);
+    }
+    
+    private static void logEvent(Event event, String message, boolean forceLogging) {
+       if (logger.isDebugEnabled() || forceLogging) {
                DocumentEventContext docEventContext = (DocumentEventContext) event.getContext();
                DocumentModel docModel = docEventContext.getSourceDocument();
                String eventType = event.getName();
@@ -138,12 +139,17 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
 
     @Override
     public void handleEvent(Event event) throws ClientException {
-       dumpEvent(event, "Update Location");
         // Ensure we have all the event data we need to proceed.
-        if (event.getContext() == null || !(event.getContext() instanceof DocumentEventContext)) {
+        if (isRegistered(event) == false || !(event.getContext() instanceof DocumentEventContext)) {
+               if (logger.isTraceEnabled() == true) {
+                       logEvent(event, "Update Location", true);
+               }
             return;
         }
 
+        Map<String, String> params = this.getParams(event);
+        logEvent(event, "Update Location");
+
         DocumentEventContext docEventContext = (DocumentEventContext) event.getContext();
         DocumentModel eventDocModel = docEventContext.getSourceDocument();        
         String eventType = event.getName();
@@ -155,7 +161,7 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
         // exits if it is set.
        if (shouldIgnoreEvent(docEventContext, IGNORE_LOCATION_UPDATE_EVENT_LABEL) == true) {
                return;
-       }        
+       }
 
         //
         // Ensure this event relates to a relationship record (between cataloging and movement records) or a movement record.  If so, get the CSID
@@ -235,23 +241,24 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
         DocumentModel mostRecentMovementDocModel;
         for (String collectionObjectCsid : collectionObjectCsids) {            
             collectionObjectDocModel = getCurrentDocModelFromCsid(session, collectionObjectCsid);
-            if (isActiveDocument(collectionObjectDocModel) == true) {           
+            if (isActiveDocument(collectionObjectDocModel) == true) {
+               DocumentModel movementDocModel = getCurrentDocModelFromCsid(session, eventMovementCsid);
                    //
                    // Get the CollectionObject's most recent, valid related Movement to use for computing the
                    // object's current location.
                    //
-                               String mostRecentLocation = getMostRecentMovement(event, session, collectionObjectCsid,
+                               String mostRecentLocation = getMostRecentLocation(event, session, collectionObjectCsid,
                                                isAboutToBeRemovedEvent, eventMovementCsid);
                    //
                    // Update the CollectionObject's Computed Current Location field with the Movement record's location
                                //
-                   boolean didLocationChange = updateCollectionObjectLocation(collectionObjectDocModel, mostRecentLocation);
+                   boolean didLocationChange = updateCollectionObjectLocation(collectionObjectDocModel, movementDocModel, mostRecentLocation);
                    
                    //
                    // If the location changed, save/persist the change to the repository and log the change.
                    //
                if (didLocationChange == true) {
-                       persisLocationChange(session, collectionObjectDocModel);
+                       persistLocationChange(session, collectionObjectDocModel);
                    //
                    // Log an INFO message if we've changed the cataloging record's location
                    //              
@@ -269,13 +276,12 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
     //
     // Disable update/documentModified events and persist the location change.
     //
-    private void persisLocationChange(CoreSessionInterface session, DocumentModel collectionObjectDocModel) {
+    private void persistLocationChange(CoreSessionInterface session, DocumentModel collectionObjectDocModel) {
        
        //
        // Set a flag in the document model indicating that we want to ignore the update event that
        // will be triggered by this save/persist request.
-       ScopedMap contextData = collectionObjectDocModel.getContextData();
-       contextData.putIfAbsent(IGNORE_LOCATION_UPDATE_EVENT_OPTION, true);
+       setDocModelContextProperty(collectionObjectDocModel, IGNORE_LOCATION_UPDATE_EVENT_LABEL, true);
 
        //
        // Save/Persist the document to the DB
@@ -283,7 +289,7 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
         
         //
         // Clear the flag we set to ignore events triggered by our save request.
-       contextData.remove(IGNORE_LOCATION_UPDATE_EVENT_OPTION);
+        clearDocModelContextProperty(collectionObjectDocModel, IGNORE_LOCATION_UPDATE_EVENT_LABEL);
     }
 
     /**
@@ -409,30 +415,30 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
      * un-retrievable via their CSIDs.
      *
      * @param session a repository session.
-     * @param collectionObjectCsid a CollectionObject identifier (CSID)
+     * @param csid a CollectionObject identifier (CSID)
      * @return a document model for the document identified by the supplied
      * CSID.
      */
-    protected static DocumentModel getCurrentDocModelFromCsid(CoreSessionInterface session, String collectionObjectCsid) {
+    protected static DocumentModel getCurrentDocModelFromCsid(CoreSessionInterface session, String csid) {
         DocumentModelList docModelList = null;
         
         try {
             final String query = "SELECT * FROM "
                     + NuxeoUtils.BASE_DOCUMENT_TYPE
                     + " WHERE "
-                    + NuxeoUtils.getByNameWhereClause(collectionObjectCsid)
+                    + NuxeoUtils.getByNameWhereClause(csid)
                     + " "
                     + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
             docModelList = session.query(query);
         } catch (Exception e) {
-            logger.warn("Exception in query to get active document model for CollectionObject: ", e);
+            logger.warn("Exception in query to get active document model for CSID: " + csid, e);
         }
         
         if (docModelList == null || docModelList.isEmpty()) {
-            logger.warn("Could not get active document models for CollectionObject(s).");
+            logger.warn("Could not get active document models for CSID=" + csid);
             return null;
         } else if (docModelList.size() != 1) {
-            logger.error("Found more than 1 active document with CSID=" + collectionObjectCsid);
+            logger.error("Found more than 1 active document with CSID=" + csid);
             return null;
         }
         
@@ -487,7 +493,7 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
      * @return the most recent Movement record related to the CollectionObject
      * identified by the supplied CSID.
      */
-    protected static String getMostRecentMovement(Event event,
+    protected String getMostRecentLocation(Event event,
                CoreSessionInterface session, String collectionObjectCsid,
             boolean isAboutToBeRemovedEvent, String eventMovementCsid)
             throws ClientException {
@@ -761,6 +767,7 @@ public abstract class AbstractUpdateObjectLocationValues implements EventListene
      * @throws ClientException
      */
     protected abstract boolean updateCollectionObjectLocation(DocumentModel collectionObjectDocModel,
+               DocumentModel movmentDocModel,
                String movementRecordsLocation)
             throws ClientException;
 }
\ No newline at end of file
diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationAndCrateOnMove.java b/3rdparty/nuxeo/nuxeo-platform-listener/updateobjectlocationonmove/src/main/java/org/collectionspace/services/listener/UpdateObjectLocationAndCrateOnMove.java
new file mode 100644 (file)
index 0000000..cd593de
--- /dev/null
@@ -0,0 +1,90 @@
+package org.collectionspace.services.listener;
+
+import java.io.Serializable;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.collectionspace.services.common.api.RefNameUtils;
+import org.collectionspace.services.common.api.Tools;
+import org.nuxeo.ecm.core.api.ClientException;
+import org.nuxeo.ecm.core.api.DocumentModel;
+
+public class UpdateObjectLocationAndCrateOnMove extends UpdateObjectLocationOnMove {
+
+    // FIXME: We might experiment here with using log4j instead of Apache Commons Logging;
+    // am using the latter to follow Ray's pattern for now
+    private final Log logger = LogFactory.getLog(UpdateObjectLocationAndCrateOnMove.class);
+    // FIXME: Get values below from external constants
+    private final static String COLLECTIONOBJECTS_ANTHROPOLOGY_SCHEMA = "collectionobjects_anthropology";
+    private final static String MOVEMENTS_ANTHROPOLOGY_SCHEMA = "movements_anthropology";
+    private final static String CRATE_PROPERTY = "crate";
+    private final static String COMPUTED_CRATE_PROPERTY = "computedCrate";
+
+    @Override
+    protected boolean updateCollectionObjectLocation(DocumentModel collectionObjectDocModel,
+            DocumentModel movementDocModel,
+            String mostRecentLocation) throws ClientException {
+        boolean flag = super.updateCollectionObjectLocation(collectionObjectDocModel, movementDocModel, mostRecentLocation);
+        collectionObjectDocModel = updateComputedCrateValue(collectionObjectDocModel, movementDocModel);
+        
+        return flag;
+    }
+
+    private DocumentModel updateComputedCrateValue(DocumentModel collectionObjectDocModel,
+            DocumentModel movementDocModel)
+            throws ClientException {
+        
+        // Get the current crate value from the Movement (the "new" value)
+        String crateRefName =
+                (String) movementDocModel.getProperty(MOVEMENTS_ANTHROPOLOGY_SCHEMA, CRATE_PROPERTY);
+
+        // Check that the value returned, which is expected to be a
+        // reference (refName) to an authority term:
+        //
+        // * If it is not blank ...
+        // * Is then capable of being successfully parsed by an authority item parser.
+        if (Tools.notBlank(crateRefName)
+                && RefNameUtils.parseAuthorityTermInfo(crateRefName) == null) {
+            logger.warn("Could not parse crate refName '" + crateRefName + "'");
+            return collectionObjectDocModel;
+        } else {
+            if (logger.isTraceEnabled()) {
+                logger.trace("crate refName passes basic validation tests.");
+                logger.trace("crate refName=" + crateRefName);
+            }
+        }
+        // Get the computed crate value of the CollectionObject
+        // (the "existing" value)
+        String existingCrateRefName =
+                (String) collectionObjectDocModel.getProperty(COLLECTIONOBJECTS_ANTHROPOLOGY_SCHEMA,
+                COMPUTED_CRATE_PROPERTY);
+        if (logger.isTraceEnabled()) {
+            logger.trace("Existing crate refName=" + existingCrateRefName);
+        }
+
+        // If the new value is blank, any non-blank existing value should always
+        // be overwritten ('nulled out') with a blank value.
+        if (Tools.isBlank(crateRefName) && Tools.notBlank(existingCrateRefName)) {
+            collectionObjectDocModel.setProperty(COLLECTIONOBJECTS_ANTHROPOLOGY_SCHEMA,
+                    COMPUTED_CRATE_PROPERTY, (Serializable) null);
+            // Otherwise, if the new value is not blank, and
+            // * the existing value is blank, or
+            // * the new value is different than the existing value ...
+        } else if (Tools.notBlank(crateRefName) &&
+                    (Tools.isBlank(existingCrateRefName)
+                    || !crateRefName.equals(existingCrateRefName))) {
+            if (logger.isTraceEnabled()) {
+                logger.trace("crate refName requires updating.");
+            }
+            // ... update the existing value in the CollectionObject with the
+            // new value from the Movement.
+            collectionObjectDocModel.setProperty(COLLECTIONOBJECTS_ANTHROPOLOGY_SCHEMA,
+                    COMPUTED_CRATE_PROPERTY, crateRefName);
+        } else {
+            if (logger.isTraceEnabled()) {
+                logger.trace("crate refName does NOT require updating.");
+            }
+        }
+        
+        return collectionObjectDocModel;
+    }
+}
\ No newline at end of file
index 93b98b20472f4b63b97e3253f67181e3e652e5b5..819197a65316ed6809ebc9792e448b5d1591eb43 100644 (file)
@@ -4,7 +4,6 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.collectionspace.services.common.api.RefNameUtils;
 import org.collectionspace.services.common.api.Tools;
-import org.collectionspace.services.nuxeo.util.NuxeoUtils;
 import org.nuxeo.ecm.core.api.ClientException;
 import org.nuxeo.ecm.core.api.DocumentModel;
 
@@ -16,6 +15,7 @@ public class UpdateObjectLocationOnMove extends AbstractUpdateObjectLocationValu
 
     @Override
     protected boolean updateCollectionObjectLocation(DocumentModel collectionObjectDocModel,
+               DocumentModel movementDocModel,
                String movementRecordsLocation) throws ClientException {
        boolean result = false;
 
index b373c5d8536e8abe13d538eb2f081e84856b1240..c57065a4fdceb90b2738d529ac7a5b033194548b 100644 (file)
@@ -2,6 +2,42 @@
 <tenant:TenantBindingConfig xmlns:merge="http://xmlmerge.el4j.elca.ch" xmlns:tenant="http://collectionspace.org/services/config/tenant">
 
        <tenant:tenantBinding>
+           <tenant:eventListenerConfigurations id="default">
+               <tenant:eventListenerConfig id="UpdateObjectLocationOnMove">
+                   <tenant:className>org.collectionspace.services.listener.UpdateObjectLocationOnMove</tenant:className>
+                   <tenant:paramList id="default">
+                       <tenant:param>
+                           <tenant:key>key0</tenant:key>
+                           <tenant:value>value0</tenant:value>
+                           </tenant:param>
+                       <tenant:param>
+                           <tenant:key>key1</tenant:key>
+                           <tenant:value>value1</tenant:value>
+                           </tenant:param>
+                       <tenant:param>
+                           <tenant:key>key2</tenant:key>
+                           <tenant:value>value2</tenant:value>
+                           </tenant:param>
+                   </tenant:paramList>
+               </tenant:eventListenerConfig>
+               <tenant:eventListenerConfig id="UpdateObjectLocationAndCrateOnMove">
+                   <tenant:className>org.collectionspace.services.listener.UpdateObjectLocationAndCrateOnMove</tenant:className>
+                   <tenant:paramList id="default">
+                       <tenant:param>
+                           <tenant:key>crate-key0</tenant:key>
+                           <tenant:value>value0</tenant:value>
+                           </tenant:param>
+                       <tenant:param>
+                           <tenant:key>crate-key1</tenant:key>
+                           <tenant:value>value1</tenant:value>
+                           </tenant:param>
+                       <tenant:param>
+                           <tenant:key>crate-key2</tenant:key>
+                           <tenant:value>value2</tenant:value>
+                           </tenant:param>
+                   </tenant:paramList>
+               </tenant:eventListenerConfig>
+           </tenant:eventListenerConfigurations>
                <tenant:properties>
                        <!-- Controls whether term completion (aka partial term matching, aka autocomplete) searches will automatically insert 
                                a leading wildcard. The default value is 'true', which will cause your search expression to be matched when found anywhere 
                        </types:item>
                </tenant:properties>
 
-               <!--
-               <tenant:serviceBindings id="Vocabularyitems" merge:matcher="id">
-                       <service:DocHandlerParams xmlns:service="http://collectionspace.org/services/config/service">
-                               <service:params>
-                                       <service:RefnameDisplayNameField>
-                                               <service:ListResultField>
-                                                       <service:element>order</service:element>
-                                                       <service:xpath>order</service:xpath>
-                                               </service:ListResultField>
-                                       </service:RefnameDisplayNameField>
-                               </service:params>
-                       </service:DocHandlerParams>
-               </tenant:serviceBindings>
-               -->
-
                <!-- begin idgenerator service meta-data -->
                <tenant:serviceBindings id="idgenerators" merge:matcher="id" name="idgenerators" type="utility"
                        version="1.0">
index 87f2760dc3ac663206d028222c2592821d44fc55..2892ae366a944279670fc94890e419dc972a01a0 100644 (file)
@@ -8,6 +8,25 @@
     <!-- value in cspace/config/services/tenants/testsci-tenant-bindings-proto.xml -->
 
     <tenant:tenantBinding id="2">
+               <tenant:eventListenerConfigurations id="default" merge:matcher="id">
+            <tenant:eventListenerConfig id="UpdateObjectLocationOnMove" merge:matcher="id">
+                <tenant:paramList id="default" merge:matcher="id" merge:action="replace">
+                       <tenant:param>
+                           <tenant:key>testsci-key0</tenant:key>
+                           <tenant:value>value0</tenant:value>
+                           </tenant:param>
+                       <tenant:param>
+                           <tenant:key>testsci-key1</tenant:key>
+                           <tenant:value>value1</tenant:value>
+                           </tenant:param>
+                       <tenant:param>
+                           <tenant:key>testsci-key2</tenant:key>
+                           <tenant:value>value2</tenant:value>
+                           </tenant:param>
+                   </tenant:paramList>
+            </tenant:eventListenerConfig>
+            <tenant:eventListenerConfig id="UpdateObjectLocationAndCrateOnMove" merge:matcher="id" merge:action="delete">
+            </tenant:eventListenerConfig>            
+        </tenant:eventListenerConfigurations>  
     </tenant:tenantBinding>
-
 </tenant:TenantBindingConfig>
index 9821be5f718766c4d340384d31082e1afa5fe4e2..b15ba97ed3325b7e93e0d100735cc808a34f66ac 100644 (file)
@@ -16,6 +16,7 @@ import javax.naming.NamingException;
 import javax.servlet.ServletContext;
 import javax.sql.DataSource;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.tomcat.dbcp.dbcp.BasicDataSource;
 import org.collectionspace.authentication.AuthN;
 import org.collectionspace.services.client.XmlTools;
@@ -36,14 +37,16 @@ import org.collectionspace.services.common.storage.JDBCTools;
 import org.collectionspace.services.config.ClientType;
 import org.collectionspace.services.config.ServiceConfig;
 import org.collectionspace.services.config.service.ServiceBindingType;
+import org.collectionspace.services.config.tenant.EventListenerConfig;
+import org.collectionspace.services.config.tenant.EventListenerConfigurations;
 import org.collectionspace.services.config.tenant.RepositoryDomainType;
 import org.collectionspace.services.config.tenant.TenantBindingType;
 import org.collectionspace.services.config.types.PropertyItemType;
 import org.collectionspace.services.config.types.PropertyType;
 import org.collectionspace.services.nuxeo.client.java.NuxeoConnectorEmbedded;
 import org.collectionspace.services.nuxeo.client.java.TenantRepository;
-
-import org.apache.commons.io.FileUtils;
+import org.collectionspace.services.nuxeo.listener.CSEventListener;
+import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.dom4j.Document;
 import org.slf4j.Logger;
@@ -57,7 +60,8 @@ import org.slf4j.LoggerFactory;
 public class ServiceMain {
 
     final static Logger logger = LoggerFactory.getLogger(ServiceMain.class);
-    
+       private static final int PRIMARY_REPOSITORY_DOMAIN = 0;
+
     /**
      * For some reason, we have trouble getting logging from this class directed to
      * the tomcat/catalina console log file.  So we do it explicitly with this method.
@@ -186,7 +190,7 @@ public class ServiceMain {
         String[] dataSourceNames = {JDBCTools.NUXEO_DATASOURCE_NAME, JDBCTools.NUXEO_READER_DATASOURCE_NAME};
         updateInitializationScript(getNuxeoDatabasesInitScriptFilename(),
                 dbsCheckedOrCreated, dataSourceNames);
-
+        
         //
         // Start up and initialize our embedded Nuxeo instance.
         //
@@ -204,6 +208,11 @@ public class ServiceMain {
                //
                throw new RuntimeException("Unknown CollectionSpace services client type: " + getClientType());
         }
+        
+        //
+        //
+        //
+        initializeEventListeners();        
                 
         //
         // Create all the default user accounts and permissions.  Since some of our "cspace" database config files
@@ -233,8 +242,93 @@ public class ServiceMain {
         */
                showTenantStatus();
     }
+        
+    /**
+     * Returns the primary repository name for a tenant -there's usually just one.
+     * @param tenantBinding
+     * @return
+     * @throws InstantiationException
+     */
     
-    private void showTenantStatus() {
+       protected String getPrimaryRepositoryName(TenantBindingType tenantBinding) throws InstantiationException {
+       String result = "default";
+       
+       List<RepositoryDomainType> repositoryDomainList = tenantBinding.getRepositoryDomain();
+       if (repositoryDomainList != null && repositoryDomainList.isEmpty() == false) {
+               String repositoryName = repositoryDomainList.get(PRIMARY_REPOSITORY_DOMAIN).getRepositoryName();
+               if (repositoryName != null && !repositoryName.isEmpty()) {
+                       result = repositoryName;
+               }
+       } else {
+               String msg = String.format("Tenant bindings for '%s' is missing a repositoryDomain element in its bindings file.",
+                               tenantBinding.getName());
+               logger.error(msg);
+               throw new InstantiationException(msg);
+       }
+       
+       return result;
+    }
+    
+    /**
+     * Initialize the event listeners.  We're essentially registering listeners with tenants.  This ensures that listeners ignore events
+     * caused by other tenants.
+     */
+    private void initializeEventListeners() {
+       Hashtable<String, TenantBindingType> tenantBindings = this.tenantBindingConfigReader.getTenantBindings();
+       
+        for (TenantBindingType tenantBinding : tenantBindings.values()) {
+               EventListenerConfigurations eventListenerConfigurations = tenantBinding.getEventListenerConfigurations();
+               if (eventListenerConfigurations != null) {
+                       List<EventListenerConfig> eventListenerConfigList = eventListenerConfigurations.getEventListenerConfig();
+                ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+                       for (EventListenerConfig eventListenerConfig : eventListenerConfigList) {
+                               String clazz = eventListenerConfig.getClassName().trim();
+                               if (clazz.isEmpty() == false) {
+                           try {
+                                   Class<?> c = tccl.loadClass(clazz);
+                                   if (CSEventListener.class.isAssignableFrom(c)) {
+                                       CSEventListener listener = (AbstractCSEventListenerImpl) c.newInstance();
+                                       listener.register(getPrimaryRepositoryName(tenantBinding), eventListenerConfig);  // Register the listener with a tenant using its repository name
+                                       if (logger.isInfoEnabled()) {
+                                               String msg = String.format("Event Listener - Success: Tenant '%30s'\tActivated listener %s:%s",
+                                                               tenantBinding.getName(), eventListenerConfig.getId(), clazz);
+                                               logger.info(msg);
+                                       }
+                                   }
+                           } catch (ClassNotFoundException e) {
+                               String msg = String.format("Event Listener - FAILURE: Tenant '%30s'\tFailed to find event listener %s:%s",
+                                               tenantBinding.getName(), eventListenerConfig.getId(), clazz);
+                               logger.warn(msg);
+                               logger.trace(msg, e);
+                               failIfRequired(eventListenerConfig);
+                           } catch (InstantiationException e) {
+                               String msg = String.format("Event Listener - FAILURE: Tenant '%30s'\tFailed to instantiate event listener %s:%s",
+                                               tenantBinding.getName(), eventListenerConfig.getId(), clazz);
+                               logger.warn(msg);
+                               logger.trace(msg, e);
+                               failIfRequired(eventListenerConfig);
+                                               } catch (IllegalAccessException e) {
+                               String msg = String.format("Event Listener - FAILURE: Tenant '%30s'\tIllegal access to event listener %s:%s",
+                                               tenantBinding.getName(), eventListenerConfig.getId(), clazz);
+                               logger.warn(msg);
+                               logger.trace(msg, e);
+                               failIfRequired(eventListenerConfig);
+                                               }
+                               }
+                       }
+               }
+               logger.info("\n");
+        }
+       }
+
+       private void failIfRequired(EventListenerConfig eventListenerConfig) {
+               if (eventListenerConfig.isRequired() == true) {
+                       throw new RuntimeException(String.format("Required event listener '%s' missing or could not be instantiated.", eventListenerConfig.getId()));
+               }
+               
+       }
+
+       private void showTenantStatus() {
        Hashtable<String,TenantBindingType> tenantBindingsList = tenantBindingConfigReader.getTenantBindings(true);
        mirrorToStdOut("++++++++++++++++ Summary - CollectionSpace tenant status. ++++++++++++++++++++++++");
        String headerTemplate = "%10s %10s %30s %60s %10s";
diff --git a/services/common/src/main/java/org/collectionspace/services/nuxeo/listener/AbstractCSEventListenerImpl.java b/services/common/src/main/java/org/collectionspace/services/nuxeo/listener/AbstractCSEventListenerImpl.java
new file mode 100644 (file)
index 0000000..ed6579b
--- /dev/null
@@ -0,0 +1,130 @@
+package org.collectionspace.services.nuxeo.listener;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.collectionspace.services.common.api.Tools;
+import org.collectionspace.services.config.tenant.EventListenerConfig;
+import org.collectionspace.services.config.tenant.Param;
+import org.nuxeo.common.collections.ScopeType;
+import org.nuxeo.common.collections.ScopedMap;
+import org.nuxeo.ecm.core.api.DocumentModel;
+import org.nuxeo.ecm.core.event.Event;
+
+public abstract class AbstractCSEventListenerImpl implements CSEventListener {
+       private static List<String> repositoryNameList = new ArrayList<String>();
+       private static Map<String, Map<String, Map<String, String>>> eventListenerParamsMap = new HashMap<String, Map<String, Map<String, String>>>();  // <repositoryName, Map<EventListenerId, Map<key, value>>>
+       private static Map<String, String> nameMap = new HashMap<String, String>();
+       
+       static final String DOCMODEL_CONTEXT_PROPERTY_PREFIX = ScopeType.DEFAULT.getScopePrefix();
+       
+       public AbstractCSEventListenerImpl() {
+               // Intentionally left blank
+       }
+       
+       protected List<String> getRepositoryNameList() {
+               return repositoryNameList;
+       }
+       
+       protected Map<String, Map<String, Map<String, String>>> getEventListenerParamsMap() {
+               return eventListenerParamsMap;
+       }
+
+       @Override
+       public boolean isRegistered(Event event) {
+               boolean result = false;
+               
+               if (event != null && event.getContext() != null) {
+                       result = repositoryNameList.contains(event.getContext().getRepositoryName());
+               }
+               
+               return result;
+       }
+
+
+       /**
+        * Returns 'true' if this collection changed as a result of the call. 
+        */
+       @Override
+       public boolean register(String respositoryName, EventListenerConfig eventListenerConfig) {
+               boolean result = false;
+               
+               // Using the repositoryName as a qualifier, register this event listener's name as specified in the tenant bindings.
+               setName(respositoryName, eventListenerConfig.getId());
+               
+               // Register this event listener with the given repository name
+               if (getRepositoryNameList().add(respositoryName)) {
+                       result = true;
+               }
+               
+               // Set this event listeners parameters, if any.  Params are qualified with the repositoryName since multiple tenants might be registering the same event listener but with different params.
+               List<Param> paramList = eventListenerConfig.getParamList().getParam(); // values from the tenant bindings that we need to copy into the event listener
+               if (paramList != null) {
+                       //
+                       // Get the list of event listeners for a given repository
+                       Map<String, Map<String, String>> eventListenerRepoParams = getEventListenerParamsMap().get(respositoryName); // Get the set of event listers for a given repository
+                       if (eventListenerRepoParams == null) {
+                               eventListenerRepoParams = new HashMap<String, Map<String, String>>();
+                               getEventListenerParamsMap().put(respositoryName, eventListenerRepoParams); // create and put an empty map
+                               result = true;
+                       }
+                       //
+                       // Get the list of params for a given event listener for a given repository
+                       Map<String, String> eventListenerParams = eventListenerRepoParams.get(eventListenerConfig.getId()); // Get the set of params for a given event listener for a given repository
+                       if (eventListenerParams == null) {
+                               eventListenerParams = new HashMap<String, String>();
+                               eventListenerRepoParams.put(eventListenerConfig.getId(), eventListenerParams); // create and put an empty map
+                               result = true;
+                       }
+                       //
+                       // copy all the values from the tenant bindings into the event listener
+                       for (Param params : paramList) {
+                               String key = params.getKey();
+                               String value = params.getValue();
+                               if (Tools.notBlank(key)) {
+                                       eventListenerParams.put(key, value);
+                                       result = true;
+                               }
+                       }
+               }
+               
+               return result;
+       }
+       
+       protected void setName(String repositoryName, String eventListenerName) {
+               nameMap.put(repositoryName, eventListenerName);
+       }
+
+       @Override
+       public Map<String, String> getParams(Event event) {
+               String repositoryName = event.getContext().getRepositoryName();
+               return getEventListenerParamsMap().get(repositoryName).get(getName(repositoryName));  // We need to qualify with the repositoryName since this event listener might be register by multiple tenants using different params
+       }
+       
+       @Override
+       public String getName(String repositoryName) {
+               return nameMap.get(repositoryName);
+       }
+       
+       //
+       // Set a property in the document model's transient context.
+       //
+       @Override
+    public void setDocModelContextProperty(DocumentModel collectionObjectDocModel, String key, Serializable value) {
+       ScopedMap contextData = collectionObjectDocModel.getContextData();
+       contextData.putIfAbsent(DOCMODEL_CONTEXT_PROPERTY_PREFIX + key, value);        
+    }
+       
+    //
+    // Clear a property from the docModel's context
+       //
+       @Override
+       public void clearDocModelContextProperty(DocumentModel docModel, String key) {
+       ScopedMap contextData = docModel.getContextData();
+       contextData.remove(DOCMODEL_CONTEXT_PROPERTY_PREFIX + key);     
+       }
+
+}
diff --git a/services/common/src/main/java/org/collectionspace/services/nuxeo/listener/CSEventListener.java b/services/common/src/main/java/org/collectionspace/services/nuxeo/listener/CSEventListener.java
new file mode 100644 (file)
index 0000000..22b2868
--- /dev/null
@@ -0,0 +1,55 @@
+package org.collectionspace.services.nuxeo.listener;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.collectionspace.services.config.tenant.EventListenerConfig;
+import org.nuxeo.ecm.core.api.DocumentModel;
+import org.nuxeo.ecm.core.event.Event;
+import org.nuxeo.ecm.core.event.EventListener;
+
+public interface CSEventListener extends EventListener {
+       /**
+        * Register ourself as an event listener for the named repository -the repo name corresponds to a specific tenant.
+        * @param respositoryName - The name of the Nuxeo repository which links us to a CollectionSpace tenant.
+        * @param eventListenerConfig - Tenant bindings config for our listener.
+        * @return
+        */
+       boolean register(String respositoryName, EventListenerConfig eventListenerConfig);
+       
+       /**
+        * Determines if we are a registered event listener for the given event.
+        * @param event
+        * @return
+        */
+       boolean isRegistered(Event event);
+       
+       /**
+        * Returns event listener related params that we're supplied in the tenant bindings.
+        * @param event
+        * @return
+        */
+       Map<String, String> getParams(Event event);
+       
+       /**
+        * Set's a property in a DocumetModel's transient data context.
+        * 
+        * @param docModel
+        * @param key
+        * @param value
+        */
+       void setDocModelContextProperty(DocumentModel docModel, String key, Serializable value);
+       
+       /**
+        * Clears a property from a DocumentModel's transient data context.
+        * @param docModel
+        * @param key
+        */
+       void clearDocModelContextProperty(DocumentModel docModel, String key);
+       
+       /**
+        * Returns the name of the event listener as defined during registration -see register() method.
+        * @return
+        */
+       String getName(String repositoryName);
+}
diff --git a/services/common/src/main/java/org/collectionspace/services/nuxeo/util/CSEventServiceComponent.java b/services/common/src/main/java/org/collectionspace/services/nuxeo/util/CSEventServiceComponent.java
new file mode 100644 (file)
index 0000000..c398787
--- /dev/null
@@ -0,0 +1,14 @@
+package org.collectionspace.services.nuxeo.util;
+
+import org.nuxeo.ecm.core.event.EventServiceComponent;
+//import org.nuxeo.ecm.core.event.impl.EventServiceImpl;
+import org.nuxeo.runtime.model.ComponentContext;
+
+public class CSEventServiceComponent extends EventServiceComponent {
+       
+    @Override
+    public void activate(ComponentContext context) {
+        service = new CSEventServiceImpl();
+    }
+
+}
diff --git a/services/common/src/main/java/org/collectionspace/services/nuxeo/util/CSEventServiceImpl.java b/services/common/src/main/java/org/collectionspace/services/nuxeo/util/CSEventServiceImpl.java
new file mode 100644 (file)
index 0000000..994dd7d
--- /dev/null
@@ -0,0 +1,18 @@
+package org.collectionspace.services.nuxeo.util;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import org.nuxeo.ecm.core.event.Event;
+import org.nuxeo.ecm.core.event.impl.EventServiceImpl;
+
+public class CSEventServiceImpl extends EventServiceImpl {
+       
+       @Override
+    public void fireEvent(Event event) {
+               String repoName = event.getContext().getRepositoryName();
+               Map<String, Serializable> eventProps = event.getContext().getProperties();
+               super.fireEvent(event);
+       }
+
+}
index f9bf0d4676345014cbc7c37aa6ef236ab918befb..177dbaed4085c24c69d87251b1772bb775ada501 100644 (file)
@@ -45,7 +45,8 @@
             <xs:documentation>Tenant bindings</xs:documentation>
         </xs:annotation>
         <xs:sequence>
-            <xs:element name="repositoryDomain" type="RepositoryDomainType" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="repositoryDomain" type="RepositoryDomainType" minOccurs="1" maxOccurs="unbounded"/>
+            <xs:element name="eventListenerConfigurations" type="EventListenerConfigurations" minOccurs="0" maxOccurs="1"/>
             <xs:element name="binaryStorePath" type="xs:string" minOccurs="0" maxOccurs="1"/>
             <xs:element name="properties" type="types:PropertyType" minOccurs="0" maxOccurs="unbounded"/>
                <xs:element name="remoteClientConfigurations" type="RemoteClientConfigurations" minOccurs="0" maxOccurs="1"/>
@@ -78,6 +79,7 @@
                <xs:sequence>
                        <xs:element name="remoteClientConfig" type="RemoteClientConfig" minOccurs="0" maxOccurs="unbounded"/>
                </xs:sequence>
+               <xs:attribute name="id" type="xs:string" use="required"/>
        </xs:complexType>
        
        <xs:complexType name="RemoteClientConfig">
                        <xs:element name="auth" type="xs:string" minOccurs="0" maxOccurs="1"/>
                </xs:sequence>
        </xs:complexType>
+       
+       <xs:complexType name="EventListenerConfigurations">
+               <xs:sequence>
+                       <xs:element name="eventListenerConfig" type="EventListenerConfig" minOccurs="0" maxOccurs="unbounded"/>
+               </xs:sequence>
+       </xs:complexType>
+       
+       <xs:complexType name="EventListenerConfig">
+               <xs:annotation>
+                       <xs:documentation>Connection details for a remote CollectionSpace instance.  Used for things like the Share Authority Server</xs:documentation>
+               </xs:annotation>
+               <xs:sequence>
+                       <xs:element name="className" type="xs:string" minOccurs="1" maxOccurs="1"/>
+                       <xs:element name="paramList" type="ParamList" minOccurs="0" maxOccurs="1">
+                   
+            </xs:element>
+               </xs:sequence>
+               <xs:attribute name="id" type="xs:string" use="required"/>
+               <xs:attribute name="required" type="xs:boolean" use="optional" default="false"/>
+       </xs:complexType>
+       
+       <xs:complexType name="ParamList">
+               <xs:sequence>
+                       <xs:element name="param" type="Param" minOccurs="0" maxOccurs="unbounded"/>
+               </xs:sequence>
+               <xs:attribute name="id" type="xs:string" use="required"/>
+       </xs:complexType>
+       
+       <xs:complexType name="Param">
+           <xs:sequence>
+               <xs:element name="key" type="xs:string" minOccurs="1" maxOccurs="1"/>
+               <xs:element name="value" type="xs:string" minOccurs="1" maxOccurs="1"/>
+               </xs:sequence>
+       </xs:complexType>
 
 </xs:schema>