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.Collection;
28 import java.util.Iterator;
29 import java.util.HashMap;
30 import java.util.List;
32 import java.util.Map.Entry;
35 import javax.ws.rs.WebApplicationException;
36 import javax.ws.rs.core.MediaType;
37 import javax.ws.rs.core.Response;
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;
51 import org.jboss.resteasy.plugins.providers.multipart.InputPart;
52 import org.jboss.resteasy.plugins.providers.multipart.MultipartInput;
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;
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;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67 import org.w3c.dom.Document;
70 * RemoteDocumentModelHandler
72 * $LastChangedRevision: $
77 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
78 extends DocumentModelHandler<T, TL> {
81 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
84 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
87 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<MultipartInput, MultipartOutput>
88 if (ctx instanceof MultipartServiceContext) {
89 super.setServiceContext(ctx);
91 throw new IllegalArgumentException("setServiceContext requires instance of "
92 + MultipartServiceContext.class.getName());
97 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
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);
116 * Adds the output part.
118 * @param unQObjectProperties the un q object properties
119 * @param schema the schema
120 * @param partMeta the part meta
121 * @throws Exception the exception
123 private void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
125 Document doc = DocumentUtils.buildDocument(partMeta, schema,
126 unQObjectProperties);
127 if (logger.isDebugEnabled() == true) {
128 logger.debug(DocumentUtils.xmlToString(doc));
130 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
131 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
135 * Extract paging info.
137 * @param commonsList the commons list
139 * @throws Exception the exception
141 protected TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
143 AbstractCommonList commonList = (AbstractCommonList) theCommonList;
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());
157 return (TL) commonList;
161 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
164 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
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
175 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
176 addOutputPart(unQObjectProperties, schema, partMeta);
181 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
184 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
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);
198 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
200 //iterate over parts received and fill those parts
201 List<InputPart> inputParts = input.getParts();
202 for (InputPart part : inputParts) {
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);
211 //skip if the part is not in metadata
212 ObjectPartType partMeta = partsMetaMap.get(partLabel);
213 if (partMeta == null) {
216 fillPart(part, docModel, partMeta, action);
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
228 protected void fillPart(InputPart part, DocumentModel docModel, ObjectPartType partMeta, Action action)
230 InputStream payload = part.getBody(InputStream.class, null);
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.
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
242 Map<String, Object> objectProps = DocumentUtils.parseProperties(document.getFirstChild());
243 if (action == Action.UPDATE) {
244 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
246 docModel.setProperties(partMeta.getLabel(), objectProps);
252 * Filters out read only properties, so they cannot be set on update.
253 * TODO: add configuration support to do this generally
254 * @param objectProps the properties parsed from the update payload
255 * @param partMeta metadata for the object to fill
257 public void filterReadOnlyPropertiesForPart(
258 Map<String, Object> objectProps, ObjectPartType partMeta) {
259 // Currently a no-op, but can be overridden in Doc handlers.
263 * extractPart extracts an XML object from given DocumentModel
265 * @param schema of the object to extract
266 * @param partMeta metadata for the object to extract
269 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
271 return extractPart(docModel, schema, partMeta, null);
275 * extractPart extracts an XML object from given DocumentModel
277 * @param schema of the object to extract
278 * @param partMeta metadata for the object to extract
281 protected Map<String, Object> extractPart(
282 DocumentModel docModel, String schema, ObjectPartType partMeta,
283 Map<String, Object> addToMap)
285 Map<String, Object> result = null;
287 MediaType mt = MediaType.valueOf(partMeta.getContent().getContentType());
288 if (mt.equals(MediaType.APPLICATION_XML_TYPE)) {
289 Map<String, Object> objectProps = docModel.getProperties(schema);
290 //unqualify properties before sending the doc over the wire (to save bandwidh)
291 //FIXME: is there a better way to avoid duplication of a collection?
292 Map<String, Object> unQObjectProperties =
293 (addToMap != null) ? addToMap : (new HashMap<String, Object>());
294 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
295 for (Entry<String, Object> entry : qualifiedEntries) {
296 String unqProp = getUnQProperty(entry.getKey());
297 unQObjectProperties.put(unqProp, entry.getValue());
299 result = unQObjectProperties;
300 } //TODO: handle other media types
306 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
309 public AuthorityRefList getAuthorityRefs(
310 DocumentWrapper<DocumentModel> docWrapper,
311 List<String> authRefFieldNames) throws PropertyException {
313 AuthorityRefList authRefList = new AuthorityRefList();
314 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
315 DocumentModel docModel = docWrapper.getWrappedObject();
318 for (String authRefFieldName : authRefFieldNames) {
320 // FIXME: Can use the schema to validate field existence,
321 // to help avoid encountering PropertyExceptions.
322 String schemaName = DocumentUtils.getSchemaNamePart(authRefFieldName);
323 Schema schema = DocumentUtils.getSchemaFromName(schemaName);
325 String descendantAuthRefFieldName = DocumentUtils.getDescendantAuthRefFieldName(authRefFieldName);
326 if (descendantAuthRefFieldName != null && !descendantAuthRefFieldName.trim().isEmpty()) {
327 authRefFieldName = DocumentUtils.getAncestorAuthRefFieldName(authRefFieldName);
330 String xpath = "//" + authRefFieldName;
331 Property prop = docModel.getProperty(xpath);
336 // If this is a single scalar field, with no children,
337 // add an item with its values to the authRefs list.
338 if (DocumentUtils.isSimpleType(prop)) {
339 appendToAuthRefsList(prop.getValue(String.class), schemaName, authRefFieldName, list);
341 // Otherwise, if this field has children, cycle through each child.
343 // Whenever we find instances of the descendant field among
344 // these children, add an item with its values to the authRefs list.
346 // FIXME: When we increase maximum repeatability depth, that is, the depth
347 // between ancestor and descendant, we'll need to use recursion here,
348 // rather than making fixed assumptions about hierarchical depth.
349 } else if ((DocumentUtils.isListType(prop) || DocumentUtils.isComplexType(prop))
350 && prop.size() > 0) {
352 Collection<Property> childProp = prop.getChildren();
353 for (Property cProp : childProp) {
354 if (DocumentUtils.isSimpleType(cProp) && cProp.getName().equals(descendantAuthRefFieldName)) {
355 appendToAuthRefsList(cProp.getValue(String.class), schemaName, descendantAuthRefFieldName, list);
356 } else if ((DocumentUtils.isListType(cProp) || DocumentUtils.isComplexType(cProp))
357 && prop.size() > 0) {
358 Collection<Property> grandChildProp = cProp.getChildren();
359 for (Property gProp : grandChildProp) {
360 if (DocumentUtils.isSimpleType(gProp) && gProp.getName().equals(descendantAuthRefFieldName)) {
361 appendToAuthRefsList(gProp.getValue(String.class), schemaName, descendantAuthRefFieldName, list);
372 } catch (PropertyException pe) {
373 String msg = "Attempted to retrieve value for invalid or missing authority field. "
374 + "Check authority field properties in tenant bindings.";
375 logger.warn(msg, pe);
377 } catch (Exception e) {
378 if (logger.isDebugEnabled()) {
379 logger.debug("Caught exception in getAuthorityRefs", e);
381 Response response = Response.status(
382 Response.Status.INTERNAL_SERVER_ERROR).entity(
383 "Failed to retrieve authority references").type(
384 "text/plain").build();
385 throw new WebApplicationException(response);
391 private void appendToAuthRefsList(String refName, String schemaName,
392 String fieldName, List<AuthorityRefList.AuthorityRefItem> list)
394 if (refName == null || refName.trim().isEmpty()) {
397 if (DocumentUtils.getSchemaNamePart(fieldName).isEmpty()) {
398 fieldName = DocumentUtils.appendSchemaName(schemaName, fieldName);
400 list.add(authorityRefListItem(fieldName, refName));
403 private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
405 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
407 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
408 ilistItem.setRefName(refName);
409 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
410 ilistItem.setItemDisplayName(termInfo.displayName);
411 ilistItem.setSourceField(authRefFieldName);
412 ilistItem.setUri(termInfo.getRelativeUri());
413 } catch (Exception e) {
414 // Do nothing upon encountering an Exception here.