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