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