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:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
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.
24 package org.collectionspace.services.nuxeo.client.java;
26 import java.util.Collection;
27 import java.util.GregorianCalendar;
28 import java.util.HashMap;
29 import java.util.List;
31 import java.util.Map.Entry;
34 import javax.ws.rs.WebApplicationException;
35 import javax.ws.rs.core.MediaType;
36 import javax.ws.rs.core.Response;
38 import org.collectionspace.services.authorization.AccountPermission;
39 import org.collectionspace.services.jaxb.AbstractCommonList;
40 import org.collectionspace.services.client.PayloadInputPart;
41 import org.collectionspace.services.client.PayloadOutputPart;
42 import org.collectionspace.services.client.PoxPayloadIn;
43 import org.collectionspace.services.client.PoxPayloadOut;
44 import org.collectionspace.services.client.workflow.WorkflowClient;
45 import org.collectionspace.services.common.authorityref.AuthorityRefList;
46 import org.collectionspace.services.common.context.JaxRsContext;
47 import org.collectionspace.services.common.context.MultipartServiceContext;
48 import org.collectionspace.services.common.context.ServiceContext;
49 import org.collectionspace.services.common.datetime.DateTimeFormatUtils;
50 import org.collectionspace.services.common.document.BadRequestException;
51 import org.collectionspace.services.common.document.DocumentUtils;
52 import org.collectionspace.services.common.document.DocumentWrapper;
53 import org.collectionspace.services.common.document.DocumentFilter;
54 import org.collectionspace.services.common.document.DocumentHandler.Action;
55 import org.collectionspace.services.common.security.SecurityUtils;
56 import org.collectionspace.services.common.service.ObjectPartType;
57 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
58 import org.collectionspace.services.common.vocabulary.RefNameUtils;
59 import org.dom4j.Element;
61 import org.nuxeo.ecm.core.api.DocumentModel;
62 import org.nuxeo.ecm.core.api.DocumentModelList;
63 import org.nuxeo.ecm.core.api.model.Property;
64 import org.nuxeo.ecm.core.api.model.PropertyException;
66 import org.nuxeo.ecm.core.schema.types.Schema;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
72 * RemoteDocumentModelHandler
74 * $LastChangedRevision: $
79 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
80 extends DocumentModelHandler<T, TL> {
83 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
86 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
89 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
90 if (ctx instanceof MultipartServiceContext) {
91 super.setServiceContext(ctx);
93 throw new IllegalArgumentException("setServiceContext requires instance of "
94 + MultipartServiceContext.class.getName());
99 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
102 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
103 DocumentModel docModel = wrapDoc.getWrappedObject();
104 //return at least those document part(s) that were received
105 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
106 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
107 PoxPayloadIn input = ctx.getInput();
109 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
110 for (PayloadInputPart part : inputParts) {
111 String partLabel = part.getLabel();
113 ObjectPartType partMeta = partsMetaMap.get(partLabel);
114 // CSPACE-4030 - generates NPE if the part is missing.
116 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
117 if(unQObjectProperties!=null) {
118 addOutputPart(unQObjectProperties, partLabel, partMeta);
121 } catch (Throwable t){
123 logger.error("Unable to addOutputPart: "+partLabel
124 +" in serviceContextPath: "+this.getServiceContextPath()
125 +" with URI: "+this.getServiceContext().getUriInfo().getPath()
130 if (logger.isWarnEnabled() == true) {
131 logger.warn("MultipartInput part was null for document id = " +
138 * Adds the output part.
140 * @param unQObjectProperties the un q object properties
141 * @param schema the schema
142 * @param partMeta the part meta
143 * @throws Exception the exception
144 * MediaType.APPLICATION_XML_TYPE
146 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
148 Element doc = DocumentUtils.buildDocument(partMeta, schema,
149 unQObjectProperties);
150 if (logger.isTraceEnabled() == true) {
151 logger.trace(doc.asXML());
153 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
154 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
158 * Extract paging info.
160 * @param commonsList the commons list
162 * @throws Exception the exception
164 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
166 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
168 DocumentFilter docFilter = this.getDocumentFilter();
169 long pageSize = docFilter.getPageSize();
170 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
171 // set the page size and page number
172 commonList.setPageNum(pageNum);
173 commonList.setPageSize(pageSize);
174 DocumentModelList docList = wrapDoc.getWrappedObject();
175 // Set num of items in list. this is useful to our testing framework.
176 commonList.setItemsInPage(docList.size());
177 // set the total result size
178 commonList.setTotalItems(docList.totalSize());
180 return (TL) commonList;
184 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
187 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
190 DocumentModel docModel = wrapDoc.getWrappedObject();
191 String[] schemas = docModel.getDeclaredSchemas();
192 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
193 for (String schema : schemas) {
194 ObjectPartType partMeta = partsMetaMap.get(schema);
195 if (partMeta == null) {
196 continue; // unknown part, ignore
198 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
199 addOutputPart(unQObjectProperties, schema, partMeta);
201 addAccountPermissionsPart();
204 private void addAccountPermissionsPart() throws Exception {
205 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
206 String currentServiceName = ctx.getServiceName();
207 String workflowSubResource = "/";
208 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
209 if (jaxRsContext != null) {
210 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
211 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
213 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
215 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
216 currentServiceName, workflowSubResource);
217 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", accountPermission);
218 ctx.addOutputPart(accountPermissionPart);
222 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
225 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
227 //TODO filling extension parts should be dynamic
228 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
229 //not an ideal way of populating objects.
230 DocumentModel docModel = wrapDoc.getWrappedObject();
231 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
232 PoxPayloadIn input = ctx.getInput();
233 if (input.getParts().isEmpty()) {
234 String msg = "No payload found!";
235 logger.error(msg + "Ctx=" + getServiceContext().toString());
236 throw new BadRequestException(msg);
239 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
241 //iterate over parts received and fill those parts
242 List<PayloadInputPart> inputParts = input.getParts();
243 for (PayloadInputPart part : inputParts) {
245 String partLabel = part.getLabel();
246 if (partLabel == null) {
247 String msg = "Part label is missing or empty!";
248 logger.error(msg + "Ctx=" + getServiceContext().toString());
249 throw new BadRequestException(msg);
252 //skip if the part is not in metadata
253 ObjectPartType partMeta = partsMetaMap.get(partLabel);
254 if (partMeta == null) {
257 fillPart(part, docModel, partMeta, action, ctx);
263 * fillPart fills an XML part into given document model
264 * @param part to fill
265 * @param docModel for the given object
266 * @param partMeta metadata for the object to fill
269 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
270 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
272 //check if this is an xml part
273 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
274 Element element = part.getElementBody();
275 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
276 if (action == Action.UPDATE) {
277 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
279 docModel.setProperties(partMeta.getLabel(), objectProps);
284 * Filters out read only properties, so they cannot be set on update.
285 * TODO: add configuration support to do this generally
286 * @param objectProps the properties parsed from the update payload
287 * @param partMeta metadata for the object to fill
289 public void filterReadOnlyPropertiesForPart(
290 Map<String, Object> objectProps, ObjectPartType partMeta) {
291 // Should add in logic to filter most of the core items on update
292 if(partMeta.getLabel().equalsIgnoreCase(COLLECTIONSPACE_CORE_SCHEMA)) {
293 objectProps.remove(COLLECTIONSPACE_CORE_CREATED_AT);
294 objectProps.remove(COLLECTIONSPACE_CORE_CREATED_BY);
295 objectProps.remove(COLLECTIONSPACE_CORE_URI);
296 objectProps.remove(COLLECTIONSPACE_CORE_TENANTID);
297 // Note that the updatedAt/updatedBy fields are set internally
298 // in DocumentModelHandler.handleCoreValues().
303 * extractPart extracts an XML object from given DocumentModel
305 * @param schema of the object to extract
306 * @param partMeta metadata for the object to extract
309 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
311 return extractPart(docModel, schema, (Map<String, Object>)null);
315 * extractPart extracts an XML object from given DocumentModel
317 * @param schema of the object to extract
318 * @param partMeta metadata for the object to extract
322 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
324 return extractPart(docModel, schema, partMeta, null);
328 * extractPart extracts an XML object from given DocumentModel
330 * @param schema of the object to extract
331 * @param partMeta metadata for the object to extract
334 protected Map<String, Object> extractPart(
335 DocumentModel docModel,
337 Map<String, Object> addToMap)
339 Map<String, Object> result = null;
341 Map<String, Object> objectProps = docModel.getProperties(schema);
342 if (objectProps != null) {
343 //unqualify properties before sending the doc over the wire (to save bandwidh)
344 //FIXME: is there a better way to avoid duplication of a Map/Collection?
345 Map<String, Object> unQObjectProperties =
346 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
347 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
348 for (Entry<String, Object> entry : qualifiedEntries) {
349 String unqProp = getUnQProperty(entry.getKey());
350 unQObjectProperties.put(unqProp, entry.getValue());
352 result = unQObjectProperties;
359 * extractPart extracts an XML object from given DocumentModel
361 * @param schema of the object to extract
362 * @param partMeta metadata for the object to extract
366 protected Map<String, Object> extractPart(
367 DocumentModel docModel, String schema, ObjectPartType partMeta,
368 Map<String, Object> addToMap)
370 Map<String, Object> result = null;
372 result = this.extractPart(docModel, schema, addToMap);
379 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
382 public AuthorityRefList getAuthorityRefs(
383 DocumentWrapper<DocumentModel> docWrapper,
384 List<String> authRefFieldNames) throws PropertyException {
386 AuthorityRefList authRefList = new AuthorityRefList();
387 AbstractCommonList commonList = (AbstractCommonList) authRefList;
389 DocumentFilter docFilter = this.getDocumentFilter();
390 long pageSize = docFilter.getPageSize();
391 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
392 // set the page size and page number
393 commonList.setPageNum(pageNum);
394 commonList.setPageSize(pageSize);
396 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
397 DocumentModel docModel = docWrapper.getWrappedObject();
400 int iFirstToUse = (int)(pageSize*pageNum);
401 int nFoundInPage = 0;
403 for (String authRefFieldName : authRefFieldNames) {
405 // FIXME: Can use the schema to validate field existence,
406 // to help avoid encountering PropertyExceptions.
407 String schemaName = DocumentUtils.getSchemaNamePart(authRefFieldName);
408 Schema schema = DocumentUtils.getSchemaFromName(schemaName);
410 String descendantAuthRefFieldName = DocumentUtils.getDescendantAuthRefFieldName(authRefFieldName);
411 if (descendantAuthRefFieldName != null && !descendantAuthRefFieldName.trim().isEmpty()) {
412 authRefFieldName = DocumentUtils.getAncestorAuthRefFieldName(authRefFieldName);
415 String xpath = "//" + authRefFieldName;
416 Property prop = docModel.getProperty(xpath);
421 // If this is a single scalar field, with no children,
422 // add an item with its values to the authRefs list.
423 if (DocumentUtils.isSimpleType(prop)) {
424 String refName = prop.getValue(String.class);
425 if (refName == null) {
428 refName = refName.trim();
429 if (refName.isEmpty()) {
432 if((nFoundTotal < iFirstToUse)
433 || (nFoundInPage >= pageSize)) {
439 appendToAuthRefsList(refName, schemaName, authRefFieldName, list);
441 // Otherwise, if this field has children, cycle through each child.
443 // Whenever we find instances of the descendant field among
444 // these children, add an item with its values to the authRefs list.
446 // FIXME: When we increase maximum repeatability depth, that is, the depth
447 // between ancestor and descendant, we'll need to use recursion here,
448 // rather than making fixed assumptions about hierarchical depth.
449 } else if ((DocumentUtils.isListType(prop) || DocumentUtils.isComplexType(prop))
450 && prop.size() > 0) {
452 Collection<Property> childProp = prop.getChildren();
453 for (Property cProp : childProp) {
454 if (DocumentUtils.isSimpleType(cProp) && cProp.getName().equals(descendantAuthRefFieldName)) {
455 String refName = cProp.getValue(String.class);
456 if (refName == null) {
459 refName = refName.trim();
460 if (refName.isEmpty()) {
463 if((nFoundTotal < iFirstToUse)
464 || (nFoundInPage >= pageSize)) {
470 appendToAuthRefsList(refName, schemaName, descendantAuthRefFieldName, list);
471 } else if ((DocumentUtils.isListType(cProp) || DocumentUtils.isComplexType(cProp))
472 && prop.size() > 0) {
473 Collection<Property> grandChildProp = cProp.getChildren();
474 for (Property gProp : grandChildProp) {
475 if (DocumentUtils.isSimpleType(gProp) && gProp.getName().equals(descendantAuthRefFieldName)) {
476 String refName = gProp.getValue(String.class);
477 if (refName == null) {
480 refName = refName.trim();
481 if (refName.isEmpty()) {
484 if((nFoundTotal < iFirstToUse)
485 || (nFoundInPage >= pageSize)) {
491 appendToAuthRefsList(refName, schemaName, descendantAuthRefFieldName, list);
498 // Set num of items in list. this is useful to our testing framework.
499 commonList.setItemsInPage(nFoundInPage);
500 // set the total result size
501 commonList.setTotalItems(nFoundTotal);
503 } catch (PropertyException pe) {
504 String msg = "Attempted to retrieve value for invalid or missing authority field. "
505 + "Check authority field properties in tenant bindings.";
506 logger.warn(msg, pe);
508 } catch (Exception e) {
509 if (logger.isDebugEnabled()) {
510 logger.debug("Caught exception in getAuthorityRefs", e);
512 Response response = Response.status(
513 Response.Status.INTERNAL_SERVER_ERROR).entity(
514 "Failed to retrieve authority references").type(
515 "text/plain").build();
516 throw new WebApplicationException(response);
522 private void appendToAuthRefsList(String refName, String schemaName,
523 String fieldName, List<AuthorityRefList.AuthorityRefItem> list)
525 if (DocumentUtils.getSchemaNamePart(fieldName).isEmpty()) {
526 fieldName = DocumentUtils.appendSchemaName(schemaName, fieldName);
528 list.add(authorityRefListItem(fieldName, refName));
531 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
533 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
535 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
536 ilistItem.setRefName(refName);
537 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
538 ilistItem.setItemDisplayName(termInfo.displayName);
539 ilistItem.setSourceField(authRefFieldName);
540 ilistItem.setUri(termInfo.getRelativeUri());
541 } catch (Exception e) {
542 // Do nothing upon encountering an Exception here.
548 * Returns the primary value from a list of values.
550 * Assumes that the first value is the primary value.
551 * This assumption may change when and if the primary value
552 * is identified explicitly.
554 * @param values a list of values.
555 * @param propertyName the name of a property through
556 * which the value can be extracted.
557 * @return the primary value.
558 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
559 String primaryValue = "";
560 if (values == null || values.size() == 0) {
563 Object value = values.get(0);
564 if (value instanceof String) {
566 primaryValue = (String) value;
568 // Multivalue group of fields
569 } else if (value instanceof Map) {
571 Map map = (Map) value;
572 if (map.values().size() > 0) {
573 if (map.get(propertyName) != null) {
574 primaryValue = (String) map.get(propertyName);
579 logger.warn("Unexpected type for property " + propertyName
580 + " in multivalue list: not String or Map.");
587 * Gets a simple property from the document.
589 * For completeness, as this duplicates DocumentModel method.
591 * @param docModel The document model to get info from
592 * @param schema The name of the schema (part)
593 * @param propertyName The simple scalar property type
594 * @return property value as String
596 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
597 String xpath = "/"+schema+":"+propName;
599 return (String)docModel.getPropertyValue(xpath);
600 } catch(PropertyException pe) {
601 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
602 +pe.getLocalizedMessage());
603 } catch(ClassCastException cce) {
604 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
605 +cce.getLocalizedMessage());
606 } catch(Exception e) {
607 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
608 +e.getLocalizedMessage());
613 * Gets first of a repeating list of scalar values, as a String, from the document.
615 * @param docModel The document model to get info from
616 * @param schema The name of the schema (part)
617 * @param listName The name of the scalar list property
618 * @return first value in list, as a String, or empty string if the list is empty
620 protected String getFirstRepeatingStringProperty(
621 DocumentModel docModel, String schema, String listName) {
622 String xpath = "/"+schema+":"+listName+"/[0]";
624 return (String)docModel.getPropertyValue(xpath);
625 } catch(PropertyException pe) {
626 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
627 +pe.getLocalizedMessage());
628 } catch(IndexOutOfBoundsException ioobe) {
629 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
630 return ""; // gracefully handle missing elements
631 } catch(ClassCastException cce) {
632 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
633 +cce.getLocalizedMessage());
634 } catch(Exception e) {
635 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
636 +e.getLocalizedMessage());
642 * Gets first of a repeating list of scalar values, as a String, from the document.
644 * @param docModel The document model to get info from
645 * @param schema The name of the schema (part)
646 * @param listName The name of the scalar list property
647 * @return first value in list, as a String, or empty string if the list is empty
649 protected String getStringValueInPrimaryRepeatingComplexProperty(
650 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
651 String xpath = "/"+schema+":"+complexPropertyName+"/[0]/"+fieldName;
653 return (String)docModel.getPropertyValue(xpath);
654 } catch(PropertyException pe) {
655 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
656 +pe.getLocalizedMessage());
657 } catch(IndexOutOfBoundsException ioobe) {
658 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
659 return ""; // gracefully handle missing elements
660 } catch(ClassCastException cce) {
661 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
662 +cce.getLocalizedMessage());
663 } catch(Exception e) {
664 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
665 +e.getLocalizedMessage());
670 * Gets XPath value from schema. Note that only "/" and "[n]" are
671 * supported for xpath. Can omit grouping elements for repeating complex types,
672 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
673 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
674 * If there are no entries for a list of scalars or for a list of complex types,
675 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
676 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
677 * that many elements in the list.
678 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
680 * @param docModel The document model to get info from
681 * @param schema The name of the schema (part)
682 * @param xpath The XPath expression (without schema prefix)
683 * @return value the indicated property value as a String
685 protected static String getXPathStringValue(DocumentModel docModel, String schema, String xpath) {
686 xpath = schema+":"+xpath;
688 Object value = docModel.getPropertyValue(xpath);
689 String returnVal = null;
691 // Nothing to do - leave returnVal null
692 } else if(value instanceof GregorianCalendar) {
693 returnVal = DateTimeFormatUtils.formatAsISO8601Timestamp((GregorianCalendar)value);
695 returnVal = value.toString();
698 } catch(PropertyException pe) {
699 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad XPath spec?"
700 +pe.getLocalizedMessage());
701 } catch(ClassCastException cce) {
702 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
703 +cce.getLocalizedMessage());
704 } catch(IndexOutOfBoundsException ioobe) {
705 // Nuxeo seems to handle foo/[0]/bar when it is missing,
706 // but not foo/bar[0] (for repeating scalars).
707 if(xpath.endsWith("[0]")) { // gracefully handle missing elements
710 String msg = ioobe.getMessage();
711 if(msg!=null && msg.equals("Index: 0, Size: 0")) {
712 // Some other variant on a missing sub-field; quietly absorb.
714 } // Otherwise, e.g., for true OOB indices, propagate the exception.
716 throw new RuntimeException("Problem retrieving property {"+xpath+"}:"
717 +ioobe.getLocalizedMessage());
718 } catch(Exception e) {
719 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
720 +e.getLocalizedMessage());