]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
d0ec9fc6cdb4e31f6b3940fe70409cf47a97c7c9
[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.nuxeo.client.java;
25
26 import java.util.ArrayList;
27 import java.util.GregorianCalendar;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.Set;
33
34 import javax.ws.rs.WebApplicationException;
35 import javax.ws.rs.core.MediaType;
36 import javax.ws.rs.core.Response;
37 import javax.xml.bind.JAXBElement;
38
39 import org.collectionspace.services.authorization.AccountPermission;
40 import org.collectionspace.services.jaxb.AbstractCommonList;
41 import org.collectionspace.services.lifecycle.TransitionDef;
42 import org.collectionspace.services.client.PayloadInputPart;
43 import org.collectionspace.services.client.PayloadOutputPart;
44 import org.collectionspace.services.client.PoxPayloadIn;
45 import org.collectionspace.services.client.PoxPayloadOut;
46 import org.collectionspace.services.client.workflow.WorkflowClient;
47 import org.collectionspace.services.common.authorityref.AuthorityRefList;
48 import org.collectionspace.services.common.context.JaxRsContext;
49 import org.collectionspace.services.common.context.MultipartServiceContext;
50 import org.collectionspace.services.common.context.ServiceContext;
51 import org.collectionspace.services.common.datetime.DateTimeFormatUtils;
52 import org.collectionspace.services.common.document.BadRequestException;
53 import org.collectionspace.services.common.document.DocumentUtils;
54 import org.collectionspace.services.common.document.DocumentWrapper;
55 import org.collectionspace.services.common.document.DocumentFilter;
56 import org.collectionspace.services.common.profile.Profiler;
57 import org.collectionspace.services.common.security.SecurityUtils;
58 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
59 import org.collectionspace.services.common.vocabulary.RefNameUtils;
60 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
61 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
62 import org.collectionspace.services.config.service.InitHandler.Params.Field;
63 import org.collectionspace.services.config.service.ListResultField;
64 import org.collectionspace.services.config.service.ObjectPartType;
65 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
66 import org.dom4j.Element;
67
68 import org.nuxeo.ecm.core.api.DocumentModel;
69 import org.nuxeo.ecm.core.api.DocumentModelList;
70 import org.nuxeo.ecm.core.api.model.Property;
71 import org.nuxeo.ecm.core.api.model.PropertyException;
72 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
73
74 import org.nuxeo.ecm.core.schema.types.Schema;
75
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
78
79 /**
80  * RemoteDocumentModelHandler
81  *
82  * $LastChangedRevision: $
83  * $LastChangedDate: $
84  * @param <T> 
85  * @param <TL> 
86  */
87 public abstract class   RemoteDocumentModelHandlerImpl<T, TL>
88         extends DocumentModelHandler<T, TL> {
89
90     /** The logger. */
91     private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
92
93     /* (non-Javadoc)
94      * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
95      */
96     @Override
97     public void setServiceContext(ServiceContext ctx) {  //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
98         if (ctx instanceof MultipartServiceContext) {
99             super.setServiceContext(ctx);
100         } else {
101             throw new IllegalArgumentException("setServiceContext requires instance of "
102                     + MultipartServiceContext.class.getName());
103         }
104     }
105
106         @Override
107         public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
108                         throws Exception {
109                 // Do nothing by default, but children can override if they want.  The really workflow transition happens in the WorkflowDocumemtModelHandler class
110         }
111     
112     /* (non-Javadoc)
113      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
114      */
115     @Override
116     public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
117         DocumentModel docModel = wrapDoc.getWrappedObject();
118         //return at least those document part(s) that were received
119         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
120         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
121         PoxPayloadIn input = ctx.getInput();
122         if (input != null) {
123                 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
124                 for (PayloadInputPart part : inputParts) {
125                     String partLabel = part.getLabel();
126                 try{
127                     ObjectPartType partMeta = partsMetaMap.get(partLabel);
128                     // CSPACE-4030 - generates NPE if the part is missing.
129                     if(partMeta!=null) {
130                             Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
131                             if(unQObjectProperties!=null) {
132                                 addOutputPart(unQObjectProperties, partLabel, partMeta);
133                             }
134                     }
135                 } catch (Throwable t){
136
137                     logger.error("Unable to addOutputPart: "+partLabel
138                                                +" in serviceContextPath: "+this.getServiceContextPath()
139                                                +" with URI: "+this.getServiceContext().getUriInfo().getPath()
140                                                +" error: "+t);
141                 }
142                 }
143         } else {
144                 if (logger.isWarnEnabled() == true) {
145                         logger.warn("MultipartInput part was null for document id = " +
146                                         docModel.getName());
147                 }
148         }
149     }
150
151     /**
152      * Adds the output part.
153      *
154      * @param unQObjectProperties the un q object properties
155      * @param schema the schema
156      * @param partMeta the part meta
157      * @throws Exception the exception
158      * MediaType.APPLICATION_XML_TYPE
159      */
160     protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
161             throws Exception {
162         Element doc = DocumentUtils.buildDocument(partMeta, schema,
163                 unQObjectProperties);
164         if (logger.isTraceEnabled() == true) {
165             logger.trace(doc.asXML());
166         }
167         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
168         ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
169     }
170     
171     /**
172      * Extract paging info.
173      *
174      * @param commonsList the commons list
175      * @return the tL
176      * @throws Exception the exception
177      */
178     public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
179             throws Exception {
180         AbstractCommonList commonList = (AbstractCommonList) theCommonList;
181
182         DocumentFilter docFilter = this.getDocumentFilter();
183         long pageSize = docFilter.getPageSize();
184         long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
185         // set the page size and page number
186         commonList.setPageNum(pageNum);
187         commonList.setPageSize(pageSize);
188         DocumentModelList docList = wrapDoc.getWrappedObject();
189         // Set num of items in list. this is useful to our testing framework.
190         commonList.setItemsInPage(docList.size());
191         // set the total result size
192         commonList.setTotalItems(docList.totalSize());
193
194         return (TL) commonList;
195     }
196
197     /* (non-Javadoc)
198      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
199      */
200     @Override
201     public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
202             throws Exception {
203
204         DocumentModel docModel = wrapDoc.getWrappedObject();
205         String[] schemas = docModel.getDeclaredSchemas();
206         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
207         for (String schema : schemas) {
208             ObjectPartType partMeta = partsMetaMap.get(schema);
209             if (partMeta == null) {
210                 continue; // unknown part, ignore
211             }
212             Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
213             if(COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
214                 addExtraCoreValues(docModel, unQObjectProperties);
215             }
216             addOutputPart(unQObjectProperties, schema, partMeta);
217         }
218         addAccountPermissionsPart();
219     }
220     
221     private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
222                 throws Exception {
223         unQObjectProperties.put(COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
224     }
225     
226     private void addAccountPermissionsPart() throws Exception {
227         Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
228         profiler.start();
229         
230         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
231         String currentServiceName = ctx.getServiceName();
232         String workflowSubResource = "/";
233         JaxRsContext jaxRsContext = ctx.getJaxRsContext();
234         if (jaxRsContext != null) {
235                 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
236                 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
237         } else {
238                 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
239         }
240         AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
241                         currentServiceName, workflowSubResource);
242         org.collectionspace.services.authorization.ObjectFactory objectFactory =
243                 new org.collectionspace.services.authorization.ObjectFactory();
244         JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
245         PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap);
246         ctx.addOutputPart(accountPermissionPart);
247         
248         profiler.stop();
249     }
250
251     /* (non-Javadoc)
252      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
253      */
254     @Override
255     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
256
257         //TODO filling extension parts should be dynamic
258         //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
259         //not an ideal way of populating objects.
260         DocumentModel docModel = wrapDoc.getWrappedObject();
261         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
262         PoxPayloadIn input = ctx.getInput();
263         if (input.getParts().isEmpty()) {
264             String msg = "No payload found!";
265             logger.error(msg + "Ctx=" + getServiceContext().toString());
266             throw new BadRequestException(msg);
267         }
268
269         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
270
271         //iterate over parts received and fill those parts
272         List<PayloadInputPart> inputParts = input.getParts();
273         for (PayloadInputPart part : inputParts) {
274
275             String partLabel = part.getLabel();
276             if (partLabel == null) {
277                 String msg = "Part label is missing or empty!";
278                 logger.error(msg + "Ctx=" + getServiceContext().toString());
279                 throw new BadRequestException(msg);
280             }
281
282             //skip if the part is not in metadata
283             ObjectPartType partMeta = partsMetaMap.get(partLabel);
284             if (partMeta == null) {
285                 continue;
286             }
287             fillPart(part, docModel, partMeta, action, ctx);
288         }//rof
289
290     }
291
292     /**
293      * fillPart fills an XML part into given document model
294      * @param part to fill
295      * @param docModel for the given object
296      * @param partMeta metadata for the object to fill
297      * @throws Exception
298      */
299     protected void fillPart(PayloadInputPart part, DocumentModel docModel,
300             ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
301             throws Exception {
302         //check if this is an xml part
303         if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
304                 Element element = part.getElementBody();
305             Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
306                 if (action == Action.UPDATE) {
307                     this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
308                 }
309                 docModel.setProperties(partMeta.getLabel(), objectProps);
310             }
311         }
312
313     /**
314      * Filters out read only properties, so they cannot be set on update.
315      * TODO: add configuration support to do this generally
316      * @param objectProps the properties parsed from the update payload
317      * @param partMeta metadata for the object to fill
318      */
319     public void filterReadOnlyPropertiesForPart(
320             Map<String, Object> objectProps, ObjectPartType partMeta) {
321         // Should add in logic to filter most of the core items on update
322         if(partMeta.getLabel().equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA)) {
323                 objectProps.remove(COLLECTIONSPACE_CORE_CREATED_AT);
324                 objectProps.remove(COLLECTIONSPACE_CORE_CREATED_BY);
325                 objectProps.remove(COLLECTIONSPACE_CORE_URI);
326                 objectProps.remove(COLLECTIONSPACE_CORE_TENANTID);
327                 // Note that the updatedAt/updatedBy fields are set internally
328                 // in DocumentModelHandler.handleCoreValues().
329         }
330     }
331
332     /**
333      * extractPart extracts an XML object from given DocumentModel
334      * @param docModel
335      * @param schema of the object to extract
336      * @param partMeta metadata for the object to extract
337      * @throws Exception
338      */
339     protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
340             throws Exception {
341         return extractPart(docModel, schema, (Map<String, Object>)null);
342     }
343     
344     /**
345      * extractPart extracts an XML object from given DocumentModel
346      * @param docModel
347      * @param schema of the object to extract
348      * @param partMeta metadata for the object to extract
349      * @throws Exception
350      */
351     @Deprecated
352     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
353             throws Exception {
354         return extractPart(docModel, schema, partMeta, null);
355     }    
356
357     /**
358      * extractPart extracts an XML object from given DocumentModel
359      * @param docModel
360      * @param schema of the object to extract
361      * @param partMeta metadata for the object to extract
362      * @throws Exception
363      */
364     protected Map<String, Object> extractPart(
365             DocumentModel docModel, 
366             String schema,
367             Map<String, Object> addToMap)
368             throws Exception {
369         Map<String, Object> result = null;
370
371         Map<String, Object> objectProps = docModel.getProperties(schema);
372         if (objectProps != null) {
373                 //unqualify properties before sending the doc over the wire (to save bandwidh)
374                 //FIXME: is there a better way to avoid duplication of a Map/Collection?
375                 Map<String, Object> unQObjectProperties =
376                         (addToMap != null) ? addToMap : (new HashMap<String, Object>());
377                 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
378                 for (Entry<String, Object> entry : qualifiedEntries) {
379                     String unqProp = getUnQProperty(entry.getKey());
380                     unQObjectProperties.put(unqProp, entry.getValue());
381                 }
382                 result = unQObjectProperties;
383         }
384
385         return result;
386     }
387     
388     /**
389      * extractPart extracts an XML object from given DocumentModel
390      * @param docModel
391      * @param schema of the object to extract
392      * @param partMeta metadata for the object to extract
393      * @throws Exception
394      */
395     @Deprecated
396     protected Map<String, Object> extractPart(
397             DocumentModel docModel, String schema, ObjectPartType partMeta,
398             Map<String, Object> addToMap)
399             throws Exception {
400         Map<String, Object> result = null;
401
402         result = this.extractPart(docModel, schema, addToMap);
403
404         return result;
405     }
406     
407     /* 
408     public String getStringPropertyFromDoc(
409                 ServiceContext ctx,
410                 String csid,
411                 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
412         RepositoryInstance repoSession = null;
413         boolean releaseRepoSession = false;
414         String returnValue = null;
415
416         try{ 
417                 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
418                 repoSession = this.getRepositorySession();
419                 if (repoSession == null) {
420                         repoSession = repoClient.getRepositorySession();
421                         releaseRepoSession = true;
422                 }
423
424                 try {
425                         DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
426                         DocumentModel docModel = wrapper.getWrappedObject();
427                         returnValue = (String) docModel.getPropertyValue(propertyXPath);
428                 } catch (PropertyException pe) {
429                         throw pe;
430                 } catch (DocumentException de) {
431                         throw de;
432                 } catch (Exception e) {
433                         if (logger.isDebugEnabled()) {
434                                 logger.debug("Caught exception ", e);
435                         }
436                         throw new DocumentException(e);
437                 } finally {
438                         if (releaseRepoSession && repoSession != null) {
439                                 repoClient.releaseRepositorySession(repoSession);
440                         }
441                 }
442         } catch (Exception e) {
443                 if (logger.isDebugEnabled()) {
444                         logger.debug("Caught exception ", e);
445                 }
446                 throw new DocumentException(e);
447         }               
448
449
450         if (logger.isWarnEnabled() == true) {
451                 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
452         }
453         return returnValue;
454     }
455      */
456
457     
458
459     /* (non-Javadoc)
460      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
461      */
462     @Override
463     public AuthorityRefList getAuthorityRefs(
464             String csid,
465             List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
466
467         AuthorityRefList authRefList = new AuthorityRefList();
468         AbstractCommonList commonList = (AbstractCommonList) authRefList;
469         
470         DocumentFilter docFilter = this.getDocumentFilter();
471         long pageSize = docFilter.getPageSize();
472         long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
473         // set the page size and page number
474         commonList.setPageNum(pageNum);
475         commonList.setPageSize(pageSize);
476         
477         List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
478
479         try {
480                 int iFirstToUse = (int)(pageSize*pageNum);
481                 int nFoundInPage = 0;
482                 int nFoundTotal = 0;
483                 
484                 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps 
485                         = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
486                 
487                 boolean releaseRepoSession = false;
488                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
489                 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
490                 RepositoryInstance repoSession = this.getRepositorySession();
491                 if (repoSession == null) {
492                         repoSession = repoClient.getRepositorySession();
493                         releaseRepoSession = true;
494                 }
495                 
496                 try {
497                         DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
498                         RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
499                         // Slightly goofy pagination support - how many refs do we expect from one object?
500                         for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
501                                 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
502                                         if(appendToAuthRefsList(ari, list)) {
503                                                 nFoundInPage++;
504                                         nFoundTotal++;
505                                         }
506                                 } else {
507                                         nFoundTotal++;
508                                 }
509                         }
510                 } finally {
511                         if (releaseRepoSession == true) {
512                                 repoClient.releaseRepositorySession(repoSession);
513                         }
514                 }
515                 
516             // Set num of items in list. this is useful to our testing framework.
517             commonList.setItemsInPage(nFoundInPage);
518             // set the total result size
519             commonList.setTotalItems(nFoundTotal);
520             
521         } catch (PropertyException pe) {
522             String msg = "Attempted to retrieve value for invalid or missing authority field. "
523                     + "Check authority field properties in tenant bindings.";
524             logger.warn(msg, pe);
525             throw pe;
526         } catch (Exception e) {
527             if (logger.isDebugEnabled()) {
528                 logger.debug("Caught exception in getAuthorityRefs", e);
529             }
530             Response response = Response.status(
531                     Response.Status.INTERNAL_SERVER_ERROR).entity(
532                     "Failed to retrieve authority references").type(
533                     "text/plain").build();
534             throw new WebApplicationException(response);
535         }
536
537         return authRefList;
538     }
539
540     private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari, 
541                                                 List<AuthorityRefList.AuthorityRefItem> list)
542             throws Exception {
543         String fieldName = ari.getQualifiedDisplayName();
544         try {
545                         String refNameValue = (String)ari.getProperty().getValue();
546                         AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
547                         if(item!=null) {        // ignore garbage values.
548                                 list.add(item);
549                                 return true;
550                         }
551         } catch(PropertyException pe) {
552                         logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
553         }
554         return false;
555     }
556
557     private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
558
559         AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
560         try {
561             RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
562             ilistItem.setRefName(refName);
563             ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
564             ilistItem.setItemDisplayName(termInfo.displayName);
565             ilistItem.setSourceField(authRefFieldName);
566             ilistItem.setUri(termInfo.getRelativeUri());
567         } catch (Exception e) {
568                 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
569                 ilistItem = null;
570         }
571         return ilistItem;
572     }
573
574     /**
575      * Returns the primary value from a list of values.
576      *
577      * Assumes that the first value is the primary value.
578      * This assumption may change when and if the primary value
579      * is identified explicitly.
580      *
581      * @param values a list of values.
582      * @param propertyName the name of a property through
583      *     which the value can be extracted.
584      * @return the primary value.
585     protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
586         String primaryValue = "";
587         if (values == null || values.size() == 0) {
588             return primaryValue;
589         }
590         Object value = values.get(0);
591         if (value instanceof String) {
592             if (value != null) {
593                 primaryValue = (String) value;
594             }
595        // Multivalue group of fields
596        } else if (value instanceof Map) {
597             if (value != null) {
598                 Map map = (Map) value;
599                 if (map.values().size() > 0) {
600                     if (map.get(propertyName) != null) {
601                       primaryValue = (String) map.get(propertyName);
602                     }
603                 }
604             }
605        } else {
606             logger.warn("Unexpected type for property " + propertyName
607                     + " in multivalue list: not String or Map.");
608        }
609        return primaryValue;
610     }
611      */
612
613     /**
614      * Gets a simple property from the document.
615      *
616      * For completeness, as this duplicates DocumentModel method. 
617      *
618      * @param docModel The document model to get info from
619      * @param schema The name of the schema (part)
620      * @param propertyName The simple scalar property type
621      * @return property value as String
622      */
623     protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
624         String xpath = "/"+schema+":"+propName;
625         try {
626                 return (String)docModel.getPropertyValue(xpath);
627         } catch(PropertyException pe) {
628                 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
629                                 +pe.getLocalizedMessage());
630         } catch(ClassCastException cce) {
631                 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
632                                 +cce.getLocalizedMessage());
633         } catch(Exception e) {
634                 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
635                                 +e.getLocalizedMessage());
636         }
637     }
638
639     /**
640      * Gets first of a repeating list of scalar values, as a String, from the document.
641      *
642      * @param docModel The document model to get info from
643      * @param schema The name of the schema (part)
644      * @param listName The name of the scalar list property
645      * @return first value in list, as a String, or empty string if the list is empty
646      */
647     protected String getFirstRepeatingStringProperty(
648                 DocumentModel docModel, String schema, String listName) {
649         String xpath = "/"+schema+":"+listName+"/[0]";
650         try {
651                 return (String)docModel.getPropertyValue(xpath);
652         } catch(PropertyException pe) {
653                 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
654                                 +pe.getLocalizedMessage());
655         } catch(IndexOutOfBoundsException ioobe) {
656                 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
657                 return "";      // gracefully handle missing elements
658         } catch(ClassCastException cce) {
659                 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
660                                 +cce.getLocalizedMessage());
661         } catch(Exception e) {
662                 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
663                                 +e.getLocalizedMessage());
664         }
665     }
666    
667
668     /**
669      * Gets first of a repeating list of scalar values, as a String, from the document.
670      *
671      * @param docModel The document model to get info from
672      * @param schema The name of the schema (part)
673      * @param listName The name of the scalar list property
674      * @return first value in list, as a String, or empty string if the list is empty
675      */
676     protected String getStringValueInPrimaryRepeatingComplexProperty(
677                 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {          
678         String result = null;
679         
680         String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
681         try {
682                 result = (String)docModel.getPropertyValue(xpath);
683         } catch(PropertyException pe) {
684                 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
685                                 +pe.getLocalizedMessage());
686         } catch(IndexOutOfBoundsException ioobe) {
687                 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
688                 result = "";    // gracefully handle missing elements
689         } catch(ClassCastException cce) {
690                 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
691                                 +cce.getLocalizedMessage());
692         } catch(Exception e) {
693                 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
694                                 +e.getLocalizedMessage());
695         }
696         
697         return result;
698     }
699    
700     /**
701      * Gets XPath value from schema. Note that only "/" and "[n]" are
702      * supported for xpath. Can omit grouping elements for repeating complex types, 
703      * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
704      * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
705      * If there are no entries for a list of scalars or for a list of complex types, 
706      * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
707      * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
708      * that many elements in the list. 
709      * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
710      *
711      * @param docModel The document model to get info from
712      * @param schema The name of the schema (part)
713      * @param xpath The XPath expression (without schema prefix)
714      * @return value the indicated property value as a String
715      */
716         protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
717                         String schema, ListResultField field) {
718                 Object result = null;
719
720                 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
721                 
722                 return result;
723         }
724    
725 }