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