]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
4c0894de049bbc9d40bf99a763fa352910535f67
[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.PoxPayloadIn;
29 import org.collectionspace.services.client.PoxPayloadOut;
30
31 import org.collectionspace.services.common.UriTemplateRegistry;
32 import org.collectionspace.services.common.api.RefName;
33 import org.collectionspace.services.common.api.Tools;
34 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
35 import org.collectionspace.services.common.context.MultipartServiceContext;
36 import org.collectionspace.services.common.context.ServiceContext;
37 import org.collectionspace.services.common.document.DocumentException;
38 import org.collectionspace.services.common.document.DocumentFilter;
39 import org.collectionspace.services.common.document.DocumentWrapper;
40 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
41 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
42 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
43
44 import org.collectionspace.services.config.service.ListResultField;
45 import org.collectionspace.services.config.service.ObjectPartType;
46
47 import org.collectionspace.services.nuxeo.client.java.DocHandlerBase;
48 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
49 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
50
51 import org.collectionspace.services.relation.RelationsCommonList;
52 import org.collectionspace.services.relation.RelationsDocListItem;
53
54 import org.collectionspace.services.vocabulary.VocabularyItemJAXBSchema;
55
56 import org.nuxeo.ecm.core.api.ClientException;
57 import org.nuxeo.ecm.core.api.DocumentModel;
58 import org.nuxeo.ecm.core.api.model.PropertyException;
59 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
60
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 import javax.ws.rs.core.MultivaluedMap;
65
66 import java.util.ArrayList;
67 import java.util.HashMap;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.regex.Matcher;
71 import java.util.regex.Pattern;
72
73 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
74 /**
75  * AuthorityItemDocumentModelHandler
76  *
77  * $LastChangedRevision: $
78  * $LastChangedDate: $
79  */
80 public abstract class AuthorityItemDocumentModelHandler<AICommon>
81         extends DocHandlerBase<AICommon> {
82
83     private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
84     private String authorityItemCommonSchemaName;
85     private String authorityItemTermGroupXPathBase;
86     /**
87      * inVocabulary is the parent Authority for this context
88      */
89     protected String inAuthority = null;
90     protected String authorityRefNameBase = null;
91     // Used to determine when the displayName changes as part of the update.
92     protected String oldDisplayNameOnUpdate = null;
93
94     public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
95         this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
96     }
97
98     @Override
99     protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
100         String result = null;
101         
102         DocumentModel docModel = docWrapper.getWrappedObject();
103         ServiceContext ctx = this.getServiceContext();
104         RefName.AuthorityItem refname = (RefName.AuthorityItem)getRefName(ctx, docModel);
105         result = refname.getDisplayName();
106         
107         return result;
108     }
109     
110     /*
111      * After calling this method successfully, the document model will contain an updated refname and short ID
112      * (non-Javadoc)
113      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getRefName(org.collectionspace.services.common.context.ServiceContext, org.nuxeo.ecm.core.api.DocumentModel)
114      */
115     @Override
116     public RefName.RefNameInterface getRefName(ServiceContext ctx,
117                 DocumentModel docModel) {
118         RefName.RefNameInterface refname = null;
119         
120         try {
121                 String displayName = getPrimaryDisplayName(docModel, authorityItemCommonSchemaName,
122                     getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
123                 if (Tools.isEmpty(displayName)) {
124                     throw new Exception("The displayName for this authority term was empty or not set.");
125                 }
126         
127                 String shortIdentifier = (String) docModel.getProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
128                 if (Tools.isEmpty(shortIdentifier)) {
129                         // We didn't find a short ID in the payload request, so we need to synthesize one.
130                         shortIdentifier = handleDisplayNameAsShortIdentifier(docModel); // updates the document model with the new short ID as a side-effect
131                 }
132                 
133                 String authorityRefBaseName = getAuthorityRefNameBase();
134                 if (Tools.isEmpty(authorityRefBaseName)) {
135                     throw new Exception("Could not create the refName for this authority term, because the refName for its authority parent was empty.");
136                 }
137                 
138                 // Create the items refname using the parent's as a base
139                 RefName.Authority parentsRefName = RefName.Authority.parse(authorityRefBaseName);
140                 refname = RefName.buildAuthorityItem(parentsRefName, shortIdentifier, displayName);
141                 // Now update the document model with the refname value
142                 String refNameStr = refname.toString();
143                 docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, refNameStr); // REM - This field is deprecated now that the refName is part of the collection_space core schema
144
145         } catch (Exception e) {
146                 logger.error(e.getMessage(), e);
147         }
148
149         return refname;
150     }
151     
152     public void setInAuthority(String inAuthority) {
153         this.inAuthority = inAuthority;
154     }
155
156     /** Subclasses may override this to customize the URI segment. */
157     public String getAuthorityServicePath() {
158         return getServiceContext().getServiceName().toLowerCase();    // Laramie20110510 CSPACE-3932
159     }
160
161     @Override
162     public String getUri(DocumentModel docModel) {
163         // Laramie20110510 CSPACE-3932
164         String authorityServicePath = getAuthorityServicePath();
165         if(inAuthority==null) { // Only happens on queries to wildcarded authorities
166                 try {
167                         inAuthority = (String) docModel.getProperty(authorityItemCommonSchemaName,
168                         AuthorityItemJAXBSchema.IN_AUTHORITY);
169                 } catch (ClientException pe) {
170                         throw new RuntimeException("Could not get parent specifier for item!");
171                 }
172         }
173         return "/" + authorityServicePath + '/' + inAuthority + '/' + AuthorityClient.ITEMS + '/' + getCsid(docModel);
174     }
175
176     protected String getAuthorityRefNameBase() {
177         return this.authorityRefNameBase;
178     }
179
180     public void setAuthorityRefNameBase(String value) {
181         this.authorityRefNameBase = value;
182     }
183
184     /*
185      * Note: the Vocabulary service's VocabularyItemDocumentModelHandler class overrides this method.
186      */
187     protected ListResultField getListResultsDisplayNameField() {
188         ListResultField result = new ListResultField();
189         // Per CSPACE-5132, the name of this element remains 'displayName'
190         // for backwards compatibility, although its value is obtained
191         // from the termDisplayName field.
192         //
193         // Update: this name is now being changed to 'termDisplayName', both
194         // because this is the actual field name and because the app layer
195         // work to convert over to this field is underway. Per Patrick, the
196         // app layer treats lists, in at least some context(s), as sparse record
197         // payloads, and thus fields in list results must all be present in
198         // (i.e. represent a strict subset of the fields in) record schemas.
199         // - ADR 2012-05-11
200         // 
201         //
202         // In CSPACE-5134, these list results will change substantially
203         // to return display names for both the preferred term and for
204         // each non-preferred term (if any).
205         result.setElement(AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
206         result.setXpath(NuxeoUtils.getPrimaryXPathPropertyName(
207                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME));
208         
209         return result;
210     }
211     
212     /*
213      * Note: the Vocabulary service's VocabularyItemDocumentModelHandler class overrides this method.
214      */    
215     protected ListResultField getListResultsTermStatusField() {
216         ListResultField result = new ListResultField();
217         
218         result.setElement(AuthorityItemJAXBSchema.TERM_STATUS);
219         result.setXpath(NuxeoUtils.getPrimaryXPathPropertyName(
220                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_STATUS));
221
222         return result;
223     }    
224     
225     private boolean isTermDisplayName(String elName) {
226         return AuthorityItemJAXBSchema.TERM_DISPLAY_NAME.equals(elName) || VocabularyItemJAXBSchema.DISPLAY_NAME.equals(elName);
227     }
228     
229     /*
230      * (non-Javadoc)
231      * @see org.collectionspace.services.nuxeo.client.java.DocHandlerBase#getListItemsArray()
232      * 
233      * Note: We're updating the "global" service and tenant bindings instance here -the list instance here is
234      * a reference to the tenant bindings instance in the singleton ServiceMain.
235      */
236     @Override
237     public List<ListResultField> getListItemsArray() throws DocumentException {
238         List<ListResultField> list = super.getListItemsArray();
239         
240         // One-time initialization for each authority item service.
241         if (isListItemArrayExtended() == false) {
242                 synchronized(AuthorityItemDocumentModelHandler.class) {
243                         if (isListItemArrayExtended() == false) {                               
244                         int nFields = list.size();
245                         // Ensure that each item in a list of Authority items includes
246                         // a set of common fields, so we do not depend upon configuration
247                         // for general logic.
248                         boolean hasDisplayName = false;
249                         boolean hasShortId = false;
250                         boolean hasTermStatus = false;
251                         for (int i = 0; i < nFields; i++) {
252                             ListResultField field = list.get(i);
253                             String elName = field.getElement();
254                             if (isTermDisplayName(elName) == true) {
255                                 hasDisplayName = true;
256                             } else if (AuthorityItemJAXBSchema.SHORT_IDENTIFIER.equals(elName)) {
257                                 hasShortId = true;
258                             } else if (AuthorityItemJAXBSchema.TERM_STATUS.equals(elName)) {
259                                 hasTermStatus = true;
260                             }
261                         }
262                                 
263                         ListResultField field;
264                         if (!hasDisplayName) {
265                                 field = getListResultsDisplayNameField();
266                             list.add(field);
267                         }
268                         if (!hasShortId) {
269                             field = new ListResultField();
270                             field.setElement(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
271                             field.setXpath(AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
272                             list.add(field);
273                         }
274                         if (!hasTermStatus) {
275                             field = getListResultsTermStatusField();
276                             list.add(field);
277                         }
278                         }
279                         
280                         setListItemArrayExtended(true);
281                 } // end of synchronized block
282         }
283
284         return list;
285     }
286     
287     /* (non-Javadoc)
288      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
289      */
290     @Override
291     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
292         // first fill all the parts of the document, refname and short ID get set as well
293         super.handleCreate(wrapDoc);
294         // Ensure we have required fields set properly
295         handleInAuthority(wrapDoc.getWrappedObject());        
296     }
297
298     /*
299      * Note that the Vocabulary service's document-model for items overrides this method.
300      */
301         protected String getPrimaryDisplayName(DocumentModel docModel, String schema,
302                         String complexPropertyName, String fieldName) {
303                 String result = null;
304
305                 result = getStringValueInPrimaryRepeatingComplexProperty(docModel, schema, complexPropertyName, fieldName);
306                 
307                 return result;
308         }
309     
310     /* (non-Javadoc)
311      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleUpdate(org.collectionspace.services.common.document.DocumentWrapper)
312      */
313     @Override
314     public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
315         // First, get a copy of the old displayName
316         // oldDisplayNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
317         //        AuthorityItemJAXBSchema.DISPLAY_NAME);
318         oldDisplayNameOnUpdate = getPrimaryDisplayName(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName,
319                 getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
320         oldRefNameOnUpdate = (String) wrapDoc.getWrappedObject().getProperty(authorityItemCommonSchemaName,
321                 AuthorityItemJAXBSchema.REF_NAME);
322         super.handleUpdate(wrapDoc);
323
324         // Now, check the new display and handle the refname update.
325         String newDisplayName = (String) getPrimaryDisplayName(wrapDoc.getWrappedObject(), authorityItemCommonSchemaName,
326                 authorityItemTermGroupXPathBase,
327                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
328         if (newDisplayName != null && !newDisplayName.equals(oldDisplayNameOnUpdate)) {
329             // Need to update the refName, and then fix all references.
330             newRefNameOnUpdate = handleItemRefNameUpdateForDisplayName(wrapDoc.getWrappedObject(), newDisplayName);
331         } else {
332             // Mark as not needing attention in completeUpdate phase.
333             newRefNameOnUpdate = null;
334             oldRefNameOnUpdate = null;
335         }
336     }
337
338     /**
339      * Handle refName updates for changes to display name.
340      * Assumes refName is already correct. Just ensures it is right.
341      *
342      * @param docModel the doc model
343      * @param newDisplayName the new display name
344      * @throws Exception the exception
345      */
346     protected String handleItemRefNameUpdateForDisplayName(DocumentModel docModel,
347             String newDisplayName) throws Exception {
348         RefName.AuthorityItem authItem = RefName.AuthorityItem.parse(oldRefNameOnUpdate);
349         if (authItem == null) {
350             String err = "Authority Item has illegal refName: " + oldRefNameOnUpdate;
351             logger.debug(err);
352             throw new IllegalArgumentException(err);
353         }
354         authItem.displayName = newDisplayName;
355         String updatedRefName = authItem.toString();
356         docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.REF_NAME, updatedRefName); // Maybe set collectionspace_core schema here?
357         return updatedRefName;
358     }
359     
360     /**
361      * If no short identifier was provided in the input payload, generate a
362      * short identifier from the preferred term display name or term name.
363      */
364         private String handleDisplayNameAsShortIdentifier(DocumentModel docModel) throws Exception {
365                 String result = (String) docModel.getProperty(authorityItemCommonSchemaName,
366                                 AuthorityItemJAXBSchema.SHORT_IDENTIFIER);
367
368                 if (Tools.isEmpty(result)) {
369                         String termDisplayName = getPrimaryDisplayName(
370                                         docModel, authorityItemCommonSchemaName,
371                                         getItemTermInfoGroupXPathBase(),
372                                         AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
373
374                         String termName = getPrimaryDisplayName(
375                                         docModel, authorityItemCommonSchemaName,
376                                         getItemTermInfoGroupXPathBase(),
377                                         AuthorityItemJAXBSchema.TERM_NAME);
378
379                         String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(termDisplayName,
380                                                         termName);
381                         docModel.setProperty(authorityItemCommonSchemaName, AuthorityItemJAXBSchema.SHORT_IDENTIFIER,
382                                         generatedShortIdentifier);
383                         result = generatedShortIdentifier;
384                 }
385                 
386                 return result;
387         }
388
389     /**
390      * Generate a refName for the authority item from the short identifier
391      * and display name.
392      * 
393      * All refNames for authority items are generated.  If a client supplies
394      * a refName, it will be overwritten during create (per this method) 
395      * or discarded during update (per filterReadOnlyPropertiesForPart).
396      * 
397      * @see #filterReadOnlyPropertiesForPart(Map<String, Object>, org.collectionspace.services.common.service.ObjectPartType)
398      * 
399      */
400     protected String updateRefnameForAuthorityItem(DocumentModel docModel,
401             String schemaName) throws Exception {
402         String result = null;
403         
404         RefName.RefNameInterface refname = getRefName(getServiceContext(), docModel);
405         String refNameStr = refname.toString();
406         docModel.setProperty(schemaName, AuthorityItemJAXBSchema.REF_NAME, refNameStr);
407         result = refNameStr;
408         
409         return result;
410     }
411
412     /**
413      * Check the logic around the parent pointer. Note that we only need do this on
414      * create, since we have logic to make this read-only on update. 
415      * 
416      * @param docModel
417      * 
418      * @throws Exception the exception
419      */
420     private void handleInAuthority(DocumentModel docModel) throws Exception {
421         if(inAuthority==null) { // Only happens on queries to wildcarded authorities
422                 throw new IllegalStateException("Trying to Create an object with no inAuthority value!");
423         }
424         docModel.setProperty(authorityItemCommonSchemaName,
425                 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
426     }
427     
428     public AuthorityRefDocList getReferencingObjects(
429                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
430                 UriTemplateRegistry uriTemplateRegistry, 
431                 List<String> serviceTypes,
432                 String propertyName,
433             String itemcsid) throws Exception {
434         AuthorityRefDocList authRefDocList = null;
435         RepositoryInstance repoSession = null;
436         boolean releaseRepoSession = false;
437         
438         try {
439                 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
440                 repoSession = this.getRepositorySession();
441                 if (repoSession == null) {
442                         repoSession = repoClient.getRepositorySession(ctx);
443                         releaseRepoSession = true;
444                 }
445             DocumentFilter myFilter = getDocumentFilter();
446
447                 try {
448                         DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, itemcsid);
449                         DocumentModel docModel = wrapper.getWrappedObject();
450                         String refName = (String) docModel.getPropertyValue(AuthorityItemJAXBSchema.REF_NAME);
451                 authRefDocList = RefNameServiceUtils.getAuthorityRefDocs(
452                                 repoSession, ctx, uriTemplateRegistry, repoClient,
453                         serviceTypes,
454                         refName,
455                         propertyName,
456                         myFilter, true /*computeTotal*/);
457                 } catch (PropertyException pe) {
458                         throw pe;
459                 } catch (DocumentException de) {
460                         throw de;
461                 } catch (Exception e) {
462                         if (logger.isDebugEnabled()) {
463                                 logger.debug("Caught exception ", e);
464                         }
465                         throw new DocumentException(e);
466                 } finally {
467                         // If we got/aquired a new seesion then we're responsible for releasing it.
468                         if (releaseRepoSession && repoSession != null) {
469                                 repoClient.releaseRepositorySession(ctx, repoSession);
470                         }
471                 }
472         } catch (Exception e) {
473                 if (logger.isDebugEnabled()) {
474                         logger.debug("Caught exception ", e);
475                 }
476                 throw new DocumentException(e);
477         }
478         
479         return authRefDocList;
480     }
481
482     /* (non-Javadoc)
483      * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
484      */
485     @Override
486     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
487             throws Exception {
488         Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
489
490         // Add the CSID to the common part, since they may have fetched via the shortId.
491         if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
492             String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
493             unQObjectProperties.put("csid", csid);
494         }
495
496         return unQObjectProperties;
497     }
498
499     /**
500      * Filters out selected values supplied in an update request.
501      * 
502      * For example, filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure
503      * that the link to the item's parent remains untouched.
504      * 
505      * @param objectProps the properties filtered out from the update payload
506      * @param partMeta metadata for the object to fill
507      */
508     @Override
509     public void filterReadOnlyPropertiesForPart(
510             Map<String, Object> objectProps, ObjectPartType partMeta) {
511         super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
512         String commonPartLabel = getServiceContext().getCommonPartLabel();
513         if (partMeta.getLabel().equalsIgnoreCase(commonPartLabel)) {
514             objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
515             objectProps.remove(AuthorityItemJAXBSchema.CSID);
516             objectProps.remove(AuthorityJAXBSchema.SHORT_IDENTIFIER);
517             objectProps.remove(AuthorityItemJAXBSchema.REF_NAME);
518         }
519     }
520     
521     protected List<String> getPartialTermDisplayNameMatches(List<String> termDisplayNameList, String partialTerm) {
522         List<String> result = new ArrayList<String>();
523         
524         for (String termDisplayName : termDisplayNameList) {
525                 if (termDisplayName.toLowerCase().contains(partialTerm.toLowerCase()) == true) {
526                         result.add(termDisplayName);
527                 }
528         }
529         
530         return result;
531     }
532     
533     @SuppressWarnings("unchecked")
534         private List<String> getPartialTermDisplayNameMatches(DocumentModel docModel, // REM - CSPACE-5133
535                         String schema, ListResultField field, String partialTerm) {
536         List<String> result = null;
537           
538         String xpath = field.getXpath(); // results in something like "persons_common:personTermGroupList/[0]/termDisplayName"
539         int endOfTermGroup = xpath.lastIndexOf("/[0]/");
540         String propertyName = endOfTermGroup != -1 ? xpath.substring(0, endOfTermGroup) : xpath; // it may not be multivalued so the xpath passed in would be the property name
541         Object value = null;
542         
543                 try {
544                         value = docModel.getProperty(schema, propertyName);
545                 } catch (Exception e) {
546                         logger.error("Could not extract term display name with property = "
547                                         + propertyName, e);
548                 }
549                 
550                 if (value != null && value instanceof ArrayList) {
551                         ArrayList<HashMap<String, Object>> termGroupList = (ArrayList<HashMap<String, Object>>)value;
552                         int arrayListSize = termGroupList.size();
553                         if (arrayListSize > 1) { // if there's only 1 element in the list then we've already matched the primary term's display name
554                                 List<String> displayNameList = new ArrayList<String>();
555                                 for (int i = 1; i < arrayListSize; i++) { // start at 1, skip the primary term's displayName since we will always return it
556                                         HashMap<String, Object> map = (HashMap<String, Object>)termGroupList.get(i);
557                                         String termDisplayName = (String) map.get(AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
558                                         displayNameList.add(i - 1, termDisplayName);
559                                 }
560                                 
561                                 result = getPartialTermDisplayNameMatches(displayNameList, partialTerm);
562                         }
563                 }
564
565         return result;
566     }
567
568     @Override
569         protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
570                         String schema, ListResultField field) {
571                 Object result = null;           
572
573                 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
574                 String elName = field.getElement();
575                 //
576                 // If the list result value is the termDisplayName element, we need to check to see if a partial term query was made.
577                 //
578                 if (isTermDisplayName(elName) == true) {
579                         MultivaluedMap<String, String> queryParams = this.getServiceContext().getQueryParams();
580                 String partialTerm = queryParams != null ? queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM) : null;
581                 if (partialTerm != null && partialTerm.trim().isEmpty() == false) {
582                                 String primaryTermDisplayName = (String)result;
583                         List<String> matches = getPartialTermDisplayNameMatches(docModel, schema, field, partialTerm);
584                         if (matches != null && matches.isEmpty() == false) {
585                                 matches.add(0, primaryTermDisplayName); // insert the primary term's display name at the beginning of the list
586                                 result = matches; // set the result to a list of matching term display names with the primary term's display name at the beginning
587                         }
588                 }
589                 }
590                 
591                 return result;
592         }
593     
594     @Override
595     public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
596         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
597         super.extractAllParts(wrapDoc);
598     }
599
600     @Override
601     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
602         //
603         // We currently don't override this method with any AuthorityItemDocumentModelHandler specific functionality, so
604         // we could remove this method.
605         //
606         super.fillAllParts(wrapDoc, action);
607     }
608
609     protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList) {
610         List<RelationsCommonList.RelationListItem> result = newRelationsCommonList();
611         for (RelationsCommonList.RelationListItem item : inboundList) {
612             result.add(item);
613         }
614         return result;
615     }
616
617     // Note that item2 may be sparse (only refName, no CSID for subject or object)
618     // But item1 must not be sparse 
619     private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
620         if (item1 == null || item2 == null) {
621             return false;
622         }
623         RelationsDocListItem subj1 = item1.getSubject();
624         RelationsDocListItem subj2 = item2.getSubject();
625         RelationsDocListItem obj1 = item1.getObject();
626         RelationsDocListItem obj2 = item2.getObject();
627         String subj1Csid = subj1.getCsid();
628         String subj2Csid = subj2.getCsid();
629         String subj1RefName = subj1.getRefName();
630         String subj2RefName = subj2.getRefName();
631
632         String obj1Csid = obj1.getCsid();
633         String obj2Csid = obj2.getCsid();
634         String obj1RefName = obj1.getRefName();
635         String obj2RefName = obj2.getRefName();
636
637         boolean isEqual = 
638                            (subj1Csid.equals(subj2Csid) || ((subj2Csid==null)  && subj1RefName.equals(subj2RefName)))
639                 && (obj1Csid.equals(obj1Csid)   || ((obj2Csid==null)   && obj1RefName.equals(obj2RefName)))
640                 // predicate is proper, but still allow relationshipType
641                 && (item1.getPredicate().equals(item2.getPredicate())
642                         ||  ((item2.getPredicate()==null)  && item1.getRelationshipType().equals(item2.getRelationshipType())))
643                 // Allow missing docTypes, so long as they do not conflict
644                 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
645                 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null);
646         return isEqual;
647     }
648
649
650     /* don't even THINK of re-using this method.
651      * String example_uri = "/locationauthorities/7ec60f01-84ab-4908-9a6a/items/a5466530-713f-43b4-bc05";
652      */
653     private String extractInAuthorityCSID(String uri) {
654         String IN_AUTHORITY_REGEX = "/(.*?)/(.*?)/(.*)";
655         Pattern p = Pattern.compile(IN_AUTHORITY_REGEX);
656         Matcher m = p.matcher(uri);
657         if (m.find()) {
658             if (m.groupCount() < 3) {
659                 logger.warn("REGEX-WRONG-GROUPCOUNT looking in " + uri);
660                 return "";
661             } else {
662                 //String service = m.group(1);
663                 String inauth = m.group(2);
664                 //String theRest = m.group(3);
665                 return inauth;
666                 //print("service:"+service+", inauth:"+inauth+", rest:"+rest);
667             }
668         } else {
669             logger.warn("REGEX-NOT-MATCHED looking in " + uri);
670             return "";
671         }
672     }
673
674     //ensures CSPACE-4042
675     protected void uriPointsToSameAuthority(String thisURI, String inboundItemURI) throws Exception {
676         String authorityCSID = extractInAuthorityCSID(thisURI);
677         String authorityCSIDForInbound = extractInAuthorityCSID(inboundItemURI);
678         if (Tools.isBlank(authorityCSID)
679                 || Tools.isBlank(authorityCSIDForInbound)
680                 || (!authorityCSID.equalsIgnoreCase(authorityCSIDForInbound))) {
681             throw new Exception("Item URI " + thisURI + " must point to same authority as related item: " + inboundItemURI);
682         }
683     }
684
685     public String getItemTermInfoGroupXPathBase() {
686         return authorityItemTermGroupXPathBase;
687     }
688         
689     public void setItemTermInfoGroupXPathBase(String itemTermInfoGroupXPathBase) {
690         authorityItemTermGroupXPathBase = itemTermInfoGroupXPathBase;
691     }
692     
693     protected String getAuthorityItemCommonSchemaName() {
694         return authorityItemCommonSchemaName;
695     }
696 }