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