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