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