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.collectionspace.services.batch.nuxeo.UpdateAccessCodeBatchJob;
12 import org.collectionspace.services.client.workflow.WorkflowClient;
13 import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants;
14 import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants;
15 import org.collectionspace.services.common.ResourceMap;
16 import org.collectionspace.services.common.invocable.InvocationResults;
17 import org.collectionspace.services.common.relation.nuxeo.RelationConstants;
18 import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl;
19 import org.collectionspace.services.taxonomy.nuxeo.TaxonBotGardenConstants;
20 import org.collectionspace.services.taxonomy.nuxeo.TaxonConstants;
21 import org.jboss.resteasy.spi.ResteasyProviderFactory;
22 import org.nuxeo.ecm.core.api.DocumentModel;
23 import org.nuxeo.ecm.core.api.event.CoreEventConstants;
24 import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
25 import org.nuxeo.ecm.core.event.Event;
26 import org.nuxeo.ecm.core.event.EventContext;
27 import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
32 * A listener that updates the access code on taxon records when collectionobjects
33 * or taxon records are created or modified.
35 * @see org.collectionspace.services.batch.nuxeo.UpdateAccessCodeBatchJob
39 public class UpdateAccessCodeListener extends AbstractCSEventListenerImpl {
40 final Logger logger = LoggerFactory.getLogger(UpdateAccessCodeListener.class);
42 public static final String PREVIOUS_DEAD_FLAG_PROPERTY_NAME = "UpdateAccessCodeListener.previousDeadFlag";
43 public static final String PREVIOUS_TAXON_NAMES_PROPERTY_NAME = "UpdateAccessCodeListener.previousTaxonNames";
44 public static final String PREVIOUS_ACCESS_CODE_PROPERTY_NAME = "UpdateAccessCodeListener.previousAccessCode";
45 public static final String DELETED_RELATION_PARENT_CSID_PROPERTY_NAME = "UpdateAccessCodeListener.deletedRelationParentCsid";
47 private static final String[] TAXON_PATH_ELEMENTS = CollectionObjectBotGardenConstants.TAXON_FIELD_NAME.split("/");
48 private static final String TAXONOMIC_IDENT_GROUP_LIST_FIELD_NAME = TAXON_PATH_ELEMENTS[0];
49 private static final String TAXON_FIELD_NAME = TAXON_PATH_ELEMENTS[2];
52 public void handleEvent(Event event) {
53 EventContext ec = event.getContext();
55 if (isRegistered(event) && ec instanceof DocumentEventContext) {
56 DocumentEventContext context = (DocumentEventContext) ec;
57 DocumentModel doc = context.getSourceDocument();
59 logger.debug("docType=" + doc.getType());
61 if (doc.getType().startsWith(CollectionObjectConstants.NUXEO_DOCTYPE) &&
64 !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
66 if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) {
67 // Stash the previous dead flag and taxonomic ident values, so they can be retrieved in the documentModified handler.
69 DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL);
71 String previousDeadFlag = (String) previousDoc.getProperty(CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME,
72 CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME);
73 context.setProperty(PREVIOUS_DEAD_FLAG_PROPERTY_NAME, previousDeadFlag);
75 List<String> previousTaxonNames = getTaxonNames(previousDoc);
76 context.setProperty(PREVIOUS_TAXON_NAMES_PROPERTY_NAME, previousTaxonNames.toArray(new String[previousTaxonNames.size()]));
79 boolean deadFlagChanged = false;
80 Set<String> deletedTaxonNames = null;
81 Set<String> addedTaxonNames = null;
83 if (event.getName().equals(DocumentEventTypes.DOCUMENT_UPDATED)) {
84 // As an optimization, check if the dead flag of the collectionobject has
85 // changed, or if the taxonomic identification has changed. If so, we need to
86 // update the access codes of referenced taxon records.
88 String previousDeadFlag = (String) context.getProperty(PREVIOUS_DEAD_FLAG_PROPERTY_NAME);
89 String currentDeadFlag = (String) doc.getProperty(CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME,
90 CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME);
92 if (previousDeadFlag == null) {
93 previousDeadFlag = "";
96 if (currentDeadFlag == null) {
100 if (previousDeadFlag.equals(currentDeadFlag)) {
101 logger.debug("dead flag not changed: previousDeadFlag=" + previousDeadFlag + " currentDeadFlag=" + currentDeadFlag);
104 logger.debug("dead flag changed: previousDeadFlag=" + previousDeadFlag + " currentDeadFlag=" + currentDeadFlag);
105 deadFlagChanged = true;
108 List<String> previousTaxonNames = Arrays.asList((String[]) context.getProperty(PREVIOUS_TAXON_NAMES_PROPERTY_NAME));
109 List<String> currentTaxonNames = getTaxonNames(doc);
111 deletedTaxonNames = findDeletedTaxonNames(previousTaxonNames, currentTaxonNames);
112 logger.debug("found deleted taxon names: " + StringUtils.join(deletedTaxonNames, ", "));
114 addedTaxonNames = findAddedTaxonNames(previousTaxonNames, currentTaxonNames);
115 logger.debug("found added taxon names: " + StringUtils.join(addedTaxonNames, ", "));
117 else if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) {
118 deadFlagChanged = true;
121 UpdateAccessCodeBatchJob updater = createUpdater();
123 if (deadFlagChanged) {
124 String collectionObjectCsid = doc.getName();
127 // Pass false for the second parameter to updateReferencedAccessCodes, so that it doesn't
128 // propagate changes up the taxon hierarchy. Propagation is taken care of by this
129 // event handler: As taxon records are modified, this handler executes, and updates the
132 InvocationResults results = updater.updateReferencedAccessCodes(collectionObjectCsid, false);
134 logger.debug("updateReferencedAccessCodes complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
136 catch (Exception e) {
137 logger.error(e.getMessage(), e);
141 // If the dead flag didn't change, we still need to recalculate the access codes of
142 // any taxonomic idents that were added.
144 if (addedTaxonNames != null) {
145 for (String addedTaxonName : addedTaxonNames) {
146 logger.debug("updating added taxon: " + addedTaxonName);
149 InvocationResults results = updater.updateAccessCode(addedTaxonName, false);
151 logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
153 catch (Exception e) {
154 logger.error(e.getMessage(), e);
160 if (deletedTaxonNames != null) {
161 // If any taxonomic idents were removed from the collectionobject, they need to have their
162 // access codes recalculated.
164 for (String deletedTaxonName : deletedTaxonNames) {
165 logger.debug("updating deleted taxon: " + deletedTaxonName);
168 InvocationResults results = updater.updateAccessCode(deletedTaxonName, false);
170 logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
172 catch (Exception e) {
173 logger.error(e.getMessage(), e);
179 else if (doc.getType().startsWith(TaxonConstants.NUXEO_DOCTYPE) &&
182 !doc.getCurrentLifeCycleState().equals(WorkflowClient.WORKFLOWSTATE_DELETED)) {
184 if (event.getName().equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) {
185 // Stash the previous access code value, so it can be retrieved in the documentModified handler.
187 DocumentModel previousDoc = (DocumentModel) context.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL);
188 String previousAccessCode = (String) previousDoc.getProperty(TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME);
190 context.setProperty(PREVIOUS_ACCESS_CODE_PROPERTY_NAME, previousAccessCode);
193 boolean updateRequired = false;
195 if (event.getName().equals(DocumentEventTypes.DOCUMENT_UPDATED)) {
196 // As an optimization, check if the access code of the taxon has
197 // changed. We only need to update the access code of the parent taxon
200 String previousAccessCode = (String) context.getProperty(PREVIOUS_ACCESS_CODE_PROPERTY_NAME);
201 String currentAccessCode = (String) doc.getProperty(TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME);
203 if (previousAccessCode == null) {
204 previousAccessCode = "";
207 if (currentAccessCode == null) {
208 currentAccessCode = "";
211 if (previousAccessCode.equals(currentAccessCode)) {
212 logger.debug("update not required: previousAccessCode=" + previousAccessCode + " currentAccessCode=" + currentAccessCode);
215 logger.debug("update required: previousAccessCode=" + previousAccessCode + " currentAccessCode=" + currentAccessCode);
216 updateRequired = true;
219 else if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) {
220 updateRequired = true;
223 if (updateRequired) {
224 String taxonCsid = doc.getName();
227 // Pass false for the second parameter to updateReferencedAccessCodes, so that it doesn't
228 // propagate changes up the taxon hierarchy. Propagation is taken care of by this
229 // event handler: As taxon records are modified, this handler executes, and updates the
232 InvocationResults results = createUpdater().updateParentAccessCode(taxonCsid, false);
234 logger.debug("updateParentAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
236 catch (Exception e) {
237 logger.error(e.getMessage(), e);
242 else if (doc.getType().equals(RelationConstants.NUXEO_DOCTYPE) &&
246 if (event.getName().equals(DocumentEventTypes.DOCUMENT_CREATED)) {
247 String subjectDocType = (String) doc.getProperty(RelationConstants.SUBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.SUBJECT_DOCTYPE_FIELD_NAME);
248 String objectDocType = (String) doc.getProperty(RelationConstants.OBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.OBJECT_DOCTYPE_FIELD_NAME);;
249 String relationType = (String) doc.getProperty(RelationConstants.TYPE_SCHEMA_NAME, RelationConstants.TYPE_FIELD_NAME);
251 logger.debug("subjectDocType=" + subjectDocType + " objectDocType=" + objectDocType + " relationType=" + relationType);
253 if (subjectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && objectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && relationType.equals(RelationConstants.BROADER_TYPE)) {
254 String parentTaxonCsid = (String) doc.getProperty(RelationConstants.OBJECT_CSID_SCHEMA_NAME, RelationConstants.OBJECT_CSID_FIELD_NAME);
255 logger.debug("child added, updating parent taxon: parentTaxonCsid=" + parentTaxonCsid);
258 InvocationResults results = createUpdater().updateAccessCode(parentTaxonCsid, false);
260 logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
262 catch (Exception e) {
263 logger.error(e.getMessage(), e);
267 else if (event.getName().equals(DocumentEventTypes.ABOUT_TO_REMOVE)) {
268 String subjectDocType = (String) doc.getProperty(RelationConstants.SUBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.SUBJECT_DOCTYPE_FIELD_NAME);
269 String objectDocType = (String) doc.getProperty(RelationConstants.OBJECT_DOCTYPE_SCHEMA_NAME, RelationConstants.OBJECT_DOCTYPE_FIELD_NAME);;
270 String relationType = (String) doc.getProperty(RelationConstants.TYPE_SCHEMA_NAME, RelationConstants.TYPE_FIELD_NAME);
272 logger.debug("subjectDocType=" + subjectDocType + " objectDocType=" + objectDocType + " relationType=" + relationType);
274 if (subjectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && objectDocType.equals(TaxonConstants.NUXEO_DOCTYPE) && relationType.equals(RelationConstants.BROADER_TYPE)) {
275 String parentTaxonCsid = (String) doc.getProperty(RelationConstants.OBJECT_CSID_SCHEMA_NAME, RelationConstants.OBJECT_CSID_FIELD_NAME);
277 // Stash the parent taxon csid, so it can be retrieved in the documentRemoved handler.
278 logger.debug("about to delete taxon hierarchy relation: parentTaxonCsid=" + parentTaxonCsid);
279 context.setProperty(DELETED_RELATION_PARENT_CSID_PROPERTY_NAME, parentTaxonCsid);
282 else if (event.getName().equals(DocumentEventTypes.DOCUMENT_REMOVED)) {
283 String parentTaxonCsid = (String) context.getProperty(DELETED_RELATION_PARENT_CSID_PROPERTY_NAME);
285 if (StringUtils.isNotEmpty(parentTaxonCsid)) {
286 logger.debug("child removed, updating parent taxon: parentTaxonCsid=" + parentTaxonCsid);
289 InvocationResults results = createUpdater().updateAccessCode(parentTaxonCsid, false);
291 logger.debug("updateAccessCode complete: numAffected=" + results.getNumAffected() + " userNote=" + results.getUserNote());
293 catch (Exception e) {
294 logger.error(e.getMessage(), e);
302 private List<String> getTaxonNames(DocumentModel doc) {
303 List<Map<String, Object>> taxonomicIdentGroupList = (List<Map<String, Object>>) doc.getProperty(CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME,
304 TAXONOMIC_IDENT_GROUP_LIST_FIELD_NAME);
305 List<String> taxonNames = new ArrayList<String>();
307 for (Map<String, Object> taxonomicIdentGroup : taxonomicIdentGroupList) {
308 String taxonName = (String) taxonomicIdentGroup.get(TAXON_FIELD_NAME);
310 if (StringUtils.isNotEmpty(taxonName)) {
311 taxonNames.add(taxonName);
318 private Set<String> findDeletedTaxonNames(List<String> previousTaxonNames, List<String> currentTaxonNames) {
319 Set<String> currentTaxonNameSet = new HashSet<String>(currentTaxonNames);
320 Set<String> deletedTaxonNameSet = new HashSet<String>();
322 for (String previousTaxonName : previousTaxonNames) {
323 if (!currentTaxonNameSet.contains(previousTaxonName)) {
324 deletedTaxonNameSet.add(previousTaxonName);
328 return deletedTaxonNameSet;
331 private Set<String> findAddedTaxonNames(List<String> previousTaxonNames, List<String> currentTaxonNames) {
332 Set<String> previousTaxonNameSet = new HashSet<String>(previousTaxonNames);
333 Set<String> addedTaxonNameSet = new HashSet<String>();
335 for (String currentTaxonName : currentTaxonNames) {
336 if (!previousTaxonNameSet.contains(currentTaxonName)) {
337 addedTaxonNameSet.add(currentTaxonName);
341 return addedTaxonNameSet;
344 private UpdateAccessCodeBatchJob createUpdater() {
345 ResourceMap resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class);
347 UpdateAccessCodeBatchJob updater = new UpdateAccessCodeBatchJob();
348 updater.setResourceMap(resourceMap);