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