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