]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
ed91ad635dc37ab453ec71f75679dd36783aba5e
[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         final String SCHEMA_FIELD_DELIMITER = ":";
319         AuthorityRefList authRefList = new AuthorityRefList();
320         List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
321         String refName = "";
322
323         try {
324             DocumentModel docModel = docWrapper.getWrappedObject();
325
326             for (String authRefFieldName : authRefFieldNames) {
327
328                 String schemaName = "";
329                 // FIXME: Replacing the following by an existing utility
330                 // method or, if not already present, create a new utility
331                 // method for this task in the common package.
332                 if (authRefFieldName.indexOf(SCHEMA_FIELD_DELIMITER) > 0) {
333                     String[] authRefFieldNameParts =
334                             authRefFieldName.split(SCHEMA_FIELD_DELIMITER);
335                     schemaName = authRefFieldNameParts[0];
336                     authRefFieldName = authRefFieldNameParts[1];
337                 }
338
339                 Schema schema = DocumentUtils.getSchema(schemaName);
340                 Field field = schema.getField(authRefFieldName);
341                 Type type = field.getType();
342
343                 if (type.isSimpleType()) {
344                     Object obj = docModel.getPropertyValue(authRefFieldName);
345                     if (obj != null) {
346                         refName = (String) obj;
347                         if (refName != null || ! refName.trim().isEmpty()) {
348                             list.add(authorityRefListItem(authRefFieldName, refName));
349                         }
350                     }
351                 // FIXME: The following assumes a very simple structure
352                 // for repeatable single scalar fields: a parent (continer)
353                 // element, containing 0-n child elements, each of the same
354                 // name and type, with values capable of being meaningfully
355                 // cast to String.
356                 //
357                 // Past release 1.0a, repeatability may consist
358                 // of arbitrary nesting and complexity, rather than
359                 // scalars and single-level lists.  When needed, that
360                 // might be implemented here via recursion through
361                 // nested listTypes and/or complexTypes.
362                 } else if (type.isListType()) {
363                     // Get the name of the child field that comprises
364                     // value instances of the parent (container) field.
365                     ListType ltype = (ListType) type;
366                     field = ltype.getField();
367                     String childAuthRefFieldName = field.getName().getLocalName();
368                     // For each value instance, add its refName to the authRefs list,
369                     // with its source field name set to the child field's name.
370                     List<Object> valuesList = (List<Object>) docModel.getPropertyValue(authRefFieldName);
371                     for (Object obj : valuesList) {
372                         if (obj != null) {
373                             refName = (String) obj;
374                             if (refName != null || ! refName.trim().isEmpty()) {
375                                 list.add(authorityRefListItem(childAuthRefFieldName, refName));
376                             }
377                         }
378                     }
379                 }
380
381             }
382
383         } catch  (PropertyException pe) {
384             String msg = "Attempted to retrieve value for invalid or missing authority field. "
385                     + "Check authority field properties in tenant bindings.";
386             logger.warn(msg, pe);
387             throw pe;
388         } catch (Exception e) {
389             if (logger.isDebugEnabled()) {
390                 logger.debug("Caught exception in getAuthorityRefs", e);
391             }
392             Response response = Response.status(
393                     Response.Status.INTERNAL_SERVER_ERROR).entity(
394                     "Failed to retrieve authority references").type(
395                     "text/plain").build();
396             throw new WebApplicationException(response);
397         }
398         return authRefList;
399     }
400
401         public AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
402
403             AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
404             try {
405                 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
406                 ilistItem.setRefName(refName);
407                 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
408                 ilistItem.setItemDisplayName(termInfo.displayName);
409                 ilistItem.setSourceField(authRefFieldName);
410                 ilistItem.setUri(termInfo.getRelativeUri());
411             } catch (Exception e) {
412             }
413             return ilistItem;
414         }
415 }