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