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