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