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