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