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