]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
8a74fcf9ad54a6255e60bd44978b4cf75eb9c4f8
[tmp/jakarta-migration.git] /
1 /**
2  *  This document is a part of the source code and related artifacts
3  *  for CollectionSpace, an open source collections management system
4  *  for museums and related institutions:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17
18  *  Unless required by applicable law or agreed to in writing, software
19  *  distributed under the License is distributed on an "AS IS" BASIS,
20  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21  *  See the License for the specific language governing permissions and
22  *  limitations under the License.
23  */
24 package org.collectionspace.services.common.vocabulary.nuxeo;
25
26 import org.collectionspace.services.client.AuthorityClient;
27 import org.collectionspace.services.client.PayloadInputPart;
28 import org.collectionspace.services.client.PayloadOutputPart;
29 import org.collectionspace.services.client.PoxPayloadIn;
30 import org.collectionspace.services.client.PoxPayloadOut;
31 import org.collectionspace.services.client.RelationClient;
32 import org.collectionspace.services.common.api.CommonAPI;
33 import org.collectionspace.services.common.api.RefName;
34 import org.collectionspace.services.common.api.Tools;
35 import org.collectionspace.services.common.context.MultipartServiceContext;
36 import org.collectionspace.services.common.context.ServiceBindingUtils;
37 import org.collectionspace.services.common.context.ServiceContext;
38 import org.collectionspace.services.common.document.DocumentException;
39 import org.collectionspace.services.common.document.DocumentWrapper;
40 import org.collectionspace.services.common.document.DocumentWrapperImpl;
41 import org.collectionspace.services.common.relation.IRelationsManager;
42 import org.collectionspace.services.common.repository.RepositoryClient;
43 import org.collectionspace.services.common.repository.RepositoryClientFactory;
44 import org.collectionspace.services.common.service.ObjectPartType;
45 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
46 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
47 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
48 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
49 import org.collectionspace.services.common.service.ListResultField;
50 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
51 import org.collectionspace.services.relation.RelationResource;
52 import org.collectionspace.services.relation.RelationsCommon;
53 import org.collectionspace.services.relation.RelationsCommonList;
54 import org.collectionspace.services.relation.RelationsDocListItem;
55 import org.collectionspace.services.relation.RelationshipType;
56 import org.nuxeo.ecm.core.api.DocumentModel;
57 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61 import javax.ws.rs.core.MultivaluedMap;
62 import javax.ws.rs.core.UriInfo;
63 import java.util.ArrayList;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.regex.Matcher;
67 import java.util.regex.Pattern;
68
69 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
70 /**
71  * AuthorityItemDocumentModelHandler
72  *
73  * $LastChangedRevision: $
74  * $LastChangedDate: $
75  */
76 public abstract class AuthorityItemDocumentModelHandler<AICommon>
77         extends DocHandlerBase<AICommon> {
78
79     private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
80     private String authorityItemCommonSchemaName;
81     /**
82      * inVocabulary is the parent Authority for this context
83      */
84     protected String inAuthority;
85     protected String authorityRefNameBase;
86     // Used to determine when the displayName changes as part of the update.
87     protected String oldDisplayNameOnUpdate = null;
88     protected String oldRefNameOnUpdate = null;
89     protected String newRefNameOnUpdate = null;
90
91     public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
92         this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
93     }
94
95     public String getInAuthority() {
96         return inAuthority;
97     }
98
99     public void setInAuthority(String inAuthority) {
100         this.inAuthority = inAuthority;
101     }
102
103     /** Subclasses may override this to customize the URI segment. */
104     public String getAuthorityServicePath() {
105         return getServiceContext().getServiceName().toLowerCase();    // Laramie20110510 CSPACE-3932
106     }
107
108     @Override
109     public String getUri(DocumentModel docModel) {
110         // Laramie20110510 CSPACE-3932
111         String authorityServicePath = getAuthorityServicePath();
112         return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
113     }
114
115     public String getAuthorityRefNameBase() {
116         return this.authorityRefNameBase;
117     }
118
119     public void setAuthorityRefNameBase(String value) {
120         this.authorityRefNameBase = value;
121     }
122
123     @Override
124     public List<ListResultField> getListItemsArray() throws DocumentException {
125         List<ListResultField> list = super.getListItemsArray();
126         int nFields = list.size();
127         // Ensure some common fields so do not depend upon config for general logic
128         boolean hasDisplayName = false;
129         boolean hasShortId = false;
130         boolean hasRefName = false;
131         boolean hasTermStatus = false;
132         for (int i = 0; i < nFields; i++) {
133             ListResultField field = list.get(i);
134             String elName = field.getElement();
135             if (AuthorityItemJAXBSchema.DISPLAY_NAME.equals(elName)) {
136                 hasDisplayName = true;
137             } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
138                 hasShortId = true;
139             } else if (AuthorityItemJAXBSchema.REF_NAME.equals(elName)) {
140                 hasRefName = true;
141             } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
142                 hasTermStatus = true;
143             }
144         }
145         ListResultField field;
146         if (!hasDisplayName) {
147             field = new ListResultField();
148             field.setElement(AuthorityItemJAXBSchema.DISPLAY_NAME);
149             field.setXpath(AuthorityItemJAXBSchema.DISPLAY_NAME);
150             list.add(field);
151         }
152         if (!hasShortId) {
153             field = new ListResultField();
154             field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
155             field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
156             list.add(field);
157         }
158         if (!hasRefName) {
159             field = new ListResultField();
160             field.setElement(AuthorityItemJAXBSchema.REF_NAME);
161             field.setXpath(AuthorityItemJAXBSchema.REF_NAME);
162             list.add(field);
163         }
164         if (!hasTermStatus) {
165             field = new ListResultField();
166             field.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
167             field.setXpath(AuthorityItemJAXBSchema.TERM_STATUS);
168             list.add(field);
169         }
170         return list;
171
172     }
173
174
175     /* (non-Javadoc)
176      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
177      */
178     @Override
179     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
180         // first fill all the parts of the document
181         super.handleCreate(wrapDoc);
182         // Ensure we have required fields set properly
183         handleInAuthority(wrapDoc.getWrappedObject());
184
185         handleComputedDisplayNames(wrapDoc.getWrappedObject());
186         String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
187                 AuthorityItemJAXBSchema.DISPLAY_NAME);
188         if (Tools.isEmpty(displayName)) {
189             logger.warn("Creating Authority Item with no displayName!");
190         }
191         // CSPACE-3178:
192         handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
193         // refName includes displayName, so we force a correct value here.
194         updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
195     }
196
197     /* (non-Javadoc)
198      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
199      */
200     @Override
201     public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
202         // First, get a copy of the old displayName
203         oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
204                 AuthorityItemJAXBSchema.DISPLAY_NAME);
205         oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
206                 AuthorityItemJAXBSchema.REF_NAME);
207         super.handleUpdate(wrapDoc);
208         handleComputedDisplayNames(wrapDoc.getWrappedObject());
209         String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
210                 AuthorityItemJAXBSchema.DISPLAY_NAME);
211         if (newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
212             // Need to update the refName, and then fix all references.
213             newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
214         } else {
215             // Mark as not needing attention in completeUpdate phase.
216             newRefNameOnUpdate = null;
217             oldRefNameOnUpdate = null;
218         }
219     }
220
221     /**
222      * Handle display name.
223      *
224      * @param docModel the doc model
225      * @throws Exception the exception
226      */
227     protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
228         // Do nothing by default.
229     }
230
231     /**
232      * Handle refName updates for changes to display name.
233      * Assumes refName is already correct. Just ensures it is right.
234      *
235      * @param docModel the doc model
236      * @throws Exception the exception
237      */
238     protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
239             String newDisplayName) throws Exception {
240         //String suppliedRefName = (String) docModel.getProperty(authorityItemCommonSchemaName, 
241         //                                                                                                              AuthorityItemJAXBSchema.REF_NAME);
242         RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
243         if (authItem == null) {
244             String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
245             logger.debug(err);
246             throw new IllegalArgumentException(err);
247         }
248         authItem.displayName = newDisplayName;
249         String updatedRefName = authItem.toString();
250         docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
251         return updatedRefName;
252     }
253
254     /**
255      * Checks to see if the refName has changed, and if so, 
256      * uses utilities to find all references and update them.
257      */
258     protected void handleItemRefNameReferenceUpdate() {
259         if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
260             // We have work to do.
261             if (logger.isDebugEnabled()) {
262                 String eol = System.getProperty("line.separator");
263                 logger.debug("Need to find and update references to Item." + eol
264                         + "   Old refName" + oldRefNameOnUpdate + eol
265                         + "   New refName" + newRefNameOnUpdate);
266             }
267             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
268             RepositoryClient repoClient = getRepositoryClient(ctx);
269             // HACK - this should be defined for each handler, as with 
270             // AuthorityResource.getRefPropName()
271             String refNameProp = ServiceBindingUtils.AUTH_REF_PROP;
272
273             int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient,
274                     oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
275             if (logger.isDebugEnabled()) {
276                 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
277             }
278         }
279     }
280
281     private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
282         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
283         String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
284         String shortDisplayName = "";
285         try {
286             shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
287         } catch (PropertyNotFoundException pnfe) {
288             // Do nothing on exception. Some vocabulary schemas may not include a short display name.
289         }
290         if (Tools.isEmpty(shortIdentifier)) {
291             String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
292             docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
293         }
294     }
295
296     protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
297             String schemaName,
298             String authorityRefBaseName) throws Exception {
299         DocumentModel docModel = wrapDoc.getWrappedObject();
300         String suppliedRefName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME);
301         // CSPACE-3178:
302         // Temporarily accept client-supplied refName values, rather than always generating such values.
303         // Remove first block and the surrounding 'if' statement when clients should no longer supply refName values.
304         if (!Tools.isEmpty(suppliedRefName)) {
305             // Supplied refName must at least be legal
306             RefName.AuthorityItem item = RefName.AuthorityItem.parse(suppliedRefName);
307             if (item == null) {
308                 logger.error("Passed refName for authority item not legal: " + suppliedRefName);
309                 suppliedRefName = null; // Clear this and compute a new one below.
310             }
311         }
312         // Recheck, in case we cleared it for being illegal
313         if (Tools.isEmpty(suppliedRefName)) {
314             String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
315             String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
316             if (Tools.isEmpty(authorityRefBaseName)) {
317                 throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
318             }
319             RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
320             String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
321             docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
322         }
323     }
324
325     /**
326      * Check the logic around the parent pointer. Note that we only need do this on
327      * create, since we have logic to make this read-only on update. 
328      * 
329      * @param docModel
330      * 
331      * @throws Exception the exception
332      */
333     private void handleInAuthority(DocumentModel docModel) throws Exception {
334         docModel.setProperty(authorityItemCommonSchemaName,
335                 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
336     }
337
338
339     /* (non-Javadoc)
340      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
341      */
342     @Override
343     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
344             throws Exception {
345         Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
346
347         // Add the CSID to the common part, since they may have fetched via the shortId.
348         if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
349             String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
350             unQObjectProperties.put("csid", csid);
351         }
352
353         return unQObjectProperties;
354     }
355
356     /**
357      * Filters out selected values supplied in an update request.
358      * 
359      * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
360      * that the link to the item's parent remains untouched.
361      * 
362      * @param objectProps the properties filtered out from the update payload
363      * @param partMeta metadata for the object to fill
364      */
365     @Override
366     public void filterReadOnlyPropertiesForPart(
367             Map<String, Object> objectProps, ObjectPartType partMeta) {
368         super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
369         String commonPartLabel = getServiceContext().getCommonPartLabel();
370         if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
371             objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
372             objectProps.remove(AuthorityItemJAXBSchema.CSID);
373             objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
374             // Enable when clients should no longer supply refName values
375             // objectProps.remove(AuthorityItemJAXBSchema.REF_NAME); // CSPACE-3178
376
377         }
378     }
379
380     @Override
381     public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
382         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
383         super.extractAllParts(wrapDoc);
384
385         String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
386         if (Tools.isTrue(showSiblings)) {
387             showSiblings(wrapDoc, ctx);
388             return;   // actual result is returned on ctx.addOutputPart();
389         }
390
391         String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
392         if (Tools.isTrue(showRelations)) {
393             showRelations(wrapDoc, ctx);
394             return;   // actual result is returned on ctx.addOutputPart();
395         }
396
397         String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
398         if (Tools.isTrue(showAllRelations)) {
399             showAllRelations(wrapDoc, ctx);
400             return;   // actual result is returned on ctx.addOutputPart();
401         }
402     }
403
404     /** @return null on parent not found
405      */
406     protected String getParentCSID(String thisCSID) throws Exception {
407         String parentCSID = null;
408         try {
409             String predicate = RelationshipType.HAS_BROADER.value();
410             RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
411             List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
412             if (parentList != null) {
413                 if (parentList.size() == 0) {
414                     return null;
415                 }
416                 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
417                 parentCSID = relationListItem.getObjectCsid();
418             }
419             return parentCSID;
420         } catch (Exception e) {
421             logger.error("Could not find parent for this: " + thisCSID, e);
422             return null;
423         }
424     }
425
426     public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
427             MultipartServiceContext ctx) throws Exception {
428         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
429
430         String predicate = RelationshipType.HAS_BROADER.value();
431         RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
432         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
433
434         RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
435         List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
436
437         //Assume that there are more children than parents.  Will be true for parent/child, but maybe not for other relations.
438         //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
439         //Not optimal, but that's the current design spec.
440         long added = 0;
441         for (RelationsCommonList.RelationListItem parent : parentList) {
442             childrenList.add(parent);
443             added++;
444         }
445         long childrenSize = childrenList.size();
446         childrenListOuter.setTotalItems(childrenSize);
447         childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
448
449         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
450         ctx.addOutputPart(relationsPart);
451     }
452
453     public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
454             MultipartServiceContext ctx) throws Exception {
455         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
456         String parentCSID = getParentCSID(thisCSID);
457         if (parentCSID == null) {
458             logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
459             return;
460         }
461
462         String predicate = RelationshipType.HAS_BROADER.value();
463         RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
464         List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
465
466         List<RelationsCommonList.RelationListItem> toRemoveList = newList();
467
468
469         RelationsCommonList.RelationListItem item = null;
470         for (RelationsCommonList.RelationListItem sibling : siblingList) {
471             if (thisCSID.equals(sibling.getSubjectCsid())) {
472                 toRemoveList.add(sibling);   //IS_A copy of the main item, i.e. I have a parent that is my parent, so I'm in the list from the above query.
473             }
474         }
475         //rather than create an immutable iterator, I'm just putting the items to remove on a separate list, then looping over that list and removing.
476         for (RelationsCommonList.RelationListItem self : toRemoveList) {
477             removeFromList(siblingList, self);
478         }
479
480         long siblingSize = siblingList.size();
481         siblingListOuter.setTotalItems(siblingSize);
482         siblingListOuter.setItemsInPage(siblingSize);
483
484         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
485         ctx.addOutputPart(relationsPart);
486     }
487
488     public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
489         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
490
491         RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null);   //  nulls are wildcards:  predicate=*, and object=*
492         List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
493
494         RelationsCommonList objectListOuter = getRelations(null, thisCSID, null);   //  nulls are wildcards:  subject=*, and predicate=*
495         List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
496
497         //  MERGE LISTS:
498         subjectList.addAll(objectList);
499
500         //now subjectList actually has records BOTH where thisCSID is subject and object.
501         long relatedSize = subjectList.size();
502         subjectListOuter.setTotalItems(relatedSize);
503         subjectListOuter.setItemsInPage(relatedSize);
504
505         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
506         ctx.addOutputPart(relationsPart);
507     }
508
509     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
510         super.fillAllParts(wrapDoc, action);
511         /*
512         ServiceContext ctx = getServiceContext();
513         PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
514         DocumentModel documentModel = (wrapDoc.getWrappedObject());
515         String itemCsid = documentModel.getName();
516         
517         //UPDATE and CREATE will call.   Updates relations part
518         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
519         
520         PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
521         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
522          */
523     }
524
525     public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
526         super.completeCreate(wrapDoc);
527         handleRelationsPayload(wrapDoc, false);
528     }
529
530     public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
531         super.completeUpdate(wrapDoc);
532         handleRelationsPayload(wrapDoc, true);
533         handleItemRefNameReferenceUpdate();
534     }
535
536     // Note that we must do this after we have completed the Update, so that the repository has the
537     // info for the item itself. The relations code must call into the repo to get info for each end.
538     // This could be optimized to pass in the parent docModel, since it will often be one end.
539     // Nevertheless, we should complete the item save before we do work on the relations, especially
540     // since a save on Create might fail, and we would not want to create relations for something
541     // that may not be created...
542     private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
543         ServiceContext ctx = getServiceContext();
544         PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
545         DocumentModel documentModel = (wrapDoc.getWrappedObject());
546         String itemCsid = documentModel.getName();
547
548         //Updates relations part
549         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
550
551         PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
552         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
553
554         //now we add part for relations list
555         //ServiceContext ctx = getServiceContext();
556         //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
557         ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
558     }
559
560     /**  updateRelations strategy:
561     
562     go through inboundList, remove anything from childList that matches  from childList
563     go through inboundList, remove anything from parentList that matches  from parentList
564     go through parentList, delete all remaining
565     go through childList, delete all remaining
566     go through actionList, add all remaining.
567     check for duplicate children
568     check for more than one parent.
569     
570     inboundList                           parentList                      childList          actionList
571     ----------------                          ---------------                  ----------------       ----------------
572     child-a                                   parent-c                        child-a             child-b
573     child-b                                   parent-d                        child-c
574     parent-a
575      */
576     private RelationsCommonList updateRelations(
577             String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
578             throws Exception {
579         if (logger.isTraceEnabled()) {
580             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
581         }
582         PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME);        //input.getPart("relations_common");
583         if (part == null) {
584             return null;  //nothing to do--they didn't send a list of relations.
585         }
586         RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
587         List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
588         List<RelationsCommonList.RelationListItem> actionList = newList();
589         List<RelationsCommonList.RelationListItem> childList = null;
590         List<RelationsCommonList.RelationListItem> parentList = null;
591         DocumentModel docModel = wrapDoc.getWrappedObject();
592
593         ServiceContext ctx = getServiceContext();
594         //Do magic replacement of ${itemCSID} and fix URI's.
595         fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
596
597         String HAS_BROADER = RelationshipType.HAS_BROADER.value();
598         UriInfo uriInfo = ctx.getUriInfo();
599         MultivaluedMap queryParams = uriInfo.getQueryParameters();
600
601         if (fUpdate) {
602             //Run getList() once as sent to get childListOuter:
603             String predicate = RelationshipType.HAS_BROADER.value();
604             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
605             queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
606             queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
607             queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
608             queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
609             RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo());    //magically knows all query params because they are in the context.
610
611             //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
612             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
613             queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
614             queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
615             RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
616
617
618             childList = childListOuter.getRelationListItem();
619             parentList = parentListOuter.getRelationListItem();
620
621             if (parentList.size() > 1) {
622                 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
623             }
624
625             if (logger.isTraceEnabled()) {
626                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
627             }
628         }
629
630
631         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
632             // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
633             // and so the CSID for those may be null
634             if (itemCSID.equals(inboundItem.getObject().getCsid())
635                     && inboundItem.getPredicate().equals(HAS_BROADER)) {
636                 //then this is an item that says we have a child.  That child is inboundItem
637                 RelationsCommonList.RelationListItem childItem =
638                         (childList == null) ? null : findInList(childList, inboundItem);
639                 if (childItem != null) {
640                     removeFromList(childList, childItem);    //exists, just take it off delete list
641                 } else {
642                     actionList.add(inboundItem);   //doesn't exist as a child, but is a child.  Add to additions list
643                 }
644                 ensureChildHasNoOtherParents(ctx, queryParams, inboundItem.getSubject().getCsid());
645
646             } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
647                     && inboundItem.getPredicate().equals(HAS_BROADER)) {
648                 //then this is an item that says we have a parent.  inboundItem is that parent.
649                 RelationsCommonList.RelationListItem parentItem =
650                         (parentList == null) ? null : findInList(parentList, inboundItem);
651                 if (parentItem != null) {
652                     removeFromList(parentList, parentItem);    //exists, just take it off delete list
653                 } else {
654                     actionList.add(inboundItem);   //doesn't exist as a parent, but is a parent. Add to additions list
655                 }
656             } else {
657                 logger.warn("Element didn't match parent or child, but may have partial fields that match. inboundItem: " + inboundItem);
658                 //not dealing with: hasNarrower or any other predicate.
659             }
660         }
661         if (logger.isTraceEnabled()) {
662             String dump = dumpLists(itemCSID, parentList, childList, actionList);
663             //System.out.println("====dump====="+CR+dump);
664             logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
665         }
666         if (fUpdate) {
667             if (logger.isTraceEnabled()) {
668                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
669                         + parentList.size() + " existing parents and " + childList.size() + " existing children.");
670             }
671             deleteRelations(parentList, ctx, "parentList");               //todo: there are items appearing on both lists....april 20.
672             deleteRelations(childList, ctx, "childList");
673         }
674         if (logger.isTraceEnabled()) {
675             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
676                     + actionList.size() + " new parents and children.");
677         }
678         createRelations(actionList, ctx);
679         if (logger.isTraceEnabled()) {
680             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
681         }
682         //We return all elements on the inbound list, since we have just worked to make them exist in the system
683         // and be non-redundant, etc.  That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
684         return relationsCommonListBody;
685     }
686
687     private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
688         queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
689         queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
690         queryParams.putSingle(IRelationsManager.OBJECT_QP, null);  //null means ANY
691         RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
692         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
693         //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
694         deleteRelations(parentList, ctx, "parentList-delete");
695     }
696
697     private String dumpLists(String itemCSID,
698             List<RelationsCommonList.RelationListItem> parentList,
699             List<RelationsCommonList.RelationListItem> childList,
700             List<RelationsCommonList.RelationListItem> actionList) {
701         StringBuffer sb = new StringBuffer();
702         sb.append("itemCSID: " + itemCSID + CR);
703         sb.append(dumpList(parentList, "parentList"));
704         sb.append(dumpList(childList, "childList"));
705         sb.append(dumpList(actionList, "actionList"));
706         return sb.toString();
707     }
708     private final static String CR = "\r\n";
709     private final static String T = " ";
710
711     private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
712         StringBuffer sb = new StringBuffer();
713         String s;
714         if (list.size() > 0) {
715             sb.append("=========== " + label + " ==========" + CR);
716         }
717         for (RelationsCommonList.RelationListItem item : list) {
718             s =
719                     T + item.getSubject().getCsid() //+T4 + item.getSubject().getUri()
720                     + T + item.getPredicate()
721                     + T + item.getObject().getCsid() //+T4  + item.getObject().getUri()
722                     + CR //+"subject:{"+item.getSubject()+"}\r\n object:{"+item.getObject()+"}"
723                     //+ CR + "relation-record: {"+item+"}"
724                     ;
725             sb.append(s);
726
727         }
728         return sb.toString();
729     }
730
731     /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
732      *   and sets URI correctly for related items.
733      *   Operates directly on the items in the list.  Does not change the list ordering, does not add or remove any items.
734      */
735     protected void fixupInboundListItems(ServiceContext ctx,
736             List<RelationsCommonList.RelationListItem> inboundList,
737             DocumentModel docModel,
738             String itemCSID) throws Exception {
739         String thisURI = this.getUri(docModel);
740         // WARNING:  the two code blocks below are almost identical  and seem to ask to be put in a generic method.
741         //                    beware of the little diffs in  inboundItem.setObjectCsid(itemCSID); and   inboundItem.setSubjectCsid(itemCSID); in the two blocks.
742         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
743             RelationsDocListItem inboundItemObject = inboundItem.getObject();
744             RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
745
746             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
747                 inboundItem.setObjectCsid(itemCSID);
748                 inboundItemObject.setCsid(itemCSID);
749                 //inboundItemObject.setUri(getUri(docModel));
750             } else {
751                 /*
752                 String objectCsid = inboundItemObject.getCsid();
753                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid);    //null if not found.
754                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
755                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
756                 inboundItemObject.setUri(uri);    //CSPACE-4037
757                  */
758             }
759             //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri());    //CSPACE-4042
760
761             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
762                 inboundItem.setSubjectCsid(itemCSID);
763                 inboundItemSubject.setCsid(itemCSID);
764                 //inboundItemSubject.setUri(getUri(docModel));
765             } else {
766                 /*
767                 String subjectCsid = inboundItemSubject.getCsid();
768                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid);    //null if not found.
769                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
770                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
771                 inboundItemSubject.setUri(uri);    //CSPACE-4037
772                  */
773             }
774             //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri());  //CSPACE-4042
775
776         }
777     }
778
779     // this method calls the RelationResource to have it create the relations and persist them.
780     private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
781         for (RelationsCommonList.RelationListItem item : inboundList) {
782             RelationsCommon rc = new RelationsCommon();
783             //rc.setCsid(item.getCsid());
784             //todo: assignTo(item, rc);
785             RelationsDocListItem itemSubject = item.getSubject();
786             RelationsDocListItem itemObject = item.getObject();
787
788             // Set at least one of CSID and refName for Subject and Object
789             // Either value might be null for for each of Subject and Object 
790             String subjectCsid = itemSubject.getCsid();
791             rc.setSubjectCsid(subjectCsid);
792             rc.setDocumentId1(subjectCsid); // populate legacy field for backward compatibility
793
794             String objCsid = itemObject.getCsid();
795             rc.setObjectCsid(objCsid);
796             rc.setDocumentId2(objCsid); // populate legacy field for backward compatibility
797
798             rc.setSubjectRefName(itemSubject.getRefName());
799             rc.setObjectRefName(itemObject.getRefName());
800
801             rc.setRelationshipType(item.getPredicate());
802             //RelationshipType  foo = (RelationshipType.valueOf(item.getPredicate())) ;
803             //rc.setPredicate(foo);     //this must be one of the type found in the enum in  services/jaxb/src/main/resources/relations_common.xsd
804
805             // This is superfluous, since it will be fetched by the Relations Create logic.
806             rc.setSubjectDocumentType(itemSubject.getDocumentType());
807             rc.setObjectDocumentType(itemObject.getDocumentType());
808             // populate legacy fields for backward compatibility
809             rc.setDocumentType1(itemSubject.getDocumentType());
810             rc.setDocumentType2(itemObject.getDocumentType());
811
812             // This is superfluous, since it will be fetched by the Relations Create logic.
813             rc.setSubjectUri(itemSubject.getUri());
814             rc.setObjectUri(itemObject.getUri());
815             // May not have the info here. Only really require CSID or refName. 
816             // Rest is handled in the Relation create mechanism
817             //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
818
819             PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
820             PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
821             payloadOut.addPart(outputPart);
822             //System.out.println("\r\n==== TO CREATE: "+rc.getDocumentId1()+"==>"+rc.getPredicate()+"==>"+rc.getDocumentId2());
823             RelationResource relationResource = new RelationResource();
824             Object res = relationResource.create(ctx.getResourceMap(),
825                     ctx.getUriInfo(), payloadOut.toXML());    //NOTE ui recycled from above to pass in unknown query params.
826         }
827     }
828
829     private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
830         try {
831             //if (list.size()>0){ logger.info("==== deleteRelations from : "+listName); }
832             for (RelationsCommonList.RelationListItem item : list) {
833                 RelationResource relationResource = new RelationResource();
834                 //logger.info("==== TO DELETE: " + item.getCsid() + ": " + item.getSubject().getCsid() + "--" + item.getPredicate() + "-->" + item.getObject().getCsid());
835                 Object res = relationResource.delete(item.getCsid());
836             }
837         } catch (Throwable t) {
838             String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
839             logger.error(msg);
840         }
841     }
842
843     private List<RelationsCommonList.RelationListItem> newList() {
844         List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
845         return result;
846     }
847
848     protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
849         List<RelationsCommonList.RelationListItem> result = newList();
850         for (RelationsCommonList.RelationListItem item : inboundList) {
851             result.add(item);
852         }
853         return result;
854     }
855
856     private RelationsCommonList.RelationListItem findInList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
857         for (RelationsCommonList.RelationListItem listItem : list) {
858             if (itemsEqual(listItem, item)) {   //equals must be defined, else
859                 return listItem;
860             }
861         }
862         return null;
863     }
864
865     private boolean itemsEqual(RelationsCommonList.RelationListItem item, RelationsCommonList.RelationListItem item2) {
866         if (item == null || item2 == null) {
867             return false;
868         }
869         RelationsDocListItem subj1 = item.getSubject();
870         RelationsDocListItem subj2 = item2.getSubject();
871         RelationsDocListItem obj1 = item.getObject();
872         RelationsDocListItem obj2 = item2.getObject();
873
874         return (subj1.getCsid().equals(subj2.getCsid()))
875                 && (obj1.getCsid().equals(obj1.getCsid()))
876                 && ((item.getPredicate().equals(item2.getPredicate()))
877                 && (item.getRelationshipType().equals(item2.getRelationshipType())))
878                 && (obj1.getDocumentType().equals(obj2.getDocumentType()))
879                 && (subj1.getDocumentType().equals(subj2.getDocumentType()));
880     }
881
882     private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
883         list.remove(item);
884     }
885
886     /* don't even THINK of re-using this method.
887      * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
888      */
889     private String extractInAuthorityCSID(String uri) {
890         String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
891         Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
892         Matcher m = p.matcher(uri);
893         if (m.find()) {
894             if (m.groupCount() < 3) {
895                 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
896                 return "";
897             } else {
898                 //String service = m.group(1);
899                 String inauth = m.group(2);
900                 //String theRest = m.group(3);
901                 return inauth;
902                 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
903             }
904         } else {
905             logger.warn("REGEX-NOT-MATCHED looking in " + uri);
906             return "";
907         }
908     }
909
910     //ensures CSPACE-4042
911     protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
912         String authorityCSID = extractInAuthorityCSID(thisURI);
913         String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
914         if (Tools.isBlank(authorityCSID)
915                 || Tools.isBlank(authorityCSIDForInbound)
916                 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
917             throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
918         }
919     }
920
921     //================= TODO: move this to common, refactoring this and  CollectionObjectResource.java
922     public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
923         ServiceContext ctx = getServiceContext();
924         MultivaluedMap queryParams = ctx.getQueryParams();
925         queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
926         queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
927         queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
928
929         RelationResource relationResource = new RelationResource();
930         RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
931         return relationsCommonList;
932     }
933     //============================= END TODO refactor ==========================
934 }