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