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