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.HashMap;
28 import java.util.List;
30 import java.util.Map.Entry;
33 import javax.ws.rs.WebApplicationException;
34 import javax.ws.rs.core.MediaType;
35 import javax.ws.rs.core.Response;
36 import javax.xml.bind.JAXBElement;
38 import org.collectionspace.services.authorization.AccountPermission;
39 import org.collectionspace.services.jaxb.AbstractCommonList;
40 import org.collectionspace.services.lifecycle.TransitionDef;
41 import org.collectionspace.services.client.CollectionSpaceClient;
42 import org.collectionspace.services.client.PayloadInputPart;
43 import org.collectionspace.services.client.PayloadOutputPart;
44 import org.collectionspace.services.client.PoxPayloadIn;
45 import org.collectionspace.services.client.PoxPayloadOut;
46 import org.collectionspace.services.client.workflow.WorkflowClient;
47 import org.collectionspace.services.common.authorityref.AuthorityRefList;
48 import org.collectionspace.services.common.context.JaxRsContext;
49 import org.collectionspace.services.common.context.MultipartServiceContext;
50 import org.collectionspace.services.common.context.ServiceContext;
51 import org.collectionspace.services.common.document.BadRequestException;
52 import org.collectionspace.services.common.document.DocumentException;
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.api.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.DocHandlerParams;
63 import org.collectionspace.services.config.service.ListResultField;
64 import org.collectionspace.services.config.service.ObjectPartType;
65 import org.collectionspace.services.config.service.ServiceBindingType;
66 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
67 import org.dom4j.Element;
69 import org.nuxeo.ecm.core.api.DocumentModel;
70 import org.nuxeo.ecm.core.api.DocumentModelList;
71 import org.nuxeo.ecm.core.api.model.PropertyException;
72 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
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 * Returns the document handler parameters that were loaded at startup from the
106 * tenant bindings config file.
108 public DocHandlerParams.Params getDocHandlerParams() throws DocumentException {
109 MultipartServiceContext sc = (MultipartServiceContext) getServiceContext();
110 ServiceBindingType sb = sc.getServiceBinding();
111 DocHandlerParams dhb = sb.getDocHandlerParams();
112 if (dhb != null && dhb.getParams() != null) {
113 return dhb.getParams();
115 throw new DocumentException("No DocHandlerParams configured for: "
120 public boolean supportsHierarchy() {
123 DocHandlerParams.Params params = null;
125 params = getDocHandlerParams();
126 } catch (DocumentException e) {
127 // TODO Auto-generated catch block
128 logger.error(String.format("Could not get document handler params for class %s", this.getClass().getName()), e);
130 result = params.isSupportsHierarchy();
136 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
138 // Do nothing by default, but children can override if they want. The really workflow transition happens in the WorkflowDocumemtModelHandler class
142 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
145 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
146 DocumentModel docModel = wrapDoc.getWrappedObject();
147 //return at least those document part(s) that were received
148 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
149 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
150 PoxPayloadIn input = ctx.getInput();
152 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
153 for (PayloadInputPart part : inputParts) {
154 String partLabel = part.getLabel();
156 ObjectPartType partMeta = partsMetaMap.get(partLabel);
157 // CSPACE-4030 - generates NPE if the part is missing.
159 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
160 if(unQObjectProperties!=null) {
161 addOutputPart(unQObjectProperties, partLabel, partMeta);
164 } catch (Throwable t){
166 logger.error("Unable to addOutputPart: "+partLabel
167 +" in serviceContextPath: "+this.getServiceContextPath()
168 +" with URI: "+this.getServiceContext().getUriInfo().getPath()
173 if (logger.isWarnEnabled() == true) {
174 logger.warn("MultipartInput part was null for document id = " +
181 * Adds the output part.
183 * @param unQObjectProperties the un q object properties
184 * @param schema the schema
185 * @param partMeta the part meta
186 * @throws Exception the exception
187 * MediaType.APPLICATION_XML_TYPE
189 protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
191 Element doc = DocumentUtils.buildDocument(partMeta, schema,
192 unQObjectProperties);
193 if (logger.isTraceEnabled() == true) {
194 logger.trace(doc.asXML());
196 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
197 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
201 * Extract paging info.
203 * @param commonsList the commons list
205 * @throws Exception the exception
207 public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
209 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
211 DocumentFilter docFilter = this.getDocumentFilter();
212 long pageSize = docFilter.getPageSize();
213 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
214 // set the page size and page number
215 commonList.setPageNum(pageNum);
216 commonList.setPageSize(pageSize);
217 DocumentModelList docList = wrapDoc.getWrappedObject();
218 // Set num of items in list. this is useful to our testing framework.
219 commonList.setItemsInPage(docList.size());
220 // set the total result size
221 commonList.setTotalItems(docList.totalSize());
223 return (TL) commonList;
227 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
230 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
233 DocumentModel docModel = wrapDoc.getWrappedObject();
234 String[] schemas = docModel.getDeclaredSchemas();
235 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
236 for (String schema : schemas) {
237 ObjectPartType partMeta = partsMetaMap.get(schema);
238 if (partMeta == null) {
239 continue; // unknown part, ignore
241 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
242 if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
243 addExtraCoreValues(docModel, unQObjectProperties);
245 addOutputPart(unQObjectProperties, schema, partMeta);
247 addAccountPermissionsPart();
250 private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
252 unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
255 private void addAccountPermissionsPart() throws Exception {
256 Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
259 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
260 String currentServiceName = ctx.getServiceName();
261 String workflowSubResource = "/";
262 JaxRsContext jaxRsContext = ctx.getJaxRsContext();
263 if (jaxRsContext != null) {
264 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
265 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
267 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
269 AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
270 currentServiceName, workflowSubResource);
271 org.collectionspace.services.authorization.ObjectFactory objectFactory =
272 new org.collectionspace.services.authorization.ObjectFactory();
273 JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
274 PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap);
275 ctx.addOutputPart(accountPermissionPart);
281 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
284 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
286 //TODO filling extension parts should be dynamic
287 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
288 //not an ideal way of populating objects.
289 DocumentModel docModel = wrapDoc.getWrappedObject();
290 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
291 PoxPayloadIn input = ctx.getInput();
292 if (input.getParts().isEmpty()) {
293 String msg = "No payload found!";
294 logger.error(msg + "Ctx=" + getServiceContext().toString());
295 throw new BadRequestException(msg);
298 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
300 //iterate over parts received and fill those parts
301 List<PayloadInputPart> inputParts = input.getParts();
302 for (PayloadInputPart part : inputParts) {
304 String partLabel = part.getLabel();
305 if (partLabel == null) {
306 String msg = "Part label is missing or empty!";
307 logger.error(msg + "Ctx=" + getServiceContext().toString());
308 throw new BadRequestException(msg);
311 //skip if the part is not in metadata
312 ObjectPartType partMeta = partsMetaMap.get(partLabel);
313 if (partMeta == null) {
316 fillPart(part, docModel, partMeta, action, ctx);
322 * fillPart fills an XML part into given document model
323 * @param part to fill
324 * @param docModel for the given object
325 * @param partMeta metadata for the object to fill
328 protected void fillPart(PayloadInputPart part, DocumentModel docModel,
329 ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
331 //check if this is an xml part
332 if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
333 Element element = part.getElementBody();
334 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
335 if (action == Action.UPDATE) {
336 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
338 docModel.setProperties(partMeta.getLabel(), objectProps);
343 * Filters out read only properties, so they cannot be set on update.
344 * TODO: add configuration support to do this generally
345 * @param objectProps the properties parsed from the update payload
346 * @param partMeta metadata for the object to fill
348 public void filterReadOnlyPropertiesForPart(
349 Map<String, Object> objectProps, ObjectPartType partMeta) {
350 // Should add in logic to filter most of the core items on update
351 if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
352 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
353 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
354 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
355 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
356 // Note that the updatedAt/updatedBy fields are set internally
357 // in DocumentModelHandler.handleCoreValues().
362 * extractPart extracts an XML object from given DocumentModel
364 * @param schema of the object to extract
365 * @param partMeta metadata for the object to extract
368 protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
370 return extractPart(docModel, schema, (Map<String, Object>)null);
374 * extractPart extracts an XML object from given DocumentModel
376 * @param schema of the object to extract
377 * @param partMeta metadata for the object to extract
381 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
383 return extractPart(docModel, schema, partMeta, null);
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
393 protected Map<String, Object> extractPart(
394 DocumentModel docModel,
396 Map<String, Object> addToMap)
398 Map<String, Object> result = null;
400 Map<String, Object> objectProps = docModel.getProperties(schema);
401 if (objectProps != null) {
402 //unqualify properties before sending the doc over the wire (to save bandwidh)
403 //FIXME: is there a better way to avoid duplication of a Map/Collection?
404 Map<String, Object> unQObjectProperties =
405 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
406 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
407 for (Entry<String, Object> entry : qualifiedEntries) {
408 String unqProp = getUnQProperty(entry.getKey());
409 unQObjectProperties.put(unqProp, entry.getValue());
411 result = unQObjectProperties;
418 * extractPart extracts an XML object from given DocumentModel
420 * @param schema of the object to extract
421 * @param partMeta metadata for the object to extract
425 protected Map<String, Object> extractPart(
426 DocumentModel docModel, String schema, ObjectPartType partMeta,
427 Map<String, Object> addToMap)
429 Map<String, Object> result = null;
431 result = this.extractPart(docModel, schema, addToMap);
437 public String getStringPropertyFromDoc(
440 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
441 RepositoryInstance repoSession = null;
442 boolean releaseRepoSession = false;
443 String returnValue = null;
446 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
447 repoSession = this.getRepositorySession();
448 if (repoSession == null) {
449 repoSession = repoClient.getRepositorySession();
450 releaseRepoSession = true;
454 DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
455 DocumentModel docModel = wrapper.getWrappedObject();
456 returnValue = (String) docModel.getPropertyValue(propertyXPath);
457 } catch (PropertyException pe) {
459 } catch (DocumentException de) {
461 } catch (Exception e) {
462 if (logger.isDebugEnabled()) {
463 logger.debug("Caught exception ", e);
465 throw new DocumentException(e);
467 if (releaseRepoSession && repoSession != null) {
468 repoClient.releaseRepositorySession(repoSession);
471 } catch (Exception e) {
472 if (logger.isDebugEnabled()) {
473 logger.debug("Caught exception ", e);
475 throw new DocumentException(e);
479 if (logger.isWarnEnabled() == true) {
480 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
489 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
492 public AuthorityRefList getAuthorityRefs(
494 List<AuthRefConfigInfo> authRefsInfo) throws PropertyException {
496 AuthorityRefList authRefList = new AuthorityRefList();
497 AbstractCommonList commonList = (AbstractCommonList) authRefList;
499 DocumentFilter docFilter = this.getDocumentFilter();
500 long pageSize = docFilter.getPageSize();
501 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
502 // set the page size and page number
503 commonList.setPageNum(pageNum);
504 commonList.setPageSize(pageSize);
506 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
509 int iFirstToUse = (int)(pageSize*pageNum);
510 int nFoundInPage = 0;
513 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps
514 = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
516 boolean releaseRepoSession = false;
517 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
518 RepositoryJavaClientImpl repoClient = (RepositoryJavaClientImpl)this.getRepositoryClient(ctx);
519 RepositoryInstance repoSession = this.getRepositorySession();
520 if (repoSession == null) {
521 repoSession = repoClient.getRepositorySession(ctx);
522 releaseRepoSession = true;
526 DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
527 RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
528 // Slightly goofy pagination support - how many refs do we expect from one object?
529 for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
530 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
531 if(appendToAuthRefsList(ari, list)) {
540 if (releaseRepoSession == true) {
541 repoClient.releaseRepositorySession(ctx, repoSession);
545 // Set num of items in list. this is useful to our testing framework.
546 commonList.setItemsInPage(nFoundInPage);
547 // set the total result size
548 commonList.setTotalItems(nFoundTotal);
550 } catch (PropertyException pe) {
551 String msg = "Attempted to retrieve value for invalid or missing authority field. "
552 + "Check authority field properties in tenant bindings.";
553 logger.warn(msg, pe);
555 } catch (Exception e) {
556 if (logger.isDebugEnabled()) {
557 logger.debug("Caught exception in getAuthorityRefs", e);
559 Response response = Response.status(
560 Response.Status.INTERNAL_SERVER_ERROR).entity(
561 "Failed to retrieve authority references").type(
562 "text/plain").build();
563 throw new WebApplicationException(response);
569 private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari,
570 List<AuthorityRefList.AuthorityRefItem> list)
572 String fieldName = ari.getQualifiedDisplayName();
574 String refNameValue = (String)ari.getProperty().getValue();
575 AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
576 if(item!=null) { // ignore garbage values.
580 } catch(PropertyException pe) {
581 logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
586 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
588 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
590 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
591 ilistItem.setRefName(refName);
592 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
593 ilistItem.setItemDisplayName(termInfo.displayName);
594 ilistItem.setSourceField(authRefFieldName);
595 ilistItem.setUri(termInfo.getRelativeUri());
596 } catch (Exception e) {
597 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
604 * Returns the primary value from a list of values.
606 * Assumes that the first value is the primary value.
607 * This assumption may change when and if the primary value
608 * is identified explicitly.
610 * @param values a list of values.
611 * @param propertyName the name of a property through
612 * which the value can be extracted.
613 * @return the primary value.
614 protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
615 String primaryValue = "";
616 if (values == null || values.size() == 0) {
619 Object value = values.get(0);
620 if (value instanceof String) {
622 primaryValue = (String) value;
624 // Multivalue group of fields
625 } else if (value instanceof Map) {
627 Map map = (Map) value;
628 if (map.values().size() > 0) {
629 if (map.get(propertyName) != null) {
630 primaryValue = (String) map.get(propertyName);
635 logger.warn("Unexpected type for property " + propertyName
636 + " in multivalue list: not String or Map.");
643 * Gets a simple property from the document.
645 * For completeness, as this duplicates DocumentModel method.
647 * @param docModel The document model to get info from
648 * @param schema The name of the schema (part)
649 * @param propertyName The simple scalar property type
650 * @return property value as String
652 protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
653 String xpath = "/"+schema+":"+propName;
655 return (String)docModel.getPropertyValue(xpath);
656 } catch(PropertyException pe) {
657 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
658 +pe.getLocalizedMessage());
659 } catch(ClassCastException cce) {
660 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
661 +cce.getLocalizedMessage());
662 } catch(Exception e) {
663 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
664 +e.getLocalizedMessage());
669 * Gets first of a repeating list of scalar values, as a String, from the document.
671 * @param docModel The document model to get info from
672 * @param schema The name of the schema (part)
673 * @param listName The name of the scalar list property
674 * @return first value in list, as a String, or empty string if the list is empty
676 protected String getFirstRepeatingStringProperty(
677 DocumentModel docModel, String schema, String listName) {
678 String xpath = "/"+schema+":"+listName+"/[0]";
680 return (String)docModel.getPropertyValue(xpath);
681 } catch(PropertyException pe) {
682 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a repeating scalar?"
683 +pe.getLocalizedMessage());
684 } catch(IndexOutOfBoundsException ioobe) {
685 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
686 return ""; // gracefully handle missing elements
687 } catch(ClassCastException cce) {
688 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a repeating String property?"
689 +cce.getLocalizedMessage());
690 } catch(Exception e) {
691 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
692 +e.getLocalizedMessage());
698 * Gets first of a repeating list of scalar values, as a String, from the document.
700 * @param docModel The document model to get info from
701 * @param schema The name of the schema (part)
702 * @param listName The name of the scalar list property
703 * @return first value in list, as a String, or empty string if the list is empty
705 protected String getStringValueInPrimaryRepeatingComplexProperty(
706 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {
707 String result = null;
709 String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
711 result = (String)docModel.getPropertyValue(xpath);
712 } catch(PropertyException pe) {
713 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
714 +pe.getLocalizedMessage());
715 } catch(IndexOutOfBoundsException ioobe) {
716 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
717 result = ""; // gracefully handle missing elements
718 } catch(ClassCastException cce) {
719 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
720 +cce.getLocalizedMessage());
721 } catch(Exception e) {
722 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
723 +e.getLocalizedMessage());
730 * Gets XPath value from schema. Note that only "/" and "[n]" are
731 * supported for xpath. Can omit grouping elements for repeating complex types,
732 * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
733 * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
734 * If there are no entries for a list of scalars or for a list of complex types,
735 * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
736 * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
737 * that many elements in the list.
738 * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
740 * @param docModel The document model to get info from
741 * @param schema The name of the schema (part)
742 * @param xpath The XPath expression (without schema prefix)
743 * @return value the indicated property value as a String
745 protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
746 String schema, ListResultField field) {
747 Object result = null;
749 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());