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;
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;
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
"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
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();
@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();
// 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
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
//
//
// 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
//
// 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);
}
/**
* 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;
}
* @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 {
* @throws ClientException
*/
protected abstract boolean updateCollectionObjectLocation(DocumentModel collectionObjectDocModel,
+ DocumentModel movmentDocModel,
String movementRecordsLocation)
throws ClientException;
}
\ No newline at end of file
--- /dev/null
+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
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;
@Override
protected boolean updateCollectionObjectLocation(DocumentModel collectionObjectDocModel,
+ DocumentModel movementDocModel,
String movementRecordsLocation) throws ClientException {
boolean result = false;
<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">
<!-- 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>
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;
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;
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.
String[] dataSourceNames = {JDBCTools.NUXEO_DATASOURCE_NAME, JDBCTools.NUXEO_READER_DATASOURCE_NAME};
updateInitializationScript(getNuxeoDatabasesInitScriptFilename(),
dbsCheckedOrCreated, dataSourceNames);
-
+
//
// Start up and initialize our embedded Nuxeo instance.
//
//
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
*/
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";
--- /dev/null
+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);
+ }
+
+}
--- /dev/null
+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);
+}
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+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);
+ }
+
+}
<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"/>
<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>