]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
b5d5947a36d26034556dc52620256f9fda1167fe
[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.jaxb.AbstractCommonList;
39 import org.collectionspace.services.common.authorityref.AuthorityRefList;
40 import org.collectionspace.services.common.context.MultipartServiceContext;
41 import org.collectionspace.services.common.context.ServiceContext;
42 import org.collectionspace.services.common.document.BadRequestException;
43 import org.collectionspace.services.common.document.DocumentUtils;
44 import org.collectionspace.services.common.document.DocumentWrapper;
45 import org.collectionspace.services.common.document.DocumentFilter;
46 import org.collectionspace.services.common.document.DocumentHandler.Action;
47 import org.collectionspace.services.common.service.ObjectPartType;
48 import org.collectionspace.services.common.vocabulary.RefNameUtils;
49
50 import org.jboss.resteasy.plugins.providers.multipart.InputPart;
51 import org.jboss.resteasy.plugins.providers.multipart.MultipartInput;
52
53 import org.nuxeo.ecm.core.api.DocumentModel;
54 import org.nuxeo.ecm.core.api.DocumentModelList;
55 import org.nuxeo.ecm.core.api.model.Property;
56 import org.nuxeo.ecm.core.api.model.PropertyException;
57
58 import org.nuxeo.ecm.core.schema.types.Schema;
59
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62 import org.w3c.dom.Document;
63
64 /**
65  * RemoteDocumentModelHandler
66  *
67  * $LastChangedRevision: $
68  * $LastChangedDate: $
69  * @param <T> 
70  * @param <TL> 
71  */
72 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
73         extends DocumentModelHandler<T, TL> {
74
75     /** The logger. */
76     private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
77
78     /* (non-Javadoc)
79      * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
80      */
81     @Override
82     public void setServiceContext(ServiceContext ctx) {  //FIXME: Apply proper generics to ServiceContext<MultipartInput, MultipartOutput>
83         if (ctx instanceof MultipartServiceContext) {
84             super.setServiceContext(ctx);
85         } else {
86             throw new IllegalArgumentException("setServiceContext requires instance of "
87                     + MultipartServiceContext.class.getName());
88         }
89     }
90
91     /* (non-Javadoc)
92      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
93      */
94     @Override
95     public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
96         DocumentModel docModel = wrapDoc.getWrappedObject();
97         //return at least those document part(s) that were received
98         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
99         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
100         List<InputPart> inputParts = ctx.getInput().getParts();
101         for (InputPart part : inputParts) {
102             String partLabel = part.getHeaders().getFirst("label");
103             ObjectPartType partMeta = partsMetaMap.get(partLabel);
104 //            extractPart(docModel, partLabel, partMeta);
105             Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
106             addOutputPart(unQObjectProperties, partLabel, partMeta);
107         }
108     }
109
110     /**
111      * Adds the output part.
112      *
113      * @param unQObjectProperties the un q object properties
114      * @param schema the schema
115      * @param partMeta the part meta
116      * @throws Exception the exception
117      */
118     private void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
119             throws Exception {
120         Document doc = DocumentUtils.buildDocument(partMeta, schema,
121                 unQObjectProperties);
122         if (logger.isDebugEnabled() == true) {
123             logger.debug(DocumentUtils.xmlToString(doc));
124         }
125         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
126         ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
127     }
128
129     /**
130      * Extract paging info.
131      *
132      * @param commonsList the commons list
133      * @return the tL
134      * @throws Exception the exception
135      */
136     protected TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
137             throws Exception {
138         AbstractCommonList commonList = (AbstractCommonList) theCommonList;
139
140         DocumentFilter docFilter = this.getDocumentFilter();
141         long pageSize = docFilter.getPageSize();
142         long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
143         // set the page size and page number
144         commonList.setPageNum(pageNum);
145         commonList.setPageSize(pageSize);
146         DocumentModelList docList = wrapDoc.getWrappedObject();
147         // Set num of items in list. this is useful to our testing framework.
148         commonList.setItemsInPage(docList.size());
149         // set the total result size
150         commonList.setTotalItems(docList.totalSize());
151
152         return (TL) commonList;
153     }
154
155     /* (non-Javadoc)
156      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
157      */
158     @Override
159     public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
160             throws Exception {
161
162         DocumentModel docModel = wrapDoc.getWrappedObject();
163         String[] schemas = docModel.getDeclaredSchemas();
164         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
165         for (String schema : schemas) {
166             ObjectPartType partMeta = partsMetaMap.get(schema);
167             if (partMeta == null) {
168                 continue; // unknown part, ignore
169             }
170             Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
171             addOutputPart(unQObjectProperties, schema, partMeta);
172         }
173     }
174
175     /* (non-Javadoc)
176      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
177      */
178     @Override
179     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
180
181         //TODO filling extension parts should be dynamic
182         //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
183         //not an ideal way of populating objects.
184         DocumentModel docModel = wrapDoc.getWrappedObject();
185         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
186         MultipartInput input = ctx.getInput();
187         if (input.getParts().isEmpty()) {
188             String msg = "No payload found!";
189             logger.error(msg + "Ctx=" + getServiceContext().toString());
190             throw new BadRequestException(msg);
191         }
192
193         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
194
195         //iterate over parts received and fill those parts
196         List<InputPart> inputParts = input.getParts();
197         for (InputPart part : inputParts) {
198
199             String partLabel = part.getHeaders().getFirst("label");
200             if (partLabel == null) {
201                 String msg = "Part label is missing or empty!";
202                 logger.error(msg + "Ctx=" + getServiceContext().toString());
203                 throw new BadRequestException(msg);
204             }
205
206             //skip if the part is not in metadata
207             ObjectPartType partMeta = partsMetaMap.get(partLabel);
208             if (partMeta == null) {
209                 continue;
210             }
211             fillPart(part, docModel, partMeta, action, ctx);
212         }//rof
213
214     }
215
216     /**
217      * fillPart fills an XML part into given document model
218      * @param part to fill
219      * @param docModel for the given object
220      * @param partMeta metadata for the object to fill
221      * @throws Exception
222      */
223     protected void fillPart(InputPart part, DocumentModel docModel,
224             ObjectPartType partMeta, Action action, ServiceContext ctx)
225             throws Exception {
226         InputStream payload = part.getBody(InputStream.class, null);
227
228 // TODO for sub-docs - after we parse the doc, we need to look for elements that are configured as 
229 // subitem lists, for this part (schema), pull them out, and set them aside for later processing.
230
231         //check if this is an xml part
232         if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
233             if (payload != null) {
234                 Document document = DocumentUtils.parseDocument(payload, partMeta,
235                         false /*don't validate*/);
236                 //TODO: callback to handler if registered to validate the
237                 //document
238 //                Map<String, Object> objectProps = DocumentUtils.parseProperties(document.getFirstChild());
239                 Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, document, ctx);
240                 if (action == Action.UPDATE) {
241                     this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
242                 }
243                 docModel.setProperties(partMeta.getLabel(), objectProps);
244             }
245         }
246     }
247
248     /**
249      * Filters out read only properties, so they cannot be set on update.
250      * TODO: add configuration support to do this generally
251      * @param objectProps the properties parsed from the update payload
252      * @param partMeta metadata for the object to fill
253      */
254     public void filterReadOnlyPropertiesForPart(
255             Map<String, Object> objectProps, ObjectPartType partMeta) {
256         // Currently a no-op, but can be overridden in Doc handlers.
257     }
258
259     /**
260      * extractPart extracts an XML object from given DocumentModel
261      * @param docModel
262      * @param schema of the object to extract
263      * @param partMeta metadata for the object to extract
264      * @throws Exception
265      */
266     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
267             throws Exception {
268         return extractPart(docModel, schema, partMeta, null);
269     }
270
271     /**
272      * extractPart extracts an XML object from given DocumentModel
273      * @param docModel
274      * @param schema of the object to extract
275      * @param partMeta metadata for the object to extract
276      * @throws Exception
277      */
278     protected Map<String, Object> extractPart(
279             DocumentModel docModel, String schema, ObjectPartType partMeta,
280             Map<String, Object> addToMap)
281             throws Exception {
282         Map<String, Object> result = null;
283
284         MediaType mt = MediaType.valueOf(partMeta.getContent().getContentType());
285         if (mt.equals(MediaType.APPLICATION_XML_TYPE)) {
286             Map<String, Object> objectProps = docModel.getProperties(schema);
287             //unqualify properties before sending the doc over the wire (to save bandwidh)
288             //FIXME: is there a better way to avoid duplication of a collection?
289             Map<String, Object> unQObjectProperties =
290                     (addToMap != null) ? addToMap : (new HashMap<String, Object>());
291             Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
292             for (Entry<String, Object> entry : qualifiedEntries) {
293                 String unqProp = getUnQProperty(entry.getKey());
294                 unQObjectProperties.put(unqProp, entry.getValue());
295             }
296             result = unQObjectProperties;
297         } //TODO: handle other media types
298
299         return result;
300     }
301
302     /* (non-Javadoc)
303      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
304      */
305     @Override
306     public AuthorityRefList getAuthorityRefs(
307             DocumentWrapper<DocumentModel> docWrapper,
308             List<String> authRefFieldNames) throws PropertyException {
309
310         AuthorityRefList authRefList = new AuthorityRefList();
311         List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
312         DocumentModel docModel = docWrapper.getWrappedObject();
313
314         try {
315             for (String authRefFieldName : authRefFieldNames) {
316
317                 // FIXME: Can use the schema to validate field existence,
318                 // to help avoid encountering PropertyExceptions.
319                 String schemaName = DocumentUtils.getSchemaNamePart(authRefFieldName);
320                 Schema schema = DocumentUtils.getSchemaFromName(schemaName);
321
322                 String descendantAuthRefFieldName = DocumentUtils.getDescendantAuthRefFieldName(authRefFieldName);
323                 if (descendantAuthRefFieldName != null && !descendantAuthRefFieldName.trim().isEmpty()) {
324                     authRefFieldName = DocumentUtils.getAncestorAuthRefFieldName(authRefFieldName);
325                 }
326
327                 String xpath = "//" + authRefFieldName;
328                 Property prop = docModel.getProperty(xpath);
329                 if (prop == null) {
330                     continue;
331                 }
332
333                 // If this is a single scalar field, with no children,
334                 // add an item with its values to the authRefs list.
335                 if (DocumentUtils.isSimpleType(prop)) {
336                     appendToAuthRefsList(prop.getValue(String.class), schemaName, authRefFieldName, list);
337
338                     // Otherwise, if this field has children, cycle through each child.
339                     //
340                     // Whenever we find instances of the descendant field among
341                     // these children, add an item with its values to the authRefs list.
342                     //
343                     // FIXME: When we increase maximum repeatability depth, that is, the depth
344                     // between ancestor and descendant, we'll need to use recursion here,
345                     // rather than making fixed assumptions about hierarchical depth.
346                 } else if ((DocumentUtils.isListType(prop) || DocumentUtils.isComplexType(prop))
347                         && prop.size() > 0) {
348                     
349                     Collection<Property> childProp = prop.getChildren();
350                     for (Property cProp : childProp) {
351                         if (DocumentUtils.isSimpleType(cProp) && cProp.getName().equals(descendantAuthRefFieldName)) {
352                             appendToAuthRefsList(cProp.getValue(String.class), schemaName, descendantAuthRefFieldName, list);
353                         } else if ((DocumentUtils.isListType(cProp) || DocumentUtils.isComplexType(cProp))
354                             && prop.size() > 0) {
355                             Collection<Property> grandChildProp = cProp.getChildren();
356                             for (Property gProp : grandChildProp) {
357                                 if (DocumentUtils.isSimpleType(gProp) && gProp.getName().equals(descendantAuthRefFieldName)) {
358                                     appendToAuthRefsList(gProp.getValue(String.class), schemaName, descendantAuthRefFieldName, list);
359                                 }
360                             }
361                         }
362                     }
363
364                 }
365
366             }
367
368             
369         } catch (PropertyException pe) {
370             String msg = "Attempted to retrieve value for invalid or missing authority field. "
371                     + "Check authority field properties in tenant bindings.";
372             logger.warn(msg, pe);
373             throw pe;
374         } catch (Exception e) {
375             if (logger.isDebugEnabled()) {
376                 logger.debug("Caught exception in getAuthorityRefs", e);
377             }
378             Response response = Response.status(
379                     Response.Status.INTERNAL_SERVER_ERROR).entity(
380                     "Failed to retrieve authority references").type(
381                     "text/plain").build();
382             throw new WebApplicationException(response);
383         }
384
385         return authRefList;
386     }
387
388     private void appendToAuthRefsList(String refName, String schemaName,
389             String fieldName, List<AuthorityRefList.AuthorityRefItem> list)
390             throws Exception {
391         if (refName == null || refName.trim().isEmpty()) {
392             return;
393         }
394         if (DocumentUtils.getSchemaNamePart(fieldName).isEmpty()) {
395             fieldName = DocumentUtils.appendSchemaName(schemaName, fieldName);
396         }
397         list.add(authorityRefListItem(fieldName, refName));
398     }
399
400     private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
401
402         AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
403         try {
404             RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
405             ilistItem.setRefName(refName);
406             ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
407             ilistItem.setItemDisplayName(termInfo.displayName);
408             ilistItem.setSourceField(authRefFieldName);
409             ilistItem.setUri(termInfo.getRelativeUri());
410         } catch (Exception e) {
411             // Do nothing upon encountering an Exception here.
412         }
413         return ilistItem;
414     }
415
416 }