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