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