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