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