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