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