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