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;
53 import org.nuxeo.ecm.core.api.DocumentModel;
54 import org.nuxeo.ecm.core.api.DocumentModelList;
55 import org.nuxeo.ecm.core.api.model.PropertyException;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58 import org.w3c.dom.Document;
61 * RemoteDocumentModelHandler
63 * $LastChangedRevision: $
68 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
69 extends DocumentModelHandler<T, TL> {
72 private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
75 * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
78 public void setServiceContext(ServiceContext ctx) { //FIXME: Apply proper generics to ServiceContext<MultipartInput, MultipartOutput>
79 if(ctx instanceof MultipartServiceContext){
80 super.setServiceContext(ctx);
82 throw new IllegalArgumentException("setServiceContext requires instance of " +
83 MultipartServiceContext.class.getName());
88 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
91 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
92 DocumentModel docModel = wrapDoc.getWrappedObject();
93 //return at least those document part(s) that were received
94 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
95 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
96 List<InputPart> inputParts = ctx.getInput().getParts();
97 for(InputPart part : inputParts){
98 String partLabel = part.getHeaders().getFirst("label");
99 ObjectPartType partMeta = partsMetaMap.get(partLabel);
100 // extractPart(docModel, partLabel, partMeta);
101 Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
102 addOutputPart(unQObjectProperties, partLabel, partMeta);
107 * Adds the output part.
109 * @param unQObjectProperties the un q object properties
110 * @param schema the schema
111 * @param partMeta the part meta
112 * @throws Exception the exception
114 private void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
116 Document doc = DocumentUtils.buildDocument(partMeta, schema,
117 unQObjectProperties);
118 if (logger.isDebugEnabled() == true) {
119 logger.debug(DocumentUtils.xmlToString(doc));
121 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
122 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
126 * Extract paging info.
128 * @param commonsList the commons list
130 * @throws Exception the exception
132 protected TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
134 AbstractCommonList commonList = (AbstractCommonList)theCommonList;
136 DocumentFilter docFilter = this.getDocumentFilter();
137 long pageSize = docFilter.getPageSize();
138 long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
139 // set the page size and page number
140 commonList.setPageNum(pageNum);
141 commonList.setPageSize(pageSize);
142 DocumentModelList docList = wrapDoc.getWrappedObject();
143 // Set num of items in list. this is useful to our testing framework.
144 commonList.setItemsInPage(docList.size());
145 // set the total result size
146 commonList.setTotalItems(docList.totalSize());
148 return (TL)commonList;
153 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
156 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
159 DocumentModel docModel = wrapDoc.getWrappedObject();
160 String[] schemas = docModel.getDeclaredSchemas();
161 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
162 for (String schema : schemas) {
163 ObjectPartType partMeta = partsMetaMap.get(schema);
164 if (partMeta == null) {
165 continue; // unknown part, ignore
167 Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
168 addOutputPart(unQObjectProperties, schema, partMeta);
173 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
176 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
178 //TODO filling extension parts should be dynamic
179 //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
180 //not an ideal way of populating objects.
181 DocumentModel docModel = wrapDoc.getWrappedObject();
182 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
183 MultipartInput input = ctx.getInput();
184 if(input.getParts().isEmpty()){
185 String msg = "No payload found!";
186 logger.error(msg + "Ctx=" + getServiceContext().toString());
187 throw new BadRequestException(msg);
190 Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
192 //iterate over parts received and fill those parts
193 List<InputPart> inputParts = input.getParts();
194 for(InputPart part : inputParts){
196 String partLabel = part.getHeaders().getFirst("label");
197 if (partLabel == null) {
198 String msg = "Part label is missing or empty!";
199 logger.error(msg + "Ctx=" + getServiceContext().toString());
200 throw new BadRequestException(msg);
203 //skip if the part is not in metadata
204 ObjectPartType partMeta = partsMetaMap.get(partLabel);
208 fillPart(part, docModel, partMeta, action);
214 * fillPart fills an XML part into given document model
215 * @param part to fill
216 * @param docModel for the given object
217 * @param partMeta metadata for the object to fill
220 protected void fillPart(InputPart part, DocumentModel docModel, ObjectPartType partMeta, Action action)
222 InputStream payload = part.getBody(InputStream.class, null);
224 // TODO for sub-docs - after we parse the doc, we need to look for elements that are configured as
225 // subitem lists, for this part (schema), pull them out, and set them aside for later processing.
227 //check if this is an xml part
228 if(part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)){
230 Document document = DocumentUtils.parseDocument(payload, partMeta,
231 false /*don't validate*/);
232 //TODO: callback to handler if registered to validate the
234 Map<String, Object> objectProps = DocumentUtils.parseProperties(document.getFirstChild());
235 if(action==Action.UPDATE) {
236 this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
238 docModel.setProperties(partMeta.getLabel(), objectProps);
244 * Filters out read only properties, so they cannot be set on update.
245 * TODO: add configuration support to do this generally
246 * @param objectProps the properties parsed from the update payload
247 * @param partMeta metadata for the object to fill
249 public void filterReadOnlyPropertiesForPart(
250 Map<String, Object> objectProps, ObjectPartType partMeta) {
251 // Currently a no-op, but can be overridden in Doc handlers.
255 * extractPart extracts an XML object from given DocumentModel
257 * @param schema of the object to extract
258 * @param partMeta metadata for the object to extract
261 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
263 return extractPart( docModel, schema, partMeta, null );
267 * extractPart extracts an XML object from given DocumentModel
269 * @param schema of the object to extract
270 * @param partMeta metadata for the object to extract
273 protected Map<String, Object> extractPart(
274 DocumentModel docModel, String schema, ObjectPartType partMeta,
275 Map<String, Object> addToMap)
277 Map<String, Object> result = null;
279 MediaType mt = MediaType.valueOf(partMeta.getContent().getContentType());
280 if (mt.equals(MediaType.APPLICATION_XML_TYPE)){
281 Map<String, Object> objectProps = docModel.getProperties(schema);
282 //unqualify properties before sending the doc over the wire (to save bandwidh)
283 //FIXME: is there a better way to avoid duplication of a collection?
284 Map<String, Object> unQObjectProperties =
285 (addToMap!=null)? addToMap:(new HashMap<String, Object>());
286 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
287 for(Entry<String, Object> entry : qualifiedEntries){
288 String unqProp = getUnQProperty(entry.getKey());
289 unQObjectProperties.put(unqProp, entry.getValue());
291 result = unQObjectProperties;
292 } //TODO: handle other media types
298 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
301 public AuthorityRefList getAuthorityRefs(
302 DocumentWrapper<DocumentModel> docWrapper,
303 List<String> authRefFields) throws PropertyException {
304 final String FIELD_REFNAME_DELIMITER = "|";
305 final String FIELD_REFNAME_DELIMITER_REGEX = "\\" + FIELD_REFNAME_DELIMITER;
306 AuthorityRefList authRefList = new AuthorityRefList();
308 DocumentModel docModel = docWrapper.getWrappedObject();
309 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
311 for (String field : authRefFields) {
312 // FIXME If the code used below doesn't support
313 // arbitrary levels of nesting; e.g. "get all authrefs
314 // in any children of a parent," then we might use
315 // docModel.getProperties() instead,
316 List<String> refNames = new ArrayList<String>();
317 Object val = docModel.getPropertyValue(field);
318 if (val instanceof String)
319 refNames.add((String) val);
320 else if (val instanceof List) {
321 refNames = (List<String>) val;
323 for (String refName : refNames) {
324 if (refName == null || refName.trim().isEmpty())
327 // If the refName is prefixed by a field name
328 // and a delimiter, this means that it was
329 // found in a child of the specified authref field.
331 // Store the child field's name as the field name.
332 // Then strip off the child's name and the delimiter
335 // FIXME: Move this 'split' code to its own utility method.
336 // FIXME: Verify that the behavior description above
337 // is accurate for arbitrary levels of nesting.
338 if (refName.indexOf(FIELD_REFNAME_DELIMITER) > 0) {
339 String[] refNameParts =
340 refName.split(FIELD_REFNAME_DELIMITER_REGEX);
341 field = refNameParts[0];
342 refName = refNameParts[1];
345 RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils
346 .parseAuthorityTermInfo(refName);
347 AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
348 ilistItem.setRefName(refName);
349 ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
350 ilistItem.setItemDisplayName(termInfo.displayName);
351 ilistItem.setSourceField(field);
352 ilistItem.setUri(termInfo.getRelativeUri());
354 } catch (Exception e) {
355 // FIXME: Do we need to throw this Exception here?
356 if (logger.isDebugEnabled()) {
357 logger.debug("Caught exception in getAuthorityRefs", e);
362 } catch (PropertyException pe) {
363 String msg = "Attempted to retrieve value for invalid or missing authority field. "
364 + "Check authority field properties in tenant bindings.";
365 logger.warn(msg, pe);
367 } catch (Exception e) {
368 if (logger.isDebugEnabled()) {
369 logger.debug("Caught exception in getAuthorityRefs", e);
371 Response response = Response.status(
372 Response.Status.INTERNAL_SERVER_ERROR).entity(
373 "Failed to retrieve authority references").type(
374 "text/plain").build();
375 throw new WebApplicationException(response);