]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
e49339fd489979ce4d46b6abd74efe32f5b52ded
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.batch.nuxeo;
2
3 import java.net.URISyntaxException;
4 import java.util.Arrays;
5 import java.util.List;
6
7 import org.apache.commons.lang.StringUtils;
8 import org.collectionspace.services.client.PoxPayloadOut;
9 import org.collectionspace.services.client.TaxonomyAuthorityClient;
10 import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectBotGardenConstants;
11 import org.collectionspace.services.collectionobject.nuxeo.CollectionObjectConstants;
12 import org.collectionspace.services.common.api.RefName;
13 import org.collectionspace.services.common.api.Tools;
14 import org.collectionspace.services.common.invocable.InvocationResults;
15 import org.collectionspace.services.common.vocabulary.AuthorityResource;
16 import org.collectionspace.services.taxonomy.nuxeo.TaxonBotGardenConstants;
17 import org.collectionspace.services.taxonomy.nuxeo.TaxonConstants;
18 import org.dom4j.DocumentException;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /**
23  * A batch job that sets the access code on taxonomy records. The single CSID context is supported.
24  *
25  * If the document is a taxon record, the access codes of the taxon record and all of its descendant
26  * (narrower context) records are updated.
27  *
28  * If the document is a collectionobject, the access codes of all taxon records referenced by the
29  * collectionobject's taxonomic identification are updated, and propagated up the taxon
30  * hierarchy to the ancestors (broader contexts) of each taxon record.
31  *
32  * @author ray
33  *
34  */
35 public class UpdateAccessCodeBatchJob extends AbstractBatchJob {
36         final Logger logger = LoggerFactory.getLogger(UpdateAccessCodeBatchJob.class);
37
38         private final String[] TAXON_FIELD_NAME_PARTS = CollectionObjectBotGardenConstants.TAXON_FIELD_NAME.split("\\/");
39         private final String TAXON_FIELD_NAME_WITHOUT_PATH = TAXON_FIELD_NAME_PARTS[TAXON_FIELD_NAME_PARTS.length - 1];
40
41         public UpdateAccessCodeBatchJob() {
42                 this.setSupportedInvocationModes(Arrays.asList(INVOCATION_MODE_SINGLE));
43         }
44
45         @Override
46         public void run() {
47                 setCompletionStatus(STATUS_MIN_PROGRESS);
48
49                 try {
50                         if (this.requestIsForInvocationModeSingle()) {
51                                 String csid = getInvocationContext().getSingleCSID();
52
53                                 if (StringUtils.isEmpty(csid)) {
54                                         throw new Exception("Missing context csid");
55                                 }
56
57                                 String docType = getInvocationContext().getDocType();
58
59                                 if (docType.equals(TaxonConstants.NUXEO_DOCTYPE)) {
60                                         setResults(updateAccessCode(csid, true));
61                                         //setResults(updateParentAccessCode(csid, true));
62                                 }
63                                 else if (docType.equals(CollectionObjectConstants.NUXEO_DOCTYPE)) {
64                                         setResults(updateReferencedAccessCodes(csid, true));
65                                 }
66                                 else {
67                                         throw new Exception("Unsupported document type: " + docType);
68                                 }
69                         }
70                         else {
71                                 throw new Exception("Unsupported invocation mode: " + this.getInvocationContext().getMode());
72                         }
73
74                         setCompletionStatus(STATUS_COMPLETE);
75                 }
76                 catch(Exception e) {
77                         setCompletionStatus(STATUS_ERROR);
78                         setErrorInfo(new InvocationError(INT_ERROR_STATUS, e.getMessage()));
79                 }
80         }
81
82
83         /**
84          * Updates the access code of the specified taxon record.
85          *
86          * @param taxonRefNameOrCsid    The refname or csid of the taxon record.
87          * @param deep                                  If true, update the access codes of all descendant (narrower context)
88          *                                                              taxon records. On a deep update, the access codes of all descendant
89          *                                                              records are updated first, before calculating the access code of the parent.
90          *                                                              This ensures that the access codes of children are up-to-date, and can be
91          *                                                              used to calculate an up-to-date value for the parent.
92          *
93          *                                                              If false, only the specified taxon record is updated. The calculation
94          *                                                              of the access code uses the access codes of child taxon records, so
95          *                                                              an accurate result depends on the accuracy of the children's access codes.
96          * @return                                              The results of the invocation.
97          * @throws URISyntaxException
98          * @throws DocumentException
99          */
100         public InvocationResults updateAccessCode(String taxonRefNameOrCsid, boolean deep) throws URISyntaxException, DocumentException, Exception {
101                 UpdateAccessCodeResults updateResults = updateAccessCode(taxonRefNameOrCsid, deep, false);
102
103                 InvocationResults results = new InvocationResults();
104                 results.setNumAffected(updateResults.getNumAffected());
105                 results.setUserNote(updateResults.isChanged() ? "access code changed to " + updateResults.getAccessCode() : "access code not changed");
106
107                 return results;
108         }
109
110         /**
111          * Updates the access code of the parent (broader context) of the specified taxon record.
112          *
113          * @param taxonCsid                             The csid of the taxon record.
114          * @param propagate                             If true, propagate the access code up the taxon hierarchy to
115          *                                                              all ancestors of the taxon record. The propagation stops when
116          *                                                              the new value of the access code is the same as the old value,
117          *                                                              or when a root node (a node with no broader context) is reached.
118          *
119          *                                                              If false, update only the access code of the parent.
120          * @return                                              The results of the invocation.
121          * @throws URISyntaxException
122          * @throws DocumentException
123          */
124         public InvocationResults updateParentAccessCode(String taxonCsid, boolean propagate) throws URISyntaxException, DocumentException, Exception {
125                 PoxPayloadOut taxonPayload = findTaxonByCsid(taxonCsid);
126                 String taxonRefName = getFieldValue(taxonPayload, TaxonConstants.REFNAME_SCHEMA_NAME, TaxonConstants.REFNAME_FIELD_NAME);
127                 String accessCode = getFieldValue(taxonPayload, TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME);
128
129                 logger.debug("updating parent access code: taxonRefName=" + taxonRefName + " propagate=" + propagate + " accessCode=" + accessCode);
130
131                 UpdateAccessCodeResults updateResults = updateParentAccessCode(taxonCsid, accessCode, propagate);
132
133                 InvocationResults results = new InvocationResults();
134                 results.setNumAffected(updateResults.getNumAffected());
135                 results.setUserNote(results.getNumAffected() + " access codes changed");
136
137                 return results;
138         }
139
140         /**
141          * Updates the access codes of all taxon records that are referenced in the taxonomic identification
142          * field of the specified collectionobject.
143          *
144          * @param collectionObjectCsid  The csid of the collectionobject.
145          * @param propagate                             If true, propagate the access code up the taxon hierarchy to
146          *                                                              the ancestors of each referenced taxon record. The propagation stops when
147          *                                                              the new value of the access code is the same as the old value,
148          *                                                              or when a root node (a node with no broader context) is reached.
149          *
150          *                                                              If false, update only the access codes of the taxon records
151          *                                                              that are directly referenced.
152          * @return                                              The results of the invocation.
153          * @throws URISyntaxException
154          * @throws DocumentException
155          */
156         public InvocationResults updateReferencedAccessCodes(String collectionObjectCsid, boolean propagate) throws URISyntaxException, DocumentException, Exception {
157                 PoxPayloadOut collectionObjectPayload = findCollectionObjectByCsid(collectionObjectCsid);
158
159                 String deadFlag = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME,
160                                 CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME);
161                 boolean isAlive = (deadFlag == null) || (!deadFlag.equalsIgnoreCase("true"));
162
163                 logger.debug("updating referenced access codes: collectionObjectCsid=" + collectionObjectCsid + " propagate=" + propagate + " isAlive=" + isAlive);
164
165                 List<String> taxonRefNames = getFieldValues(collectionObjectPayload, CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME,
166                                 CollectionObjectBotGardenConstants.TAXON_FIELD_NAME);
167                 long numAffected = 0;
168
169                 for (String taxonRefName : taxonRefNames) {
170                         PoxPayloadOut taxonPayload = findTaxonByRefName(taxonRefName);
171                         if (taxonPayload != null) {
172                                 UpdateAccessCodeResults updateResults = updateAccessCode(taxonPayload, false, isAlive);
173                                 if (updateResults.isChanged()) {
174                                         numAffected += updateResults.getNumAffected();
175         
176                                         if (propagate) {
177                                                 UpdateAccessCodeResults parentUpdateResults = updateParentAccessCode(getCsid(taxonPayload), updateResults.getAccessCode(), true);
178         
179                                                 numAffected += parentUpdateResults.getNumAffected();
180                                         }
181                                 }
182                         } else {
183                                 if (Tools.isBlank(taxonRefName) == false) {
184                                         String msg = String.format("%s found that cataloging/object record CSID=%s references taxon '%s' which could not be found.",
185                                                         getClass().getName(), collectionObjectCsid, taxonRefName);
186                                         logger.warn(msg);
187                                 }
188                         }
189                 }
190
191                 InvocationResults results = new InvocationResults();
192                 results.setNumAffected(numAffected);
193                 results.setUserNote(numAffected + " access codes changed");
194
195                 return results;
196         }
197
198         /**
199          * Updates the access code of the specified taxon record. The access code is determined by
200          * examining all collectionobjects that have a taxonomic identification that matches the
201          * refname of the taxon record, as well as the access codes of child (narrower context)
202          * taxon records. If all referencing collectionobjects are dead (as determined
203          * by the dead flag), and all child taxon records are dead (as determined by their access
204          * codes), then the access code is set to Dead. If any matching collectionobjects
205          * are not dead, or any child taxons are not dead, and the access code is currently Dead,
206          * the access code is set to Unrestricted. Otherwise, the access code is not changed.
207          *
208          * @param taxonPayload  The services payload of the taxon record.
209          * @param deep                  If true, update the access code of all descendant taxon records.
210          *                                              On a deep update, the access codes of all descendant
211          *                                              records are updated first, before calculating the access code of the parent.
212          *                                              This ensures that the access codes of children are up-to-date, and can be
213          *                                              used to calculate an up-to-date value for the parent.
214
215          *                                              If false, only the specified taxon record is updated. The calculation
216          *                                              of the access code uses the access codes of child taxon records, so
217          *                                              an accurate result depends on the accuracy of the children's access codes.
218          * @param knownAlive    A hint that a child taxon of the specified taxon is known to be
219          *                      alive, or that a collectionobject of the specified taxon is known to be
220          *                      alive. This parameter allows for optimization when propagating
221          *                      access code changes up the hierarchy; if a child taxon or
222          *                      referencing collectionobject is known to be alive, and the
223          *                      current access code is Dead, then the access code can be changed
224          *                      to Unrestricted without examining any other records.
225          * @return                              The results of the update.
226          * @throws DocumentException
227          * @throws URISyntaxException
228          */
229         public UpdateAccessCodeResults updateAccessCode(PoxPayloadOut taxonPayload, boolean deep, boolean knownAlive) throws URISyntaxException, DocumentException, Exception {
230                 UpdateAccessCodeResults results = new UpdateAccessCodeResults();
231                 boolean foundAlive = knownAlive;
232
233                 String taxonCsid = getCsid(taxonPayload);
234                 String taxonRefName = getFieldValue(taxonPayload, TaxonConstants.REFNAME_SCHEMA_NAME, TaxonConstants.REFNAME_FIELD_NAME);
235                 String accessCode = getFieldValue(taxonPayload, TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME);
236
237                 logger.debug("updating access code: taxonRefName=" + taxonRefName + " deep=" + deep + " knownAlive=" + knownAlive);
238
239                 if (accessCode == null) {
240                         accessCode = "";
241                 }
242
243                 List<String> childTaxonCsids = findNarrower(taxonCsid);
244
245                 if (deep) {
246                         long numChildrenChanged = 0;
247
248                         // Update the access code on all the children, and track whether any are alive.
249
250                         for (String childTaxonCsid : childTaxonCsids) {
251                                 UpdateAccessCodeResults childResults = updateAccessCode(childTaxonCsid, true, false);
252
253                                 if (!childResults.isSoftDeleted()) {
254                                         String childAccessCode = childResults.getAccessCode();
255                                         boolean isChildAlive = !childAccessCode.equals(TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE);
256
257                                         if (isChildAlive) {
258                                                 foundAlive = true;
259                                         }
260
261                                         if (childResults.isChanged()) {
262                                                 numChildrenChanged++;
263                                         }
264                                 }
265                         }
266
267                         results.setNumAffected(numChildrenChanged);
268                 }
269                 else {
270                         if (!foundAlive) {
271                                 // Check if any of the children are alive.
272
273                                 for (String childTaxonCsid : childTaxonCsids) {
274                                         PoxPayloadOut childTaxonPayload = findTaxonByCsid(childTaxonCsid);
275
276                                         String childAccessCode = getFieldValue(childTaxonPayload, TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME,
277                                                         TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME);
278                                         boolean isChildAlive = !childAccessCode.equals(TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE);
279
280                                         if (isChildAlive) {
281                                                 foundAlive = true;
282                                                 break;
283                                         }
284                                 }
285                         }
286                 }
287
288                 if (!foundAlive) {
289                         // Check if any examples of this taxon are alive.
290
291                         RefName.AuthorityItem item = RefName.AuthorityItem.parse(taxonRefName);
292                         String vocabularyShortId = item.getParentShortIdentifier();
293
294                         List<String> collectionObjectCsids = findReferencingCollectionObjects(TaxonomyAuthorityClient.SERVICE_NAME, vocabularyShortId, taxonCsid,
295                                         CollectionObjectBotGardenConstants.TAXON_SCHEMA_NAME + ":" + TAXON_FIELD_NAME_WITHOUT_PATH);
296
297                         for (String collectionObjectCsid : collectionObjectCsids) {
298                                 PoxPayloadOut collectionObjectPayload = findCollectionObjectByCsid(collectionObjectCsid);
299
300                                 String deadFlag = getFieldValue(collectionObjectPayload, CollectionObjectBotGardenConstants.DEAD_FLAG_SCHEMA_NAME,
301                                                 CollectionObjectBotGardenConstants.DEAD_FLAG_FIELD_NAME);
302                                 boolean isDead = (deadFlag != null) && (deadFlag.equalsIgnoreCase("true"));
303
304                                 if (!isDead) {
305                                         foundAlive = true;
306                                         break;
307                                 }
308                         }
309                 }
310
311                 String newAccessCode;
312
313                 // The access code only needs to be changed if:
314                 //
315                 // 1. There is a living example of the taxon, but the access code is dead.
316                 // 2. There are no living examples, but the access code is not dead.
317                 //
318                 // Otherwise, the access code should stay the same. In particular, if there is a
319                 // living example, and the access code is not dead, the current value of unrestricted
320                 // or restricted should be retained.
321
322                 if (foundAlive && (StringUtils.isEmpty(accessCode) || accessCode.equals(TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE))) {
323                         newAccessCode = TaxonBotGardenConstants.ACCESS_CODE_UNRESTRICTED_VALUE;
324                 }
325                 else if (!foundAlive) {
326                         newAccessCode = TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE;
327                 }
328                 else {
329                         newAccessCode = accessCode;
330                 }
331
332                 if (!newAccessCode.equals(accessCode)) {
333                         String inAuthority = getFieldValue(taxonPayload, TaxonConstants.IN_AUTHORITY_SCHEMA_NAME, TaxonConstants.IN_AUTHORITY_FIELD_NAME);
334
335                         setAccessCode(inAuthority, taxonCsid, newAccessCode);
336
337                         results.setChanged(true);
338                         results.setNumAffected(results.getNumAffected() + 1);
339                 }
340
341                 results.setAccessCode(newAccessCode);
342
343                 return results;
344         }
345
346         /**
347          * Updates the access code of the taxon record with the specified refname or csid.
348          *
349          * @param taxonRefNameOrCsid
350          * @param deep
351          * @param knownAlive
352          * @return
353          * @throws URISyntaxException
354          * @throws DocumentException
355          */
356         public UpdateAccessCodeResults updateAccessCode(String taxonRefNameOrCsid, boolean deep, boolean knownAlive) throws URISyntaxException, DocumentException, Exception {
357                 PoxPayloadOut taxonPayload;
358
359                 if (RefName.AuthorityItem.parse(taxonRefNameOrCsid) == null) {
360                         taxonPayload = findTaxonByCsid(taxonRefNameOrCsid);
361                 }
362                 else {
363                         taxonPayload = findTaxonByRefName(taxonRefNameOrCsid);
364                 }
365
366                 return updateAccessCode(taxonPayload, deep, knownAlive);
367         }
368
369         /**
370          * Updates the access code of the taxon record with the specified refname or csid, when a new
371          * child taxon is known to have been added.
372          *
373          * @param taxonRefNameOrCsid
374          * @param deep
375          * @param newChildCsid       The csid of the newly added child.
376          * @return
377          * @throws URISyntaxException
378          * @throws DocumentException
379          */
380         public UpdateAccessCodeResults updateAccessCode(String taxonRefNameOrCsid, boolean deep, String newChildTaxonCsid) throws URISyntaxException, DocumentException, Exception {
381                 PoxPayloadOut newChildTaxonPayload = findTaxonByCsid(newChildTaxonCsid);
382                 String newChildTaxonAccessCode = getFieldValue(newChildTaxonPayload, TaxonBotGardenConstants.ACCESS_CODE_SCHEMA_NAME, TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME);
383
384                 if (newChildTaxonAccessCode == null) {
385                         newChildTaxonAccessCode = "";
386                 }
387
388                 boolean knownAlive = !newChildTaxonAccessCode.equals(TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE);
389
390                 return updateAccessCode(taxonRefNameOrCsid, deep, knownAlive);
391         }
392
393         /**
394          * Updates the access code of the parent (broader context) of the specified taxon record,
395          * whose access code is assumed to be a specified value.
396          *
397          * @param taxonCsid                             The csid of the taxon record.
398          * @param accessCode                    The access code of the taxon record.
399          * @param propagate                             If true, propagate the access code up the taxon hierarchy to
400          *                                                              all ancestors of the taxon record. The propagation stops when
401          *                                                              the new value of the access code is the same as the old value,
402          *                                                              or when a root node (a node with no broader context) is reached.
403          *
404          *                                                              If false, update only the access code of the parent.
405          * @return                                              The results of the update.
406          * @throws URISyntaxException
407          * @throws DocumentException
408          */
409         public UpdateAccessCodeResults updateParentAccessCode(String taxonCsid, String accessCode, boolean propagate) throws URISyntaxException, DocumentException, Exception {
410                 UpdateAccessCodeResults results = new UpdateAccessCodeResults();
411                 String parentTaxonCsid = findBroader(taxonCsid);
412                 long numAffected = 0;
413
414                 logger.debug("updating parent access code: taxonCsid=" + taxonCsid + " accessCode=" + accessCode + " propagate=" + propagate);
415
416                 if (parentTaxonCsid != null) {
417                         boolean isAlive = (accessCode == null) || !accessCode.equals(TaxonBotGardenConstants.ACCESS_CODE_DEAD_VALUE);
418
419                         UpdateAccessCodeResults parentUpdateResults = updateAccessCode(parentTaxonCsid, false, isAlive);
420
421                         if (parentUpdateResults.isChanged()) {
422                                 // Except for numAffected, the result fields are probably not all that useful in this situation.
423                                 // Set the changed flag to whether the immediate parent was changed, and the access code to
424                                 // the immediate parent's.
425
426                                 results.setAccessCode(parentUpdateResults.getAccessCode());
427                                 results.setChanged(true);
428
429                                 numAffected += parentUpdateResults.getNumAffected();
430
431                                 if (propagate) {
432                                         UpdateAccessCodeResults grandparentUpdateResults = updateParentAccessCode(parentTaxonCsid, parentUpdateResults.getAccessCode(), true);
433                                         numAffected += grandparentUpdateResults.getNumAffected();
434                                 }
435                         }
436                 }
437
438                 results.setNumAffected(numAffected);
439
440                 return results;
441         }
442
443         /**
444          * Sets the access code of the specified taxon record to the specified value.
445          *
446          * @param authorityCsid                 The csid of the authority containing the taxon record.
447          * @param taxonCsid                             The csid of the taxon record.
448          * @param accessCode                    The value of the access code.
449          * @throws URISyntaxException
450          */
451         private void setAccessCode(String authorityCsid, String taxonCsid, String accessCode) throws URISyntaxException {
452                 String updatePayload =
453                                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
454                                 "<document name=\"taxon\">" +
455                                         "<ns2:taxon_naturalhistory xmlns:ns2=\"http://collectionspace.org/services/taxonomy/domain/naturalhistory\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">" +
456                                                 getFieldXml(TaxonBotGardenConstants.ACCESS_CODE_FIELD_NAME, accessCode) +
457                                         "</ns2:taxon_naturalhistory>" +
458                                 "</document>";
459
460                 AuthorityResource<?, ?> resource = (AuthorityResource<?, ?>) getResourceMap().get(TaxonomyAuthorityClient.SERVICE_NAME);
461                 resource.updateAuthorityItem(getServiceContext(), getResourceMap(), createUriInfo(), authorityCsid, taxonCsid, updatePayload);
462         }
463
464         public class UpdateAccessCodeResults {
465                 private boolean isSoftDeleted = false;
466                 private boolean isChanged = false;
467                 private String accessCode = null;
468                 private long numAffected = 0;
469
470                 public boolean isSoftDeleted() {
471                         return isSoftDeleted;
472                 }
473
474                 /**
475                  * @param isSoftDeleted
476                  */
477                 public void setSoftDeleted(boolean isSoftDeleted) {
478                         this.isSoftDeleted = isSoftDeleted;
479                 }
480
481                 public boolean isChanged() {
482                         return isChanged;
483                 }
484
485                 public void setChanged(boolean isChanged) {
486                         this.isChanged = isChanged;
487                 }
488
489                 public String getAccessCode() {
490                         return accessCode;
491                 }
492
493                 public void setAccessCode(String accessCode) {
494                         this.accessCode = accessCode;
495                 }
496
497                 public long getNumAffected() {
498                         return numAffected;
499                 }
500
501                 public void setNumAffected(long numAffected) {
502                         this.numAffected = numAffected;
503                 }
504         }
505 }