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