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:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
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.
24 package org.collectionspace.services.nuxeo.client.java;
26 import java.io.InputStream;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
31 import java.util.Map.Entry;
34 import javax.ws.rs.WebApplicationException;
35 import javax.ws.rs.core.MediaType;
36 import javax.ws.rs.core.Response;
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;
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;
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;
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;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72 import org.w3c.dom.Document;
75 * RemoteDocumentModelHandler
77 * $LastChangedRevision: $
82 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
83 extends DocumentModelHandler<T, TL> {
86 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
89 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
92 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<MultipartInput, MultipartOutput>
93 if (ctx instanceof MultipartServiceContext) {
94 super.setServiceContext(ctx);
96 throw new IllegalArgumentException("setServiceContext requires instance of "
97 + MultipartServiceContext.class.getName());
102 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
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);
121 * Adds the output part.
123 * @param unQObjectProperties the un q object properties
124 * @param schema the schema
125 * @param partMeta the part meta
126 * @throws Exception the exception
128 private void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
130 Document doc = DocumentUtils.buildDocument(partMeta, schema,
131 unQObjectProperties);
132 if (logger.isDebugEnabled() == true) {
133 logger.debug(DocumentUtils.xmlToString(doc));
135 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
136 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
140 * Extract paging info.
142 * @param commonsList the commons list
144 * @throws Exception the exception
146 protected TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
148 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
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());
162 return (TL) commonList;
166 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
169 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
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
180 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
181 addOutputPart(unQObjectProperties, schema, partMeta);
186 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
189 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
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);
203 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
205 //iterate over parts received and fill those parts
206 List<InputPart> inputParts = input.getParts();
207 for (InputPart part : inputParts) {
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);
216 //skip if the part is not in metadata
217 ObjectPartType partMeta = partsMetaMap.get(partLabel);
218 if (partMeta == null) {
221 fillPart(part, docModel, partMeta, action);
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
233 protected void fillPart(InputPart part, DocumentModel docModel, ObjectPartType partMeta, Action action)
235 InputStream payload = part.getBody(InputStream.class, null);
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.
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
247 Map<String, Object> objectProps = DocumentUtils.parseProperties(document.getFirstChild());
248 if (action == Action.UPDATE) {
249 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
251 docModel.setProperties(partMeta.getLabel(), objectProps);
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
262 public void filterReadOnlyPropertiesForPart(
263 Map<String, Object> objectProps, ObjectPartType partMeta) {
264 // Currently a no-op, but can be overridden in Doc handlers.
268 * extractPart extracts an XML object from given DocumentModel
270 * @param schema of the object to extract
271 * @param partMeta metadata for the object to extract
274 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
276 return extractPart(docModel, schema, partMeta, null);
280 * extractPart extracts an XML object from given DocumentModel
282 * @param schema of the object to extract
283 * @param partMeta metadata for the object to extract
286 protected Map<String, Object> extractPart(
287 DocumentModel docModel, String schema, ObjectPartType partMeta,
288 Map<String, Object> addToMap)
290 Map<String, Object> result = null;
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());
304 result = unQObjectProperties;
305 } //TODO: handle other media types
311 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
314 public AuthorityRefList getAuthorityRefs(
315 DocumentWrapper<DocumentModel> docWrapper,
316 List<String> authRefFieldNames) throws PropertyException {
318 final String SCHEMA_FIELD_DELIMITER = ":";
319 AuthorityRefList authRefList = new AuthorityRefList();
320 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
324 DocumentModel docModel = docWrapper.getWrappedObject();
326 for (String authRefFieldName : authRefFieldNames) {
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];
339 Schema schema = DocumentUtils.getSchema(schemaName);
340 Field field = schema.getField(authRefFieldName);
341 Type type = field.getType();
343 if (type.isSimpleType()) {
344 Object obj = docModel.getPropertyValue(authRefFieldName);
346 refName = (String) obj;
347 if (refName != null || ! refName.trim().isEmpty()) {
348 list.add(authorityRefListItem(authRefFieldName, refName));
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
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) {
373 refName = (String) obj;
374 if (refName != null || ! refName.trim().isEmpty()) {
375 list.add(authorityRefListItem(childAuthRefFieldName, refName));
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);
388 } catch (Exception e) {
389 if (logger.isDebugEnabled()) {
390 logger.debug("Caught exception in getAuthorityRefs", e);
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);
401 public AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
403 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
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) {