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