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