]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
51b8fc4e64ed6b7eb364f2e57c52f3867108d3fc
[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 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;
59
60 /**
61  * RemoteDocumentModelHandler
62  *
63  * $LastChangedRevision: $
64  * $LastChangedDate: $
65  * @param <T> 
66  * @param <TL> 
67  */
68 public abstract class RemoteDocumentModelHandlerImpl<T, TL>
69         extends DocumentModelHandler<T, TL> {
70
71     /** The logger. */
72     private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
73
74     /* (non-Javadoc)
75      * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
76      */
77     @Override
78     public void setServiceContext(ServiceContext ctx) {  //FIXME: Apply proper generics to ServiceContext<MultipartInput, MultipartOutput>
79         if(ctx instanceof MultipartServiceContext){
80             super.setServiceContext(ctx);
81         }else{
82             throw new IllegalArgumentException("setServiceContext requires instance of " +
83                     MultipartServiceContext.class.getName());
84         }
85     }
86
87     /* (non-Javadoc)
88      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
89      */
90     @Override
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);
103         }
104     }
105
106     /**
107      * Adds the output part.
108      *
109      * @param unQObjectProperties the un q object properties
110      * @param schema the schema
111      * @param partMeta the part meta
112      * @throws Exception the exception
113      */
114     private void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
115                 throws Exception {
116                 Document doc = DocumentUtils.buildDocument(partMeta, schema,
117                                 unQObjectProperties);
118                 if (logger.isDebugEnabled() == true) {                  
119                         logger.debug(DocumentUtils.xmlToString(doc));
120                 }
121                 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
122                 ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
123     }    
124     
125     /**
126      * Extract paging info.
127      *
128      * @param commonsList the commons list
129      * @return the tL
130      * @throws Exception the exception
131      */
132     protected TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
133         throws Exception {
134         AbstractCommonList commonList = (AbstractCommonList)theCommonList;
135         
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());
147         
148         return (TL)commonList;
149     }
150     
151     
152         /* (non-Javadoc)
153          * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
154          */
155         @Override
156         public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
157                         throws Exception {
158
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
166                         }
167                         Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
168                         addOutputPart(unQObjectProperties, schema, partMeta);
169                 }
170         }
171
172     /* (non-Javadoc)
173      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
174      */
175     @Override
176     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
177
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);
188         }
189
190         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
191
192         //iterate over parts received and fill those parts
193         List<InputPart> inputParts = input.getParts();
194         for(InputPart part : inputParts){
195
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);
201             }
202             
203             //skip if the part is not in metadata
204             ObjectPartType partMeta = partsMetaMap.get(partLabel);
205             if(partMeta==null){
206                 continue;
207             }
208             fillPart(part, docModel, partMeta, action);
209         }//rof
210
211     }
212
213     /**
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
218      * @throws Exception
219      */
220     protected void fillPart(InputPart part, DocumentModel docModel, ObjectPartType partMeta, Action action)
221             throws Exception {
222         InputStream payload = part.getBody(InputStream.class, null);
223         
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.
226
227         //check if this is an xml part
228         if(part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)){
229             if(payload != null){
230                 Document document = DocumentUtils.parseDocument(payload, partMeta,
231                                 false /*don't validate*/);
232                 //TODO: callback to handler if registered to validate the
233                 //document
234                 Map<String, Object> objectProps = DocumentUtils.parseProperties(document.getFirstChild());
235                 if(action==Action.UPDATE) {
236                         this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
237                 }
238                 docModel.setProperties(partMeta.getLabel(), objectProps);
239             }
240         }
241     }
242
243     /**
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
248      */
249     public void filterReadOnlyPropertiesForPart(
250                 Map<String, Object> objectProps, ObjectPartType partMeta) {
251         // Currently a no-op, but can be overridden in Doc handlers.
252     }
253
254     /**
255      * extractPart extracts an XML object from given DocumentModel
256      * @param docModel
257      * @param schema of the object to extract
258      * @param partMeta metadata for the object to extract
259      * @throws Exception
260      */
261     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
262             throws Exception {
263         return extractPart( docModel, schema, partMeta, null );
264     }
265     
266     /**
267      * extractPart extracts an XML object from given DocumentModel
268      * @param docModel
269      * @param schema of the object to extract
270      * @param partMeta metadata for the object to extract
271      * @throws Exception
272      */
273     protected Map<String, Object> extractPart(
274                 DocumentModel docModel, String schema, ObjectPartType partMeta,
275                 Map<String, Object> addToMap)
276             throws Exception {
277         Map<String, Object> result = null;
278         
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());
290             }
291             result = unQObjectProperties;
292         } //TODO: handle other media types
293         
294         return result;
295     }
296     
297     /* (non-Javadoc)
298      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
299      */
300     @Override
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();
307         try {
308                 DocumentModel docModel = docWrapper.getWrappedObject();
309                 List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
310
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;
322                         }
323                         for (String refName : refNames) {
324                             if (refName == null || refName.trim().isEmpty())
325                                     continue;
326                             try {
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.
330                                     //
331                                     // Store the child field's name as the field name.
332                                     // Then strip off the child's name and the delimiter
333                                     // from the refName.
334                                     //
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];
343                                     }
344                                     
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());
353                                     list.add(ilistItem);
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);
358                                     }
359                             }
360                         }
361                 }
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);
366                 throw pe;
367         } catch (Exception e) {
368                 if (logger.isDebugEnabled()) {
369                         logger.debug("Caught exception in getAuthorityRefs", e);
370                 }
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);
376         }
377         return authRefList;
378     }
379 }