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