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.apache.commons.logging.Log;
10 import org.collectionspace.services.client.workflow.WorkflowClient;
11 import org.collectionspace.services.common.api.Tools;
12 import org.collectionspace.services.config.tenant.EventListenerConfig;
13 import org.collectionspace.services.config.tenant.Param;
14 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
15 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
16 import org.nuxeo.common.collections.ScopeType;
17 import org.nuxeo.common.collections.ScopedMap;
18 import org.nuxeo.ecm.core.api.DocumentModel;
19 import org.nuxeo.ecm.core.api.DocumentModelList;
20 import org.nuxeo.ecm.core.event.Event;
21 import org.nuxeo.ecm.core.event.EventContext;
22 import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
24 public abstract class AbstractCSEventListenerImpl implements CSEventListener {
25 private static Map<String, List<String>> mapOfrepositoryNames = new HashMap<String, List<String>>(); // <className, repositoryName>
26 private static Map<String, Map<String, Map<String, String>>> eventListenerParamsMap = new HashMap<String, Map<String, Map<String, String>>>(); // <repositoryName, Map<EventListenerId, Map<key, value>>>
27 private static Map<String, String> nameMap = new HashMap<String, String>();
30 private final static String NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
31 "AND ecm:isCheckedInVersion = 0"
32 + " AND ecm:isProxy = 0 ";
33 protected final static String ACTIVE_DOCUMENT_WHERE_CLAUSE_FRAGMENT =
34 "AND (ecm:currentLifeCycleState <> 'deleted') "
35 + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
36 static final String DOCMODEL_CONTEXT_PROPERTY_PREFIX = ScopeType.DEFAULT.getScopePrefix();
38 public AbstractCSEventListenerImpl() {
39 // Intentionally left blank
43 * Find out if we (the event listener) are registered (via tenant bindings config) to respond events.
46 public boolean isRegistered(Event event) {
47 boolean result = false;
49 if (event != null && event.getContext() != null) {
50 result = getRepositoryNameList().contains(event.getContext().getRepositoryName());
57 * This method is meant to be the bottleneck for handling a Nuxeo document event.
59 final public void handleEvent(Event event) {
60 getLogger().trace(String.format("Eventlistener '%s' presenented with '%s' event.",
61 getClass().getName(), event.getName()));
62 boolean isRegistered = isRegistered(event);
65 if (isRegistered && shouldHandleEvent(event)) {
67 getLogger().debug(String.format("Eventlistener '%s' accepted '%s' event.",
68 getClass().getName(), event.getName()));
71 getLogger().debug(String.format("Eventlistener '%s' declined to handle '%s' event.",
72 getClass().getName(), event.getName()));
74 getLogger().trace(String.format("Eventlistener '%s' was not registered in the service bindings for the tenant with repo '%s'.",
75 getClass().getName(), event.getContext().getRepositoryName()));
78 } catch (Exception e) {
79 String errMsg = String.format("Eventlistener '%s' presenented with '%s' event but encountered an error: %s",
80 getClass().getName(), event.getName(), e.getMessage());
81 if (getLogger().isTraceEnabled()) {
82 getLogger().error(errMsg, e);
84 getLogger().error(errMsg);
90 * An event listener can be registered by multiple tenants, so we keep track of that here.
92 * @return - the list of tenants/repositories that an event listener is registered with.
94 protected List<String> getRepositoryNameList() {
95 String key = this.getClass().getName();
96 List<String> result = mapOfrepositoryNames.get(key);
98 if (result == null) synchronized(this) {
99 result = new ArrayList<String>();
100 mapOfrepositoryNames.put(key, result);
107 * The list of parameters (specified in a tenant's bindings) for event listeners
110 protected Map<String, Map<String, Map<String, String>>> getEventListenerParamsMap() {
111 return eventListenerParamsMap;
115 * Returns 'true' if this collection changed as a result of the call.
118 public boolean register(String respositoryName, EventListenerConfig eventListenerConfig) {
119 boolean result = false;
121 // Using the repositoryName as a qualifier, register this event listener's name as specified in the tenant bindings.
122 setName(respositoryName, eventListenerConfig.getId());
124 // Register this event listener with the given repository name
125 if (getRepositoryNameList().add(respositoryName)) {
129 if (eventListenerConfig.getParamList() != null) {
130 // 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.
131 List<Param> paramList = eventListenerConfig.getParamList().getParam(); // values from the tenant bindings that we need to copy into the event listener
132 if (paramList != null) {
134 // Get the list of event listeners for a given repository
135 Map<String, Map<String, String>> eventListenerRepoParams = getEventListenerParamsMap().get(respositoryName); // Get the set of event listers for a given repository
136 if (eventListenerRepoParams == null) {
137 eventListenerRepoParams = new HashMap<String, Map<String, String>>();
138 getEventListenerParamsMap().put(respositoryName, eventListenerRepoParams); // create and put an empty map
142 // Get the list of params for a given event listener for a given repository
143 Map<String, String> eventListenerParams = eventListenerRepoParams.get(eventListenerConfig.getId()); // Get the set of params for a given event listener for a given repository
144 if (eventListenerParams == null) {
145 eventListenerParams = new HashMap<String, String>();
146 eventListenerRepoParams.put(eventListenerConfig.getId(), eventListenerParams); // create and put an empty map
150 // copy all the values from the tenant bindings into the event listener
151 for (Param params : paramList) {
152 String key = params.getKey();
153 String value = params.getValue();
154 if (Tools.notBlank(key)) {
155 eventListenerParams.put(key, value);
165 protected void setName(String repositoryName, String eventListenerName) {
166 nameMap.put(repositoryName, eventListenerName);
170 public Map<String, String> getParams(Event event) {
171 Map<String, String> result = null;
173 String repositoryName = event.getContext().getRepositoryName();
174 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
175 } catch (NullPointerException e) {
176 // Do nothing. Just means no params were configured.
182 public String getName(String repositoryName) {
183 return nameMap.get(repositoryName);
187 // Return a property in the document model's transient context.
189 protected Serializable getContextPropertyValue(DocumentEventContext docEventContext, String key) {
190 return docEventContext.getProperties().get(key);
194 // Set a property in the document model's transient context.
197 public void setDocModelContextProperty(DocumentModel collectionObjectDocModel, String key, Serializable value) {
198 ScopedMap contextData = collectionObjectDocModel.getContextData();
199 contextData.putIfAbsent(DOCMODEL_CONTEXT_PROPERTY_PREFIX + key, value);
203 // Clear a property from the docModel's context
206 public void clearDocModelContextProperty(DocumentModel docModel, String key) {
207 ScopedMap contextData = docModel.getContextData();
208 contextData.remove(DOCMODEL_CONTEXT_PROPERTY_PREFIX + key);
212 // Derived classes need to implement.
214 abstract protected Log getLogger();
216 //FIXME: Does not include all the sync-related "delete" workflow states
218 * Identifies whether a supplied event concerns a document that has
219 * been transitioned to the 'deleted' workflow state.
221 * @param eventContext an event context
223 * @return true if this event concerns a document that has
224 * been transitioned to the 'deleted' workflow state.
226 protected boolean isDocumentSoftDeletedEvent(EventContext eventContext) {
227 boolean isSoftDeletedEvent = false;
229 if (eventContext instanceof DocumentEventContext) {
230 if (eventContext.getProperties().containsKey(WorkflowClient.WORKFLOWTRANSITION_TO)
232 (eventContext.getProperties().get(WorkflowClient.WORKFLOWTRANSITION_TO).equals(WorkflowClient.WORKFLOWSTATE_DELETED)
234 eventContext.getProperties().get(WorkflowClient.WORKFLOWTRANSITION_TO).equals(WorkflowClient.WORKFLOWSTATE_LOCKED_DELETED))) {
235 isSoftDeletedEvent = true;
239 return isSoftDeletedEvent;
243 * Identifies whether a document matches a supplied document type.
245 * @param docModel a document model.
246 * @param docType a document type string.
247 * @return true if the document matches the supplied document type; false if
250 protected static boolean documentMatchesType(DocumentModel docModel, String docType) {
251 if (docModel == null || Tools.isBlank(docType)) {
254 if (docModel.getType().startsWith(docType)) {
262 * Identifies whether a document is an active document; currently, whether
263 * it is not in a 'deleted' workflow state.
266 * @return true if the document is an active document; false if it is not.
268 protected static boolean isActiveDocument(DocumentModel docModel) {
269 return isActiveDocument(docModel, false, null);
272 protected static boolean isActiveDocument(DocumentModel docModel, boolean isAboutToBeRemovedEvent, String aboutToBeRemovedCsid) {
273 boolean isActiveDocument = false;
275 if (docModel != null) {
276 if (!docModel.getCurrentLifeCycleState().contains(WorkflowClient.WORKFLOWSTATE_DELETED)) {
277 isActiveDocument = true;
280 // If doc model is the target of the "aboutToBeRemoved" event, mark it as not active.
282 if (isAboutToBeRemovedEvent && Tools.notBlank(aboutToBeRemovedCsid)) {
283 if (NuxeoUtils.getCsid(docModel).equalsIgnoreCase(aboutToBeRemovedCsid)) {
284 isActiveDocument = false;
289 return isActiveDocument;
293 * Returns the current document model for a record identified by a CSID.
295 * Excludes documents which have been versioned (i.e. are a non-current
296 * version of a document), are a proxy for another document, or are
297 * un-retrievable via their CSIDs.
299 * @param session a repository session.
300 * @param csid a CollectionObject identifier (CSID)
301 * @return a document model for the document identified by the supplied
304 protected DocumentModel getCurrentDocModelFromCsid(CoreSessionInterface session, String csid) {
305 DocumentModelList docModelList = null;
307 if (Tools.isEmpty(csid)) {
312 final String query = "SELECT * FROM "
313 + NuxeoUtils.BASE_DOCUMENT_TYPE
315 + NuxeoUtils.getByNameWhereClause(csid)
317 + NONVERSIONED_NONPROXY_DOCUMENT_WHERE_CLAUSE_FRAGMENT;
318 docModelList = session.query(query);
319 } catch (Exception e) {
320 getLogger().warn("Exception in query to get active document model for CSID: " + csid, e);
323 if (docModelList == null || docModelList.isEmpty()) {
324 getLogger().warn("Could not get active document models for CSID=" + csid);
326 } else if (docModelList.size() != 1) {
327 getLogger().error("Found more than 1 active document with CSID=" + csid);
331 return docModelList.get(0);