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