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