]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
e439c38b3f774223e48183c9ccaec6f92100c61b
[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.ResourceBase;
33 import org.collectionspace.services.common.ServiceMessages;
34 import org.collectionspace.services.common.api.CommonAPI;
35 import org.collectionspace.services.common.api.RefName;
36 import org.collectionspace.services.common.api.Tools;
37 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
38 import org.collectionspace.services.common.context.MultipartServiceContext;
39 import org.collectionspace.services.common.context.ServiceBindingUtils;
40 import org.collectionspace.services.common.context.ServiceContext;
41 import org.collectionspace.services.common.document.DocumentException;
42 import org.collectionspace.services.common.document.DocumentFilter;
43 import org.collectionspace.services.common.document.DocumentWrapper;
44 import org.collectionspace.services.common.document.DocumentWrapperImpl;
45 import org.collectionspace.services.common.relation.IRelationsManager;
46 import org.collectionspace.services.common.repository.RepositoryClient;
47 import org.collectionspace.services.common.repository.RepositoryClientFactory;
48 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
49 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
50 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
51 import org.collectionspace.services.config.service.ListResultField;
52 import org.collectionspace.services.config.service.ObjectPartType;
53 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
54 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
55 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
56 import org.collectionspace.services.relation.RelationResource;
57 import org.collectionspace.services.relation.RelationsCommon;
58 import org.collectionspace.services.relation.RelationsCommonList;
59 import org.collectionspace.services.relation.RelationsDocListItem;
60 import org.collectionspace.services.relation.RelationshipType;
61 import org.nuxeo.ecm.core.api.ClientException;
62 import org.nuxeo.ecm.core.api.DocumentModel;
63 import org.nuxeo.ecm.core.api.model.PropertyException;
64 import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
65 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
66 import org.nuxeo.runtime.transaction.TransactionHelper;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 import javax.ws.rs.PathParam;
71 import javax.ws.rs.WebApplicationException;
72 import javax.ws.rs.core.Context;
73 import javax.ws.rs.core.MultivaluedMap;
74 import javax.ws.rs.core.Response;
75 import javax.ws.rs.core.UriInfo;
76 import java.util.ArrayList;
77 import java.util.List;
78 import java.util.Map;
79 import java.util.regex.Matcher;
80 import java.util.regex.Pattern;
81
82 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
83 /**
84  * AuthorityItemDocumentModelHandler
85  *
86  * $LastChangedRevision: $
87  * $LastChangedDate: $
88  */
89 public abstract class AuthorityItemDocumentModelHandler<AICommon>
90         extends DocHandlerBase<AICommon> {
91
92     private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
93     private String authorityItemCommonSchemaName;
94     /**
95      * inVocabulary is the parent Authority for this context
96      */
97     protected String inAuthority = null;
98     protected String authorityRefNameBase = null;
99     // Used to determine when the displayName changes as part of the update.
100     protected String oldDisplayNameOnUpdate = null;
101     protected String oldRefNameOnUpdate = null;
102     protected String newRefNameOnUpdate = null;
103
104     public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
105         this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
106     }
107
108     public void setInAuthority(String inAuthority) {
109         this.inAuthority = inAuthority;
110     }
111
112     /** Subclasses may override this to customize the URI segment. */
113     public String getAuthorityServicePath() {
114         return getServiceContext().getServiceName().toLowerCase();    // Laramie20110510 CSPACE-3932
115     }
116
117     @Override
118     public String getUri(DocumentModel docModel) {
119         // Laramie20110510 CSPACE-3932
120         String authorityServicePath = getAuthorityServicePath();
121         if(inAuthority==null) { // Only happens on queries to wildcarded authorities
122                 try {
123                         inAuthority = (String) docModel.getProperty(authorityItemCommonSchemaName,
124                         AuthorityItemJAXBSchema.IN_AUTHORITY);
125                 } catch (ClientException pe) {
126                         throw new RuntimeException("Could not get parent specifier for item!");
127                 }
128         }
129         return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
130     }
131
132     protected String getAuthorityRefNameBase() {
133         return this.authorityRefNameBase;
134     }
135
136     public void setAuthorityRefNameBase(String value) {
137         this.authorityRefNameBase = value;
138     }
139
140     @Override
141     public List<ListResultField> getListItemsArray() throws DocumentException {
142         List<ListResultField> list = super.getListItemsArray();
143         int nFields = list.size();
144         // Ensure some common fields so do not depend upon config for general logic
145         boolean hasDisplayName = false;
146         boolean hasShortId = false;
147         boolean hasRefName = false;
148         boolean hasTermStatus = false;
149         for (int i = 0; i < nFields; i++) {
150             ListResultField field = list.get(i);
151             String elName = field.getElement();
152             if (AuthorityItemJAXBSchema.DISPLAY_NAME.equals(elName)) {
153                 hasDisplayName = true;
154             } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
155                 hasShortId = true;
156             } else if (AuthorityItemJAXBSchema.REF_NAME.equals(elName)) {
157                 hasRefName = true;
158             } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
159                 hasTermStatus = true;
160             }
161         }
162         ListResultField field;
163         if (!hasDisplayName) {
164             field = new ListResultField();
165             field.setElement(AuthorityItemJAXBSchema.DISPLAY_NAME);
166             field.setXpath(AuthorityItemJAXBSchema.DISPLAY_NAME);
167             list.add(field);
168         }
169         if (!hasShortId) {
170             field = new ListResultField();
171             field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
172             field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
173             list.add(field);
174         }
175         if (!hasRefName) {
176             field = new ListResultField();
177             field.setElement(AuthorityItemJAXBSchema.REF_NAME);
178             field.setXpath(AuthorityItemJAXBSchema.REF_NAME);
179             list.add(field);
180         }
181         if (!hasTermStatus) {
182             field = new ListResultField();
183             field.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
184             field.setXpath(AuthorityItemJAXBSchema.TERM_STATUS);
185             list.add(field);
186         }
187         return list;
188
189     }
190
191
192     /* (non-Javadoc)
193      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
194      */
195     @Override
196     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
197         // first fill all the parts of the document
198         super.handleCreate(wrapDoc);
199         // Ensure we have required fields set properly
200         handleInAuthority(wrapDoc.getWrappedObject());
201
202         handleComputedDisplayNames(wrapDoc.getWrappedObject());
203         String displayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
204                 AuthorityItemJAXBSchema.DISPLAY_NAME);
205         if (Tools.isEmpty(displayName)) {
206             logger.warn("Creating Authority Item with no displayName!");
207         }
208         // CSPACE-3178:
209         handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName);
210         // refName includes displayName, so we force a correct value here.
211         updateRefnameForAuthorityItem(wrapDoc, authorityItemCommonSchemaName, getAuthorityRefNameBase());
212     }
213
214     /* (non-Javadoc)
215      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
216      */
217     @Override
218     public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
219         // First, get a copy of the old displayName
220         oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
221                 AuthorityItemJAXBSchema.DISPLAY_NAME);
222         oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
223                 AuthorityItemJAXBSchema.REF_NAME);
224         super.handleUpdate(wrapDoc);
225         handleComputedDisplayNames(wrapDoc.getWrappedObject());
226         String newDisplayName = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
227                 AuthorityItemJAXBSchema.DISPLAY_NAME);
228         if (newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
229             // Need to update the refName, and then fix all references.
230             newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
231         } else {
232             // Mark as not needing attention in completeUpdate phase.
233             newRefNameOnUpdate = null;
234             oldRefNameOnUpdate = null;
235         }
236     }
237
238     /**
239      * Handle display name.
240      *
241      * @param docModel the doc model
242      * @throws Exception the exception
243      */
244     protected void handleComputedDisplayNames(DocumentModel docModel) throws Exception {
245         // Do nothing by default.
246     }
247
248     /**
249      * Handle refName updates for changes to display name.
250      * Assumes refName is already correct. Just ensures it is right.
251      *
252      * @param docModel the doc model
253      * @param newDisplayName the new display name
254      * @throws Exception the exception
255      */
256     protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
257             String newDisplayName) throws Exception {
258         RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
259         if (authItem == null) {
260             String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
261             logger.debug(err);
262             throw new IllegalArgumentException(err);
263         }
264         authItem.displayName = newDisplayName;
265         String updatedRefName = authItem.toString();
266         docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName);
267         return updatedRefName;
268     }
269     
270     protected String getRefPropName() {
271         return ServiceBindingUtils.AUTH_REF_PROP;
272     }
273
274     /**
275      * Checks to see if the refName has changed, and if so, 
276      * uses utilities to find all references and update them.
277      * @throws Exception 
278      */
279     protected void handleItemRefNameReferenceUpdate() throws Exception {
280         if (newRefNameOnUpdate != null && oldRefNameOnUpdate != null) {
281             // We have work to do.
282             if (logger.isDebugEnabled()) {
283                 String eol = System.getProperty("line.separator");
284                 logger.debug("Need to find and update references to Item." + eol
285                         + "   Old refName" + oldRefNameOnUpdate + eol
286                         + "   New refName" + newRefNameOnUpdate);
287             }
288             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
289             RepositoryClient repoClient = getRepositoryClient(ctx);
290             String refNameProp = getRefPropName();
291
292             int nUpdated = RefNameServiceUtils.updateAuthorityRefDocs(ctx, repoClient, this.getRepositorySession(),
293                     oldRefNameOnUpdate, newRefNameOnUpdate, refNameProp);
294             if (logger.isDebugEnabled()) {
295                 logger.debug("Updated " + nUpdated + " instances of oldRefName to newRefName");
296             }
297         }
298     }
299
300     /**
301      * If no short identifier was provided in the input payload,
302      * generate a short identifier from the display name.
303      */
304     private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
305         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
306         String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
307         String shortDisplayName = "";
308         try {
309             shortDisplayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_DISPLAY_NAME);
310         } catch (PropertyNotFoundException pnfe) {
311             // Do nothing on exception. Some vocabulary schemas may not include a short display name.
312         }
313         if (Tools.isEmpty(shortIdentifier)) {
314             String generatedShortIdentifier =
315                     AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
316             docModel.setProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
317         }
318     }
319
320     /**
321      * Generate a refName for the authority item from the short identifier
322      * and display name.
323      * 
324      * All refNames for authority items are generated.  If a client supplies
325      * a refName, it will be overwritten during create (per this method) 
326      * or discarded during update (per filterReadOnlyPropertiesForPart).
327      * 
328      * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
329      * 
330      */
331     protected void updateRefnameForAuthorityItem(DocumentWrapper<DocumentModel> wrapDoc,
332             String schemaName,
333             String authorityRefBaseName) throws Exception {
334         DocumentModel docModel = wrapDoc.getWrappedObject();
335         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
336         String displayName = (String) docModel.getProperty(schemaName, AuthorityItemJAXBSchema.DISPLAY_NAME);
337         if (Tools.isEmpty(authorityRefBaseName)) {
338             throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
339         }
340         RefName.Authority authority = RefName.Authority.parse(authorityRefBaseName);
341         String refName = RefName.buildAuthorityItem(authority, shortIdentifier, displayName).toString();
342         docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refName);
343     }
344
345     /**
346      * Check the logic around the parent pointer. Note that we only need do this on
347      * create, since we have logic to make this read-only on update. 
348      * 
349      * @param docModel
350      * 
351      * @throws Exception the exception
352      */
353     private void handleInAuthority(DocumentModel docModel) throws Exception {
354         if(inAuthority==null) { // Only happens on queries to wildcarded authorities
355                 throw new IllegalStateException("Trying to Create an object with no inAuthority value!");
356         }
357         docModel.setProperty(authorityItemCommonSchemaName,
358                 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
359     }
360     
361     
362     public AuthorityRefDocList getReferencingObjects(
363                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
364                 List<String> serviceTypes,
365                 String propertyName,
366             String itemcsid) throws Exception {
367         AuthorityRefDocList authRefDocList = null;
368         RepositoryInstance repoSession = null;
369         boolean releaseRepoSession = false;
370         
371         try { 
372                 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
373                 repoSession = this.getRepositorySession();
374                 if (repoSession == null) {
375                         repoSession = repoClient.getRepositorySession();
376                         releaseRepoSession = true;
377                 }
378             DocumentFilter myFilter = getDocumentFilter();
379
380                 try {
381                         DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, itemcsid);
382                         DocumentModel docModel = wrapper.getWrappedObject();
383                         String refName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
384                 authRefDocList = RefNameServiceUtils.getAuthorityRefDocs(
385                                 repoSession, ctx, repoClient,
386                         serviceTypes,
387                         refName,
388                         propertyName,
389                         myFilter.getPageSize(), myFilter.getStartPage(), true /*computeTotal*/);
390                 } catch (PropertyException pe) {
391                         throw pe;
392                 } catch (DocumentException de) {
393                         throw de;
394                 } catch (Exception e) {
395                         if (logger.isDebugEnabled()) {
396                                 logger.debug("Caught exception ", e);
397                         }
398                         throw new DocumentException(e);
399                 } finally {
400                         if (releaseRepoSession && repoSession != null) {
401                                 repoClient.releaseRepositorySession(repoSession);
402                         }
403                 }
404         } catch (Exception e) {
405                 if (logger.isDebugEnabled()) {
406                         logger.debug("Caught exception ", e);
407                 }
408                 throw new DocumentException(e);
409         }               
410         return authRefDocList;
411     }
412
413
414
415
416     /* (non-Javadoc)
417      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
418      */
419     @Override
420     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
421             throws Exception {
422         Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
423
424         // Add the CSID to the common part, since they may have fetched via the shortId.
425         if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
426             String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
427             unQObjectProperties.put("csid", csid);
428         }
429
430         return unQObjectProperties;
431     }
432
433     /**
434      * Filters out selected values supplied in an update request.
435      * 
436      * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
437      * that the link to the item's parent remains untouched.
438      * 
439      * @param objectProps the properties filtered out from the update payload
440      * @param partMeta metadata for the object to fill
441      */
442     @Override
443     public void filterReadOnlyPropertiesForPart(
444             Map<String, Object> objectProps, ObjectPartType partMeta) {
445         super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
446         String commonPartLabel = getServiceContext().getCommonPartLabel();
447         if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
448             objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
449             objectProps.remove(AuthorityItemJAXBSchema.CSID);
450             objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
451             objectProps.remove(AuthorityItemJAXBSchema.REF_NAME);
452         }
453     }
454
455     @Override
456     public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
457         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
458         super.extractAllParts(wrapDoc);
459
460         String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
461         if (Tools.isTrue(showSiblings)) {
462             showSiblings(wrapDoc, ctx);
463             return;   // actual result is returned on ctx.addOutputPart();
464         }
465
466         String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
467         if (Tools.isTrue(showRelations)) {
468             showRelations(wrapDoc, ctx);
469             return;   // actual result is returned on ctx.addOutputPart();
470         }
471
472         String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
473         if (Tools.isTrue(showAllRelations)) {
474             showAllRelations(wrapDoc, ctx);
475             return;   // actual result is returned on ctx.addOutputPart();
476         }
477     }
478
479     /** @return null on parent not found
480      */
481     protected String getParentCSID(String thisCSID) throws Exception {
482         String parentCSID = null;
483         try {
484             String predicate = RelationshipType.HAS_BROADER.value();
485             RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
486             List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
487             if (parentList != null) {
488                 if (parentList.size() == 0) {
489                     return null;
490                 }
491                 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
492                 parentCSID = relationListItem.getObjectCsid();
493             }
494             return parentCSID;
495         } catch (Exception e) {
496             logger.error("Could not find parent for this: " + thisCSID, e);
497             return null;
498         }
499     }
500
501     public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
502             MultipartServiceContext ctx) throws Exception {
503         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
504
505         String predicate = RelationshipType.HAS_BROADER.value();
506         RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
507         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
508
509         RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
510         List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
511
512         if(logger.isTraceEnabled()) {
513             String dump = dumpLists(thisCSID, parentList, childrenList, null);
514             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
515         }
516         
517         //Assume that there are more children than parents.  Will be true for parent/child, but maybe not for other relations.
518         //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
519         //Not optimal, but that's the current design spec.
520         long added = 0;
521         for (RelationsCommonList.RelationListItem parent : parentList) {
522             childrenList.add(parent);
523             added++;
524         }
525         long childrenSize = childrenList.size();
526         childrenListOuter.setTotalItems(childrenSize);
527         childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
528
529         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
530         ctx.addOutputPart(relationsPart);
531     }
532
533     public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
534             MultipartServiceContext ctx) throws Exception {
535         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
536         String parentCSID = getParentCSID(thisCSID);
537         if (parentCSID == null) {
538             logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
539             return;
540         }
541
542         String predicate = RelationshipType.HAS_BROADER.value();
543         RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
544         List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
545
546         List<RelationsCommonList.RelationListItem> toRemoveList = newList();
547
548
549         RelationsCommonList.RelationListItem item = null;
550         for (RelationsCommonList.RelationListItem sibling : siblingList) {
551             if (thisCSID.equals(sibling.getSubjectCsid())) {
552                 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.
553             }
554         }
555         //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.
556         for (RelationsCommonList.RelationListItem self : toRemoveList) {
557             removeFromList(siblingList, self);
558         }
559
560         long siblingSize = siblingList.size();
561         siblingListOuter.setTotalItems(siblingSize);
562         siblingListOuter.setItemsInPage(siblingSize);
563         if(logger.isTraceEnabled()) {
564             String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
565             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
566         }
567
568         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
569         ctx.addOutputPart(relationsPart);
570     }
571
572     public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
573         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
574
575         RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null);   //  nulls are wildcards:  predicate=*, and object=*
576         List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
577
578         RelationsCommonList objectListOuter = getRelations(null, thisCSID, null);   //  nulls are wildcards:  subject=*, and predicate=*
579         List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
580
581         if(logger.isTraceEnabled()) {
582             String dump = dumpLists(thisCSID, subjectList, objectList, null);
583             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
584         }
585         //  MERGE LISTS:
586         subjectList.addAll(objectList);
587
588         //now subjectList actually has records BOTH where thisCSID is subject and object.
589         long relatedSize = subjectList.size();
590         subjectListOuter.setTotalItems(relatedSize);
591         subjectListOuter.setItemsInPage(relatedSize);
592
593         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
594         ctx.addOutputPart(relationsPart);
595     }
596
597     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
598         super.fillAllParts(wrapDoc, action);
599         /*
600         ServiceContext ctx = getServiceContext();
601         PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
602         DocumentModel documentModel = (wrapDoc.getWrappedObject());
603         String itemCsid = documentModel.getName();
604         
605         //UPDATE and CREATE will call.   Updates relations part
606         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
607         
608         PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
609         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
610          */
611     }
612
613     public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
614         super.completeCreate(wrapDoc);
615         handleRelationsPayload(wrapDoc, false);
616     }
617
618     public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
619         super.completeUpdate(wrapDoc);
620         handleRelationsPayload(wrapDoc, true);
621         handleItemRefNameReferenceUpdate();
622     }
623
624     // Note that we must do this after we have completed the Update, so that the repository has the
625     // info for the item itself. The relations code must call into the repo to get info for each end.
626     // This could be optimized to pass in the parent docModel, since it will often be one end.
627     // Nevertheless, we should complete the item save before we do work on the relations, especially
628     // since a save on Create might fail, and we would not want to create relations for something
629     // that may not be created...
630     private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
631         ServiceContext ctx = getServiceContext();
632         PoxPayloadIn input = (PoxPayloadIn) ctx.getInput();
633         DocumentModel documentModel = (wrapDoc.getWrappedObject());
634         String itemCsid = documentModel.getName();
635
636         //Updates relations part
637         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
638
639         PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
640         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
641
642         //now we add part for relations list
643         //ServiceContext ctx = getServiceContext();
644         //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
645         ((PoxPayloadOut) ctx.getOutput()).addPart(payloadOutputPart);
646     }
647
648     /**  updateRelations strategy:
649     
650     go through inboundList, remove anything from childList that matches  from childList
651     go through inboundList, remove anything from parentList that matches  from parentList
652     go through parentList, delete all remaining
653     go through childList, delete all remaining
654     go through actionList, add all remaining.
655     check for duplicate children
656     check for more than one parent.
657     
658     inboundList                           parentList                      childList          actionList
659     ----------------                          ---------------                  ----------------       ----------------
660     child-a                                   parent-c                        child-a             child-b
661     child-b                                   parent-d                        child-c
662     parent-a
663      */
664     private RelationsCommonList updateRelations(
665             String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
666             throws Exception {
667         if (logger.isTraceEnabled()) {
668             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
669         }
670         PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME);        //input.getPart("relations_common");
671         if (part == null) {
672             return null;  //nothing to do--they didn't send a list of relations.
673         }
674         RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
675         List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
676         List<RelationsCommonList.RelationListItem> actionList = newList();
677         List<RelationsCommonList.RelationListItem> childList = null;
678         List<RelationsCommonList.RelationListItem> parentList = null;
679         DocumentModel docModel = wrapDoc.getWrappedObject();
680                 String itemRefName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
681
682         ServiceContext ctx = getServiceContext();
683         //Do magic replacement of ${itemCSID} and fix URI's.
684         fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
685
686         String HAS_BROADER = RelationshipType.HAS_BROADER.value();
687         UriInfo uriInfo = ctx.getUriInfo();
688         MultivaluedMap queryParams = uriInfo.getQueryParameters();
689
690         if (fUpdate) {
691             //Run getList() once as sent to get childListOuter:
692             String predicate = RelationshipType.HAS_BROADER.value();
693             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
694             queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
695             queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
696             queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
697             queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
698             RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo());    //magically knows all query params because they are in the context.
699
700             //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
701             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
702             queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
703             queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
704             RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
705
706
707             childList = childListOuter.getRelationListItem();
708             parentList = parentListOuter.getRelationListItem();
709
710             if (parentList.size() > 1) {
711                 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
712             }
713
714             if (logger.isTraceEnabled()) {
715                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
716             }
717         }
718
719
720         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
721             // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
722             // and so the CSID for those may be null
723             if(inboundItem.getPredicate().equals(HAS_BROADER)) {
724                 // Look for parents and children
725                 if(itemCSID.equals(inboundItem.getObject().getCsid())
726                                 || itemRefName.equals(inboundItem.getObject().getRefName())) {
727                         //then this is an item that says we have a child.  That child is inboundItem
728                         RelationsCommonList.RelationListItem childItem =
729                                         (childList == null) ? null : findInList(childList, inboundItem);
730                         if (childItem != null) {
731                         if (logger.isTraceEnabled()) {
732                                 StringBuilder sb = new StringBuilder();
733                                 itemToString(sb, "== Child: ", childItem);
734                             logger.trace("Found inboundChild in current child list: " + sb.toString());
735                         }
736                                 removeFromList(childList, childItem);    //exists, just take it off delete list
737                         } else {
738                         if (logger.isTraceEnabled()) {
739                                 StringBuilder sb = new StringBuilder();
740                                 itemToString(sb, "== Child: ", inboundItem);
741                             logger.trace("inboundChild not in current child list, will add: " + sb.toString());
742                         }
743                                 actionList.add(inboundItem);   //doesn't exist as a child, but is a child.  Add to additions list
744                                 String newChildCsid = inboundItem.getSubject().getCsid();
745                                 if(newChildCsid == null) {
746                                         String newChildRefName = inboundItem.getSubject().getRefName();
747                                         if(newChildRefName==null) {
748                                                 throw new RuntimeException("Child with no CSID or refName!");
749                                         }
750                             if (logger.isTraceEnabled()) {
751                                 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
752                             }
753                                 DocumentModel newChildDocModel = 
754                                         ResourceBase.getDocModelForRefName(this.getRepositorySession(), 
755                                                         newChildRefName, getServiceContext().getResourceMap());
756                                 newChildCsid = getCsid(newChildDocModel);
757                                 }
758                                 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
759                         }
760
761                 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
762                                         || itemRefName.equals(inboundItem.getSubject().getRefName())) {
763                         //then this is an item that says we have a parent.  inboundItem is that parent.
764                         RelationsCommonList.RelationListItem parentItem =
765                                         (parentList == null) ? null : findInList(parentList, inboundItem);
766                         if (parentItem != null) {
767                                 removeFromList(parentList, parentItem);    //exists, just take it off delete list
768                         } else {
769                                 actionList.add(inboundItem);   //doesn't exist as a parent, but is a parent. Add to additions list
770                         }
771                 } else {
772                     logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
773                 }
774             } else {
775                 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
776             }
777         }
778         if (logger.isTraceEnabled()) {
779             String dump = dumpLists(itemCSID, parentList, childList, actionList);
780             logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
781         }
782         if (fUpdate) {
783             if (logger.isTraceEnabled()) {
784                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
785                         + parentList.size() + " existing parents and " + childList.size() + " existing children.");
786             }
787             deleteRelations(parentList, ctx, "parentList");               //todo: there are items appearing on both lists....april 20.
788             deleteRelations(childList, ctx, "childList");
789         }
790         if (logger.isTraceEnabled()) {
791             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
792                     + actionList.size() + " new parents and children.");
793         }
794         createRelations(actionList, ctx);
795         if (logger.isTraceEnabled()) {
796             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
797         }
798         //We return all elements on the inbound list, since we have just worked to make them exist in the system
799         // and be non-redundant, etc.  That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
800         return relationsCommonListBody;
801     }
802
803     private void ensureChildHasNoOtherParents(ServiceContext ctx, MultivaluedMap queryParams, String childCSID) {
804         logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
805         queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
806         queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
807         queryParams.putSingle(IRelationsManager.OBJECT_QP, null);  //null means ANY
808         RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
809         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
810         //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
811         deleteRelations(parentList, ctx, "parentList-delete");
812     }
813
814     
815     private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
816         sb.append(prefix);
817                 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
818         sb.append(": ["); 
819         sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
820         sb.append("]--");
821         sb.append(item.getPredicate());
822         sb.append("-->["); 
823         sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
824         sb.append("]");
825     }
826     
827     private String dumpLists(String itemCSID,
828             List<RelationsCommonList.RelationListItem> parentList,
829             List<RelationsCommonList.RelationListItem> childList,
830             List<RelationsCommonList.RelationListItem> actionList) {
831         StringBuilder sb = new StringBuilder();
832         sb.append("itemCSID: " + itemCSID + CR);
833         if(parentList!=null) {
834                 sb.append(dumpList(parentList, "parentList"));
835         }
836         if(childList!=null) {
837                 sb.append(dumpList(childList, "childList"));
838         }
839         if(actionList!=null) {
840                 sb.append(dumpList(actionList, "actionList"));
841         }
842         return sb.toString();
843     }
844     private final static String CR = "\r\n";
845     private final static String T = " ";
846
847     private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
848         StringBuilder sb = new StringBuilder();
849         String s;
850         if (list.size() > 0) {
851             sb.append("=========== " + label + " ==========" + CR);
852         }
853         for (RelationsCommonList.RelationListItem item : list) {
854                 itemToString(sb, "==  ", item);
855                 sb.append(CR);
856         }
857         return sb.toString();
858     }
859
860     /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
861      *   and sets URI correctly for related items.
862      *   Operates directly on the items in the list.  Does not change the list ordering, does not add or remove any items.
863      */
864     protected void fixupInboundListItems(ServiceContext ctx,
865             List<RelationsCommonList.RelationListItem> inboundList,
866             DocumentModel docModel,
867             String itemCSID) throws Exception {
868         String thisURI = this.getUri(docModel);
869         // WARNING:  the two code blocks below are almost identical  and seem to ask to be put in a generic method.
870         //                    beware of the little diffs in  inboundItem.setObjectCsid(itemCSID); and   inboundItem.setSubjectCsid(itemCSID); in the two blocks.
871         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
872             RelationsDocListItem inboundItemObject = inboundItem.getObject();
873             RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
874
875             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
876                 inboundItem.setObjectCsid(itemCSID);
877                 inboundItemObject.setCsid(itemCSID);
878                 //inboundItemObject.setUri(getUri(docModel));
879             } else {
880                 /*
881                 String objectCsid = inboundItemObject.getCsid();
882                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid);    //null if not found.
883                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
884                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
885                 inboundItemObject.setUri(uri);    //CSPACE-4037
886                  */
887             }
888             //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri());    //CSPACE-4042
889
890             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
891                 inboundItem.setSubjectCsid(itemCSID);
892                 inboundItemSubject.setCsid(itemCSID);
893                 //inboundItemSubject.setUri(getUri(docModel));
894             } else {
895                 /*
896                 String subjectCsid = inboundItemSubject.getCsid();
897                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid);    //null if not found.
898                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
899                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
900                 inboundItemSubject.setUri(uri);    //CSPACE-4037
901                  */
902             }
903             //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri());  //CSPACE-4042
904
905         }
906     }
907
908     // this method calls the RelationResource to have it create the relations and persist them.
909     private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx) throws Exception {
910         for (RelationsCommonList.RelationListItem item : inboundList) {
911             RelationsCommon rc = new RelationsCommon();
912             //rc.setCsid(item.getCsid());
913             //todo: assignTo(item, rc);
914             RelationsDocListItem itemSubject = item.getSubject();
915             RelationsDocListItem itemObject = item.getObject();
916
917             // Set at least one of CSID and refName for Subject and Object
918             // Either value might be null for for each of Subject and Object 
919             String subjectCsid = itemSubject.getCsid();
920             rc.setSubjectCsid(subjectCsid);
921
922             String objCsid = itemObject.getCsid();
923             rc.setObjectCsid(objCsid);
924
925             rc.setSubjectRefName(itemSubject.getRefName());
926             rc.setObjectRefName(itemObject.getRefName());
927
928             rc.setRelationshipType(item.getPredicate());
929             //RelationshipType  foo = (RelationshipType.valueOf(item.getPredicate())) ;
930             //rc.setPredicate(foo);     //this must be one of the type found in the enum in  services/jaxb/src/main/resources/relations_common.xsd
931
932             // This is superfluous, since it will be fetched by the Relations Create logic.
933             rc.setSubjectDocumentType(itemSubject.getDocumentType());
934             rc.setObjectDocumentType(itemObject.getDocumentType());
935
936             // This is superfluous, since it will be fetched by the Relations Create logic.
937             rc.setSubjectUri(itemSubject.getUri());
938             rc.setObjectUri(itemObject.getUri());
939             // May not have the info here. Only really require CSID or refName. 
940             // Rest is handled in the Relation create mechanism
941             //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
942
943             PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
944             PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
945             payloadOut.addPart(outputPart);
946             RelationResource relationResource = new RelationResource();
947             Object res = relationResource.create(ctx.getResourceMap(),
948                     ctx.getUriInfo(), payloadOut.toXML());    //NOTE ui recycled from above to pass in unknown query params.
949         }
950     }
951
952     private void deleteRelations(List<RelationsCommonList.RelationListItem> list, ServiceContext ctx, String listName) {
953         try {
954             for (RelationsCommonList.RelationListItem item : list) {
955                 RelationResource relationResource = new RelationResource();
956                 if(logger.isTraceEnabled()) {
957                         StringBuilder sb = new StringBuilder();
958                         itemToString(sb, "==== TO DELETE: ", item);
959                         logger.trace(sb.toString());
960                 }
961                 Object res = relationResource.delete(item.getCsid());
962             }
963         } catch (Throwable t) {
964             String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
965             logger.error(msg);
966         }
967     }
968
969     private List<RelationsCommonList.RelationListItem> newList() {
970         List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
971         return result;
972     }
973
974     protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
975         List<RelationsCommonList.RelationListItem> result = newList();
976         for (RelationsCommonList.RelationListItem item : inboundList) {
977             result.add(item);
978         }
979         return result;
980     }
981
982     // Note that the item argument may be sparse (only refName, no CSID for subject or object)
983     // But the list items must not be sparse
984     private RelationsCommonList.RelationListItem findInList(
985                 List<RelationsCommonList.RelationListItem> list, 
986                 RelationsCommonList.RelationListItem item) {
987         RelationsCommonList.RelationListItem foundItem = null;
988         for (RelationsCommonList.RelationListItem listItem : list) {
989             if (itemsEqual(listItem, item)) {   //equals must be defined, else
990                 foundItem = listItem;
991                 break;
992             }
993         }
994         return foundItem;
995     }
996
997     // Note that item2 may be sparse (only refName, no CSID for subject or object)
998     // But item1 must not be sparse 
999     private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1000         if (item1 == null || item2 == null) {
1001             return false;
1002         }
1003         RelationsDocListItem subj1 = item1.getSubject();
1004         RelationsDocListItem subj2 = item2.getSubject();
1005         RelationsDocListItem obj1 = item1.getObject();
1006         RelationsDocListItem obj2 = item2.getObject();
1007         String subj1Csid = subj1.getCsid();
1008         String subj2Csid = subj2.getCsid();
1009         String subj1RefName = subj1.getRefName();
1010         String subj2RefName = subj2.getRefName();
1011
1012         String obj1Csid = obj1.getCsid();
1013         String obj2Csid = obj2.getCsid();
1014         String obj1RefName = obj1.getRefName();
1015         String obj2RefName = obj2.getRefName();
1016
1017         boolean isEqual = 
1018                            (subj1Csid.equals(subj2Csid) || ((subj2Csid==null)  && subj1RefName.equals(subj2RefName)))
1019                 && (obj1Csid.equals(obj1Csid)   || ((obj2Csid==null)   && obj1RefName.equals(obj2RefName)))
1020                 // predicate is proper, but still allow relationshipType
1021                 && (item1.getPredicate().equals(item2.getPredicate())
1022                         ||  ((item2.getPredicate()==null)  && item1.getRelationshipType().equals(item2.getRelationshipType())))
1023                 // Allow missing docTypes, so long as they do not conflict
1024                 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1025                 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
1026         return isEqual;
1027     }
1028
1029     private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
1030         list.remove(item);
1031     }
1032
1033     /* don't even THINK of re-using this method.
1034      * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
1035      */
1036     private String extractInAuthorityCSID(String uri) {
1037         String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
1038         Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
1039         Matcher m = p.matcher(uri);
1040         if (m.find()) {
1041             if (m.groupCount() < 3) {
1042                 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
1043                 return "";
1044             } else {
1045                 //String service = m.group(1);
1046                 String inauth = m.group(2);
1047                 //String theRest = m.group(3);
1048                 return inauth;
1049                 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
1050             }
1051         } else {
1052             logger.warn("REGEX-NOT-MATCHED looking in " + uri);
1053             return "";
1054         }
1055     }
1056
1057     //ensures CSPACE-4042
1058     protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
1059         String authorityCSID = extractInAuthorityCSID(thisURI);
1060         String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
1061         if (Tools.isBlank(authorityCSID)
1062                 || Tools.isBlank(authorityCSIDForInbound)
1063                 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
1064             throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
1065         }
1066     }
1067
1068     //================= TODO: move this to common, refactoring this and  CollectionObjectResource.java
1069     public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1070         ServiceContext ctx = getServiceContext();
1071         MultivaluedMap queryParams = ctx.getQueryParams();
1072         queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1073         queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1074         queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1075
1076         RelationResource relationResource = new RelationResource();
1077         RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
1078         return relationsCommonList;
1079     }
1080     //============================= END TODO refactor ==========================
1081 }