1 package org.collectionspace.services.listener.botgarden;
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.HashSet;
10 import org.apache.commons.lang.StringUtils;
11 import org.apache.commons.logging.Log;
12 import org.apache.commons.logging.LogFactory;
14 import org.collectionspace.services.batch.BatchResource;
15 import org.collectionspace.services.batch.nuxeo.UpdateAccessCodeBatchJob;
16 import org.collectionspace.services.batch.nuxeo.UpdateAccessCodeBatchJob.UpdateAccessCodeResults;
17 import org.collectionspace.services.client.BatchClient;
18 import org.collectionspace.services.client.PoxPayloadIn;
19 import org.collectionspace.services.client.PoxPayloadOut;
20 import org.collectionspace.services.client.workflow.WorkflowClient;
21 import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants;
22 import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants;
23 import org.collectionspace.services.common.ResourceMap;
24 import org.collectionspace.services.common.context.ServiceContext;
25 import org.collectionspace.services.common.invocable.InvocationResults;
26 import org.collectionspace.services.common.relation.nuxeo.RelationConstants;
27 import org.collectionspace.services.nuxeo.client.java.CoreSessionWrapper;
28 import org.collectionspace.services.nuxeo.listener.AbstractCSEventSyncListenerImpl;
29 import org.collectionspace.services.taxonomy.nuxeo.TaxonBotGardenConstants;
30 import org.collectionspace.services.taxonomy.nuxeo.TaxonConstants;
31 import org.collectionspace.services.taxonomy.nuxeo.TaxonomyAuthorityConstants;
33 import org.jboss.resteasy.spi.ResteasyProviderFactory;
35 import org.nuxeo.ecm.core.api.DocumentModel;
36 import org.nuxeo.ecm.core.api.event.CoreEventConstants;
37 import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
38 import org.nuxeo.ecm.core.event.Event;
39 import org.nuxeo.ecm.core.event.EventContext;
40 import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
43 * A listener that updates the access code on taxon records when collectionobjects
44 * or taxon records are created or modified.
46 * @see org.collectionspace.services.batch.nuxeo.UpdateAccessCodeBatchJob
50 public class UpdateAccessCodeListener extends AbstractCSEventSyncListenerImpl {
51 static final Log logger = LogFactory.getLog(UpdateAccessCodeListener.class);
53 public static final String PREVIOUS_DEAD_FLAG_PROPERTY_NAME = "UpdateAccessCodeListener.previousDeadFlag";
54 public static final String PREVIOUS_TAXON_NAMES_PROPERTY_NAME = "UpdateAccessCodeListener.previousTaxonNames";
55 public static final String PREVIOUS_ACCESS_CODE_PROPERTY_NAME = "UpdateAccessCodeListener.previousAccessCode";
56 public static final String DELETED_RELATION_PARENT_CSID_PROPERTY_NAME = "UpdateAccessCodeListener.deletedRelationParentCsid";
58 private static final String[] TAXON_PATH_ELEMENTS = CollectionObjectBotGardenConstants.TAXON_FIELD_NAME.split("/");
59 private static final String TAXONOMIC_IDENT_GROUP_LIST_FIELD_NAME = TAXON_PATH_ELEMENTS[0];
60 private static final String TAXON_FIELD_NAME = TAXON_PATH_ELEMENTS[2];
63 public boolean shouldHandleEvent(Event event) {
64 return event.getContext() instanceof DocumentEventContext;
68 public void handleCSEvent(Event event) {
69 EventContext ec = event.getContext();
71 DocumentEventContext context = (DocumentEventContext) ec;
72 DocumentModel doc = context.getSourceDocument();
73 String docType = doc.getType();
75 logger.debug("docType=" + docType);
77 if (docType.startsWith(CollectionObjectConstants.NUXEO_DOCTYPE) &&
80 !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
82 if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) {
83 // Stash the previous dead flag and taxonomic ident values, so they can be retrieved in the documentModified handler.
85 DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL);
87 String previousDeadFlag = (String) previousDoc.getProperty(CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME,
88 CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME);
89 context.setProperty(PREVIOUS_DEAD_FLAG_PROPERTY_NAME, previousDeadFlag);
91 List<String> previousTaxonNames = getTaxonNames(previousDoc);
92 context.setProperty(PREVIOUS_TAXON_NAMES_PROPERTY_NAME, previousTaxonNames.toArray(new String[previousTaxonNames.size()]));
95 boolean deadFlagChanged = false;
96 Set<String> deletedTaxonNames = null;
97 Set<String> addedTaxonNames = null;
99 String currentDeadFlag = (String) doc.getProperty(CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME,
100 CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME);
102 if (currentDeadFlag == null) {
103 currentDeadFlag = "";
106 if (event.getName().equals(DocumentEventTypes.DOCUMENT_UPDATED)) {
107 // As an optimization, check if the dead flag of the collectionobject has
108 // changed, or if the taxonomic identification has changed. If so, we need to
109 // update the access codes of referenced taxon records.
111 String previousDeadFlag = (String) context.getProperty(PREVIOUS_DEAD_FLAG_PROPERTY_NAME);
113 if (previousDeadFlag == null) {
114 previousDeadFlag = "";
117 if (previousDeadFlag.equals(currentDeadFlag)) {
118 logger.debug("dead flag not changed: previousDeadFlag=" + previousDeadFlag + " currentDeadFlag=" + currentDeadFlag);
121 logger.debug("dead flag changed: previousDeadFlag=" + previousDeadFlag + " currentDeadFlag=" + currentDeadFlag);
122 deadFlagChanged = true;
125 List<String> previousTaxonNames = Arrays.asList((String[]) context.getProperty(PREVIOUS_TAXON_NAMES_PROPERTY_NAME));
126 List<String> currentTaxonNames = getTaxonNames(doc);
128 deletedTaxonNames = findDeletedTaxonNames(previousTaxonNames, currentTaxonNames);
129 logger.debug("found deleted taxon names: " + StringUtils.join(deletedTaxonNames, ", "));
131 addedTaxonNames = findAddedTaxonNames(previousTaxonNames, currentTaxonNames);
132 logger.debug("found added taxon names: " + StringUtils.join(addedTaxonNames, ", "));
134 else if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) {
135 deadFlagChanged = true;
138 if (deadFlagChanged) {
139 String collectionObjectCsid = doc.getName();
142 // Pass false for the second parameter to updateReferencedAccessCodes, so that it doesn't
143 // propagate changes up the taxon hierarchy. Propagation is taken care of by this
144 // event handler: As taxon records are modified, this handler executes, and updates the
147 InvocationResults results = createUpdater(context).updateReferencedAccessCodes(collectionObjectCsid, false);
149 logger.debug("updateReferencedAccessCodes complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
151 catch (Exception e) {
152 logger.error(e.getMessage(), e);
156 // If the dead flag didn't change, we still need to recalculate the access codes of
157 // any taxonomic idents that were added.
159 if (addedTaxonNames != null) {
160 boolean isDead = currentDeadFlag.equalsIgnoreCase("true");
161 boolean knownAlive = !isDead;
163 for (String addedTaxonName : addedTaxonNames) {
164 logger.debug("updating added taxon: " + addedTaxonName);
167 UpdateAccessCodeResults results = createUpdater(context).updateAccessCode(addedTaxonName, false, knownAlive);
169 logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected());
171 catch (Exception e) {
172 logger.error(e.getMessage(), e);
178 if (deletedTaxonNames != null) {
179 // If any taxonomic idents were removed from the collectionobject, they need to have their
180 // access codes recalculated.
182 for (String deletedTaxonName : deletedTaxonNames) {
183 logger.debug("updating deleted taxon: " + deletedTaxonName);
186 InvocationResults results = createUpdater(context).updateAccessCode(deletedTaxonName, false);
188 logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
190 catch (Exception e) {
191 logger.error(e.getMessage(), e);
197 else if (docType.startsWith(TaxonConstants.NUXEO_DOCTYPE) &&
198 !docType.startsWith(TaxonomyAuthorityConstants.NUXEO_DOCTYPE) &&
201 !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
203 if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) {
204 // Stash the previous access code value, so it can be retrieved in the documentModified handler.
206 DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL);
207 String previousAccessCode = (String) previousDoc.getProperty(TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME);
209 context.setProperty(PREVIOUS_ACCESS_CODE_PROPERTY_NAME, previousAccessCode);
212 boolean updateRequired = false;
214 if (event.getName().equals(DocumentEventTypes.DOCUMENT_UPDATED)) {
215 // As an optimization, check if the access code of the taxon has
216 // changed. We only need to update the access code of the parent taxon
219 String previousAccessCode = (String) context.getProperty(PREVIOUS_ACCESS_CODE_PROPERTY_NAME);
220 String currentAccessCode = (String) doc.getProperty(TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME);
222 if (previousAccessCode == null) {
223 previousAccessCode = "";
226 if (currentAccessCode == null) {
227 currentAccessCode = "";
230 if (previousAccessCode.equals(currentAccessCode)) {
231 logger.debug("update not required: previousAccessCode=" + previousAccessCode + " currentAccessCode=" + currentAccessCode);
234 logger.debug("update required: previousAccessCode=" + previousAccessCode + " currentAccessCode=" + currentAccessCode);
235 updateRequired = true;
238 else if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) {
239 updateRequired = true;
242 if (updateRequired) {
243 String taxonCsid = doc.getName();
246 // Pass false for the second parameter to updateReferencedAccessCodes, so that it doesn't
247 // propagate changes up the taxon hierarchy. Propagation is taken care of by this
248 // event handler: As taxon records are modified, this handler executes, and updates the
251 InvocationResults results = createUpdater(context).updateParentAccessCode(taxonCsid, false);
253 logger.debug("updateParentAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
255 catch (Exception e) {
256 logger.error(e.getMessage(), e);
261 else if (doc.getType().equals(RelationConstants.NUXEO_DOCTYPE) &&
265 if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) {
266 String subjectDocType = (String) doc.getProperty(RelationConstants.SUBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.SUBJECT_DOCTYPE_FIELD_NAME);
267 String objectDocType = (String) doc.getProperty(RelationConstants.OBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.OBJECT_DOCTYPE_FIELD_NAME);;
268 String relationType = (String) doc.getProperty(RelationConstants.TYPE_SCHEMA_NAME, RelationConstants.TYPE_FIELD_NAME);
270 logger.debug("subjectDocType=" + subjectDocType + " objectDocType=" + objectDocType + " relationType=" + relationType);
272 if (subjectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && objectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && relationType.equals(RelationConstants.BROADER_TYPE)) {
273 String parentTaxonCsid = (String) doc.getProperty(RelationConstants.OBJECT_CSID_SCHEMA_NAME, RelationConstants.OBJECT_CSID_FIELD_NAME);
274 String childTaxonCsid = (String) doc.getProperty(RelationConstants.SUBJECT_CSID_SCHEMA_NAME, RelationConstants.SUBJECT_CSID_FIELD_NAME);
276 logger.debug("child added, updating parent taxon: parentTaxonCsid=" + parentTaxonCsid + " childTaxonCsid=" + childTaxonCsid);
279 UpdateAccessCodeResults results = createUpdater(context).updateAccessCode(parentTaxonCsid, false, childTaxonCsid);
281 logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected());
283 catch (Exception e) {
284 logger.error(e.getMessage(), e);
288 else if (event.getName().equals(DocumentEventTypes.ABOUT_TO_REMOVE)) {
289 String subjectDocType = (String) doc.getProperty(RelationConstants.SUBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.SUBJECT_DOCTYPE_FIELD_NAME);
290 String objectDocType = (String) doc.getProperty(RelationConstants.OBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.OBJECT_DOCTYPE_FIELD_NAME);;
291 String relationType = (String) doc.getProperty(RelationConstants.TYPE_SCHEMA_NAME, RelationConstants.TYPE_FIELD_NAME);
293 logger.debug("subjectDocType=" + subjectDocType + " objectDocType=" + objectDocType + " relationType=" + relationType);
295 if (subjectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && objectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && relationType.equals(RelationConstants.BROADER_TYPE)) {
296 String parentTaxonCsid = (String) doc.getProperty(RelationConstants.OBJECT_CSID_SCHEMA_NAME, RelationConstants.OBJECT_CSID_FIELD_NAME);
298 // Stash the parent taxon csid, so it can be retrieved in the documentRemoved handler.
299 logger.debug("about to delete taxon hierarchy relation: parentTaxonCsid=" + parentTaxonCsid);
300 context.setProperty(DELETED_RELATION_PARENT_CSID_PROPERTY_NAME, parentTaxonCsid);
303 else if (event.getName().equals(DocumentEventTypes.DOCUMENT_REMOVED)) {
304 String parentTaxonCsid = (String) context.getProperty(DELETED_RELATION_PARENT_CSID_PROPERTY_NAME);
306 if (StringUtils.isNotEmpty(parentTaxonCsid)) {
307 logger.debug("child removed, updating parent taxon: parentTaxonCsid=" + parentTaxonCsid);
310 InvocationResults results = createUpdater(context).updateAccessCode(parentTaxonCsid, false);
312 logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
314 catch (Exception e) {
315 logger.error(e.getMessage(), e);
322 private List<String> getTaxonNames(DocumentModel doc) {
323 List<Map<String, Object>> taxonomicIdentGroupList = (List<Map<String, Object>>) doc.getProperty(CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME,
324 TAXONOMIC_IDENT_GROUP_LIST_FIELD_NAME);
325 List<String> taxonNames = new ArrayList<String>();
327 for (Map<String, Object> taxonomicIdentGroup : taxonomicIdentGroupList) {
328 String taxonName = (String) taxonomicIdentGroup.get(TAXON_FIELD_NAME);
330 if (StringUtils.isNotEmpty(taxonName)) {
331 taxonNames.add(taxonName);
338 private Set<String> findDeletedTaxonNames(List<String> previousTaxonNames, List<String> currentTaxonNames) {
339 Set<String> currentTaxonNameSet = new HashSet<String>(currentTaxonNames);
340 Set<String> deletedTaxonNameSet = new HashSet<String>();
342 for (String previousTaxonName : previousTaxonNames) {
343 if (!currentTaxonNameSet.contains(previousTaxonName)) {
344 deletedTaxonNameSet.add(previousTaxonName);
348 return deletedTaxonNameSet;
351 private Set<String> findAddedTaxonNames(List<String> previousTaxonNames, List<String> currentTaxonNames) {
352 Set<String> previousTaxonNameSet = new HashSet<String>(previousTaxonNames);
353 Set<String> addedTaxonNameSet = new HashSet<String>();
355 for (String currentTaxonName : currentTaxonNames) {
356 if (!previousTaxonNameSet.contains(currentTaxonName)) {
357 addedTaxonNameSet.add(currentTaxonName);
361 return addedTaxonNameSet;
364 private UpdateAccessCodeBatchJob createUpdater(DocumentEventContext context) throws Exception {
365 ResourceMap resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class);
366 BatchResource batchResource = (BatchResource) resourceMap.get(BatchClient.SERVICE_NAME);
367 ServiceContext<PoxPayloadIn, PoxPayloadOut> serviceContext = batchResource.createServiceContext(batchResource.getServiceName());
369 serviceContext.setCurrentRepositorySession(new CoreSessionWrapper(context.getCoreSession()));
371 UpdateAccessCodeBatchJob updater = new UpdateAccessCodeBatchJob();
372 updater.setServiceContext(serviceContext);
373 updater.setResourceMap(resourceMap);
379 public Log getLogger() {