1 package org.collectionspace.services.nuxeo.listener;
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.HashMap;
9 import org.slf4j.Logger;
11 import org.collectionspace.services.client.workflow.WorkflowClient;
12 import org.collectionspace.services.common.api.Tools;
13 import org.collectionspace.services.config.tenant.EventListenerConfig;
14 import org.collectionspace.services.config.tenant.Param;
15 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
16 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
17 import org.nuxeo.common.collections.ScopeType;
18 import org.nuxeo.common.collections.ScopedMap;
19 import org.nuxeo.ecm.core.api.DocumentModel;
20 import org.nuxeo.ecm.core.api.DocumentModelList;
21 import org.nuxeo.ecm.core.event.Event;
22 import org.nuxeo.ecm.core.event.EventContext;
23 import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
25 public abstract class AbstractCSEventListenerImpl implements CSEventListener {
26 private static Map<String, List<String>> mapOfrepositoryNames = new HashMap<String, List<String>>(); // <className, repositoryName>
27 private static Map<String, Map<String, Map<String, String>>> eventListenerParamsMap = new HashMap<String, Map<String, Map<String, String>>>(); // <repositoryName, Map<EventListenerId, Map<key, value>>>
28 private static Map<String, String> nameMap = new HashMap<String, String>();
31 private final static String NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
32 "AND ecm:isCheckedInVersion = 0"
33 + " AND ecm:isProxy = 0 ";
34 protected final static String ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
35 "AND (ecm:currentLifeCycleState <> 'deleted') "
36 + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
37 static final String DOCMODEL_CONTEXT_PROPERTY_PREFIX = ScopeType.DEFAULT.getScopePrefix();
39 public AbstractCSEventListenerImpl() {
40 // Intentionally left blank
44 * Find out if we (the event listener) are registered (via tenant bindings config) to respond events.
47 public boolean isRegistered(Event event) {
48 boolean result = false;
50 if (event != null && event.getContext() != null) {
51 result = getRepositoryNameList().contains(event.getContext().getRepositoryName());
58 * This method is meant to be the bottleneck for handling a Nuxeo document event.
60 final public void handleEvent(Event event) {
61 getLogger().trace(String.format("Eventlistener '%s' presenented with '%s' event.",
62 getClass().getName(), event.getName()));
63 boolean isRegistered = isRegistered(event);
66 if (isRegistered && shouldHandleEvent(event)) {
68 getLogger().debug(String.format("Eventlistener '%s' accepted '%s' event.",
69 getClass().getName(), event.getName()));
72 getLogger().debug(String.format("Eventlistener '%s' declined to handle '%s' event.",
73 getClass().getName(), event.getName()));
75 getLogger().trace(String.format("Eventlistener '%s' was not registered in the service bindings for the tenant with repo '%s'.",
76 getClass().getName(), event.getContext().getRepositoryName()));
79 } catch (Exception e) {
80 String errMsg = String.format("Eventlistener '%s' presenented with '%s' event but encountered an error: %s",
81 getClass().getName(), event.getName(), e.getMessage());
82 if (getLogger().isTraceEnabled()) {
83 getLogger().error(errMsg, e);
85 getLogger().error(errMsg);
91 * An event listener can be registered by multiple tenants, so we keep track of that here.
93 * @return - the list of tenants/repositories that an event listener is registered with.
95 protected List<String> getRepositoryNameList() {
96 String key = this.getClass().getName();
97 List<String> result = mapOfrepositoryNames.get(key);
99 if (result == null) synchronized(this) {
100 result = new ArrayList<String>();
101 mapOfrepositoryNames.put(key, result);
108 * The list of parameters (specified in a tenant's bindings) for event listeners
111 protected Map<String, Map<String, Map<String, String>>> getEventListenerParamsMap() {
112 return eventListenerParamsMap;
116 * Returns 'true' if this collection changed as a result of the call.
119 public boolean register(String respositoryName, EventListenerConfig eventListenerConfig) {
120 boolean result = false;
122 // Using the repositoryName as a qualifier, register this event listener's name as specified in the tenant bindings.
123 setName(respositoryName, eventListenerConfig.getId());
125 // Register this event listener with the given repository name
126 if (getRepositoryNameList().add(respositoryName)) {
130 if (eventListenerConfig.getParamList() != null) {
131 // 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.
132 List<Param> paramList = eventListenerConfig.getParamList().getParam(); // values from the tenant bindings that we need to copy into the event listener
133 if (paramList != null) {
135 // Get the list of event listeners for a given repository
136 Map<String, Map<String, String>> eventListenerRepoParams = getEventListenerParamsMap().get(respositoryName); // Get the set of event listers for a given repository
137 if (eventListenerRepoParams == null) {
138 eventListenerRepoParams = new HashMap<String, Map<String, String>>();
139 getEventListenerParamsMap().put(respositoryName, eventListenerRepoParams); // create and put an empty map
143 // Get the list of params for a given event listener for a given repository
144 Map<String, String> eventListenerParams = eventListenerRepoParams.get(eventListenerConfig.getId()); // Get the set of params for a given event listener for a given repository
145 if (eventListenerParams == null) {
146 eventListenerParams = new HashMap<String, String>();
147 eventListenerRepoParams.put(eventListenerConfig.getId(), eventListenerParams); // create and put an empty map
151 // copy all the values from the tenant bindings into the event listener
152 for (Param params : paramList) {
153 String key = params.getKey();
154 String value = params.getValue();
155 if (Tools.notBlank(key)) {
156 eventListenerParams.put(key, value);
166 protected void setName(String repositoryName, String eventListenerName) {
167 nameMap.put(repositoryName, eventListenerName);
171 public Map<String, String> getParams(Event event) {
172 Map<String, String> result = null;
174 String repositoryName = event.getContext().getRepositoryName();
175 result = 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
176 } catch (NullPointerException e) {
177 // Do nothing. Just means no params were configured.
183 public String getName(String repositoryName) {
184 return nameMap.get(repositoryName);
188 // Return a property in the document model's transient context.
190 protected Serializable getContextPropertyValue(DocumentEventContext docEventContext, String key) {
191 return docEventContext.getProperties().get(DOCMODEL_CONTEXT_PROPERTY_PREFIX + key);
195 // Set a property in the document model's transient context.
198 public void setDocModelContextProperty(DocumentModel collectionObjectDocModel, String key, Serializable value) {
199 ScopedMap contextData = collectionObjectDocModel.getContextData();
200 contextData.putIfAbsent(DOCMODEL_CONTEXT_PROPERTY_PREFIX + key, value);
204 // Clear a property from the docModel's context
207 public void clearDocModelContextProperty(DocumentModel docModel, String key) {
208 ScopedMap contextData = docModel.getContextData();
209 contextData.remove(DOCMODEL_CONTEXT_PROPERTY_PREFIX + key);
213 // Derived classes need to implement.
215 abstract protected Logger getLogger();
217 //FIXME: Does not include all the sync-related "delete" workflow states
219 * Identifies whether a supplied event concerns a document that has
220 * been transitioned to the 'deleted' workflow state.
222 * @param eventContext an event context
224 * @return true if this event concerns a document that has
225 * been transitioned to the 'deleted' workflow state.
227 protected boolean isDocumentSoftDeletedEvent(EventContext eventContext) {
228 boolean isSoftDeletedEvent = false;
230 if (eventContext instanceof DocumentEventContext) {
231 if (eventContext.getProperties().containsKey(WorkflowClient.WORKFLOWTRANSITION_TO)
233 (eventContext.getProperties().get(WorkflowClient.WORKFLOWTRANSITION_TO).equals(WorkflowClient.WORKFLOWSTATE_DELETED)
235 eventContext.getProperties().get(WorkflowClient.WORKFLOWTRANSITION_TO).equals(WorkflowClient.WORKFLOWSTATE_LOCKED_DELETED))) {
236 isSoftDeletedEvent = true;
240 return isSoftDeletedEvent;
244 * Identifies whether a document matches a supplied document type.
246 * @param docModel a document model.
247 * @param docType a document type string.
248 * @return true if the document matches the supplied document type; false if
251 protected static boolean documentMatchesType(DocumentModel docModel, String docType) {
252 if (docModel == null || Tools.isBlank(docType)) {
255 if (docModel.getType().startsWith(docType)) {
263 * Identifies whether a document is an active document; currently, whether
264 * it is not in a 'deleted' workflow state.
267 * @return true if the document is an active document; false if it is not.
269 protected static boolean isActiveDocument(DocumentModel docModel) {
270 return isActiveDocument(docModel, false, null);
273 protected static boolean isActiveDocument(DocumentModel docModel, boolean isAboutToBeRemovedEvent, String aboutToBeRemovedCsid) {
274 boolean isActiveDocument = false;
276 if (docModel != null) {
277 if (!docModel.getCurrentLifeCycleState().contains(WorkflowClient.WORKFLOWSTATE_DELETED)) {
278 isActiveDocument = true;
281 // If doc model is the target of the "aboutToBeRemoved" event, mark it as not active.
283 if (isAboutToBeRemovedEvent && Tools.notBlank(aboutToBeRemovedCsid)) {
284 if (NuxeoUtils.getCsid(docModel).equalsIgnoreCase(aboutToBeRemovedCsid)) {
285 isActiveDocument = false;
290 return isActiveDocument;
294 * Returns the current document model for a record identified by a CSID.
296 * Excludes documents which have been versioned (i.e. are a non-current
297 * version of a document), are a proxy for another document, or are
298 * un-retrievable via their CSIDs.
300 * @param session a repository session.
301 * @param csid a CollectionObject identifier (CSID)
302 * @return a document model for the document identified by the supplied
305 protected DocumentModel getCurrentDocModelFromCsid(CoreSessionInterface session, String csid) {
306 DocumentModelList docModelList = null;
308 if (Tools.isEmpty(csid)) {
313 final String query = "SELECT * FROM "
314 + NuxeoUtils.BASE_DOCUMENT_TYPE
316 + NuxeoUtils.getByNameWhereClause(csid)
318 + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
319 docModelList = session.query(query);
320 } catch (Exception e) {
321 getLogger().warn("Exception in query to get active document model for CSID: " + csid, e);
324 if (docModelList == null || docModelList.isEmpty()) {
325 getLogger().warn("Could not get active document models for CSID=" + csid);
327 } else if (docModelList.size() != 1) {
328 getLogger().error("Found more than 1 active document with CSID=" + csid);
332 return docModelList.get(0);