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