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.ArrayList;
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;
37 import javax.xml.bind.JAXBElement;
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;
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;
72 import org.nuxeo.ecm.core.schema.types.Schema;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
78 * RemoteDocumentModelHandler
80 * $LastChangedRevision: $
85 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
86 extends DocumentModelHandler<T, TL> {
89 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
92 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
95 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
96 if (ctx instanceof MultipartServiceContext) {
97 super.setServiceContext(ctx);
99 throw new IllegalArgumentException("setServiceContext requires instance of "
100 + MultipartServiceContext.class.getName());
105 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
107 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
111 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
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();
121 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
122 for (PayloadInputPart part : inputParts) {
123 String partLabel = part.getLabel();
125 ObjectPartType partMeta = partsMetaMap.get(partLabel);
126 // CSPACE-4030 - generates NPE if the part is missing.
128 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
129 if(unQObjectProperties!=null) {
130 addOutputPart(unQObjectProperties, partLabel, partMeta);
133 } catch (Throwable t){
135 logger.error("Unable to addOutputPart: "+partLabel
136 +" in serviceContextPath: "+this.getServiceContextPath()
137 +" with URI: "+this.getServiceContext().getUriInfo().getPath()
142 if (logger.isWarnEnabled() == true) {
143 logger.warn("MultipartInput part was null for document id = " +
150 * Adds the output part.
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
158 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
160 Element doc = DocumentUtils.buildDocument(partMeta, schema,
161 unQObjectProperties);
162 if (logger.isTraceEnabled() == true) {
163 logger.trace(doc.asXML());
165 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
166 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
170 * Extract paging info.
172 * @param commonsList the commons list
174 * @throws Exception the exception
176 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
178 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
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());
192 return (TL) commonList;
196 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
199 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
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
210 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
211 if(COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
212 addExtraCoreValues(docModel, unQObjectProperties);
214 addOutputPart(unQObjectProperties, schema, partMeta);
216 addAccountPermissionsPart();
219 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
221 unQObjectProperties.put(COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
224 private void addAccountPermissionsPart() throws Exception {
225 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
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 + "/";
236 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
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);
250 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
253 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
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);
267 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
269 //iterate over parts received and fill those parts
270 List<PayloadInputPart> inputParts = input.getParts();
271 for (PayloadInputPart part : inputParts) {
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);
280 //skip if the part is not in metadata
281 ObjectPartType partMeta = partsMetaMap.get(partLabel);
282 if (partMeta == null) {
285 fillPart(part, docModel, partMeta, action, ctx);
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
297 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
298 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
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);
307 docModel.setProperties(partMeta.getLabel(), objectProps);
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
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().
331 * extractPart extracts an XML object from given DocumentModel
333 * @param schema of the object to extract
334 * @param partMeta metadata for the object to extract
337 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
339 return extractPart(docModel, schema, (Map<String, Object>)null);
343 * extractPart extracts an XML object from given DocumentModel
345 * @param schema of the object to extract
346 * @param partMeta metadata for the object to extract
350 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
352 return extractPart(docModel, schema, partMeta, null);
356 * extractPart extracts an XML object from given DocumentModel
358 * @param schema of the object to extract
359 * @param partMeta metadata for the object to extract
362 protected Map<String, Object> extractPart(
363 DocumentModel docModel,
365 Map<String, Object> addToMap)
367 Map<String, Object> result = null;
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());
380 result = unQObjectProperties;
387 * extractPart extracts an XML object from given DocumentModel
389 * @param schema of the object to extract
390 * @param partMeta metadata for the object to extract
394 protected Map<String, Object> extractPart(
395 DocumentModel docModel, String schema, ObjectPartType partMeta,
396 Map<String, Object> addToMap)
398 Map<String, Object> result = null;
400 result = this.extractPart(docModel, schema, addToMap);
406 public String getStringPropertyFromDoc(
409 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
410 RepositoryInstance repoSession = null;
411 boolean releaseRepoSession = false;
412 String returnValue = null;
415 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
416 repoSession = this.getRepositorySession();
417 if (repoSession == null) {
418 repoSession = repoClient.getRepositorySession();
419 releaseRepoSession = true;
423 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
424 DocumentModel docModel = wrapper.getWrappedObject();
425 returnValue = (String) docModel.getPropertyValue(propertyXPath);
426 } catch (PropertyException pe) {
428 } catch (DocumentException de) {
430 } catch (Exception e) {
431 if (logger.isDebugEnabled()) {
432 logger.debug("Caught exception ", e);
434 throw new DocumentException(e);
436 if (releaseRepoSession && repoSession != null) {
437 repoClient.releaseRepositorySession(repoSession);
440 } catch (Exception e) {
441 if (logger.isDebugEnabled()) {
442 logger.debug("Caught exception ", e);
444 throw new DocumentException(e);
448 if (logger.isWarnEnabled() == true) {
449 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
458 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
461 public AuthorityRefList getAuthorityRefs(
463 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
465 AuthorityRefList authRefList = new AuthorityRefList();
466 AbstractCommonList commonList = (AbstractCommonList) authRefList;
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);
475 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
478 int iFirstToUse = (int)(pageSize*pageNum);
479 int nFoundInPage = 0;
482 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
483 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
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;
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)) {
509 if (releaseRepoSession == true) {
510 repoClient.releaseRepositorySession(repoSession);
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);
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);
524 } catch (Exception e) {
525 if (logger.isDebugEnabled()) {
526 logger.debug("Caught exception in getAuthorityRefs", e);
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);
538 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
539 List<AuthorityRefList.AuthorityRefItem> list)
541 String fieldName = ari.getQualifiedDisplayName();
543 String refNameValue = (String)ari.getProperty().getValue();
544 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
545 if(item!=null) { // ignore garbage values.
549 } catch(PropertyException pe) {
550 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
555 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
557 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
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());
573 * Returns the primary value from a list of values.
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.
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) {
588 Object value = values.get(0);
589 if (value instanceof String) {
591 primaryValue = (String) value;
593 // Multivalue group of fields
594 } else if (value instanceof Map) {
596 Map map = (Map) value;
597 if (map.values().size() > 0) {
598 if (map.get(propertyName) != null) {
599 primaryValue = (String) map.get(propertyName);
604 logger.warn("Unexpected type for property " + propertyName
605 + " in multivalue list: not String or Map.");
612 * Gets a simple property from the document.
614 * For completeness, as this duplicates DocumentModel method.
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
621 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
622 String xpath = "/"+schema+":"+propName;
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());
638 * Gets first of a repeating list of scalar values, as a String, from the document.
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
645 protected String getFirstRepeatingStringProperty(
646 DocumentModel docModel, String schema, String listName) {
647 String xpath = "/"+schema+":"+listName+"/[0]";
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());
667 * Gets first of a repeating list of scalar values, as a String, from the document.
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
674 protected String getStringValueInPrimaryRepeatingComplexProperty(
675 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
676 String result = null;
678 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
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());
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.
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
714 protected static String getXPathStringValue(DocumentModel docModel,
715 String schema, String xpath) {
716 String result = null;
718 xpath = schema + ":" + xpath;
720 Object value = docModel.getPropertyValue(xpath);
721 String returnVal = null;
723 // Nothing to do - leave returnVal null
724 } else if (value instanceof GregorianCalendar) {
725 returnVal = DateTimeFormatUtils.formatAsISO8601Timestamp((GregorianCalendar) value);
727 returnVal = value.toString();
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
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
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());
755 } catch (Exception e) {
756 throw new RuntimeException("Unknown problem retrieving property {"
757 + xpath + "}." + e.getLocalizedMessage());