You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@stanbol.apache.org by rw...@apache.org on 2012/02/14 15:49:21 UTC

svn commit: r1243965 [1/2] - in /incubator/stanbol/trunk: commons/web/base/src/main/java/org/apache/stanbol/commons/web/base/writers/ enhancer/generic/servicesapi/src/main/java/org/apache/stanbol/enhancer/servicesapi/helper/ enhancer/jersey/src/main/ja...

Author: rwesten
Date: Tue Feb 14 14:49:20 2012
New Revision: 1243965

URL: http://svn.apache.org/viewvc?rev=1243965&view=rev
Log:
## Implementation of STANBOL-481 - Multi ContentPart RESTful API extensions

* An detailed description of the implementation is provided by comments on this issue.
* This also includes several Integration tests for the new RESTful API. This tests are also a good starting point for potential users.
* The REST API documentation of the Stanbol web interface is also updated by this new features.

This should also provide all the functionality missing for finishing the work on STANBOL-431: Improve Enhancer/ REST endpoint

## STANBOL-488:

This adds support for EnhancementProperties managed as ContentPart of the ContentItem. However the usage is only locally within the "org.apache.stanbol.enhancer.jersey" bundle because this issue needs further discussion before implementation.

The implementation of STANBOL-481 is in fact dependent on the presents of the "EnhancementProperties" feature, but it can be easily adapted to work with all possible implementations discussed by STANBOL-488

## STANBOL-414: Refactor EnhancementEngine to support contents with multiple related parts

This also finishes the work on STANBOL-414 as now both the Java API and the RESTful API do support ContentItems with multiple parts.

In addition I recommend to mark this issue as resolved as its initial goals are covered by STANBOL-414.

## Other changes

* The JsonLdSerializerProvider now ignores parameters when comparing the parsed formatIdentifier. Previously this SerializingProvider ignored requests with media types such as "application/json; charset=UTF-8"
* ContentItemHelper#parseMimeType now correctly trims keys and values of parsed parameters.

Added:
    incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/utils/
    incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/utils/EnhancementPropertiesHelper.java   (with props)
    incubator/stanbol/trunk/integration-tests/src/test/java/org/apache/stanbol/enhancer/it/ContentTranformationTest.java   (with props)
    incubator/stanbol/trunk/integration-tests/src/test/java/org/apache/stanbol/enhancer/it/MultipartContentItemTestUtils.java   (with props)
    incubator/stanbol/trunk/integration-tests/src/test/java/org/apache/stanbol/enhancer/it/MultipartRequestTest.java   (with props)
Modified:
    incubator/stanbol/trunk/commons/web/base/src/main/java/org/apache/stanbol/commons/web/base/writers/JsonLdSerializerProvider.java
    incubator/stanbol/trunk/enhancer/generic/servicesapi/src/main/java/org/apache/stanbol/enhancer/servicesapi/helper/ContentItemHelper.java
    incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/fragment/EnhancerWebFragment.java
    incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/reader/ContentItemReader.java
    incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource.java
    incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/writers/ContentItemWriter.java
    incubator/stanbol/trunk/enhancer/jersey/src/main/resources/org/apache/stanbol/enhancer/jersey/templates/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource/index.ftl
    incubator/stanbol/trunk/enhancer/jersey/src/test/java/org/apache/stanbol/enhancer/jersey/ContentItemReaderWriterTest.java
    incubator/stanbol/trunk/integration-tests/pom.xml
    incubator/stanbol/trunk/integration-tests/src/test/java/org/apache/stanbol/commons/httpqueryheaders/it/HttpQueryHeaderPostTest.java

Modified: incubator/stanbol/trunk/commons/web/base/src/main/java/org/apache/stanbol/commons/web/base/writers/JsonLdSerializerProvider.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/commons/web/base/src/main/java/org/apache/stanbol/commons/web/base/writers/JsonLdSerializerProvider.java?rev=1243965&r1=1243964&r2=1243965&view=diff
==============================================================================
--- incubator/stanbol/trunk/commons/web/base/src/main/java/org/apache/stanbol/commons/web/base/writers/JsonLdSerializerProvider.java (original)
+++ incubator/stanbol/trunk/commons/web/base/src/main/java/org/apache/stanbol/commons/web/base/writers/JsonLdSerializerProvider.java Tue Feb 14 14:49:20 2012
@@ -70,8 +70,15 @@ public class JsonLdSerializerProvider im
 
     @Override
     public void serialize(OutputStream serializedGraph, TripleCollection tc, String formatIdentifier) {
-        if (!formatIdentifier.equals(SUPPORTED_FORMAT)) {
-            logger.info("serialize() the format '" + formatIdentifier + "' is not supported by this implementation");
+        String deParameterizedIdentifier;
+        int semicolonPos = formatIdentifier.indexOf(';');
+        if (semicolonPos > -1) {
+            deParameterizedIdentifier = formatIdentifier.substring(0, semicolonPos);
+        } else {
+            deParameterizedIdentifier = formatIdentifier;
+        }
+        if (!deParameterizedIdentifier.equalsIgnoreCase(SUPPORTED_FORMAT)) {
+            logger.info("serialize() the format '" + deParameterizedIdentifier + "' is not supported by this implementation");
             return;
         }
 

Modified: incubator/stanbol/trunk/enhancer/generic/servicesapi/src/main/java/org/apache/stanbol/enhancer/servicesapi/helper/ContentItemHelper.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/generic/servicesapi/src/main/java/org/apache/stanbol/enhancer/servicesapi/helper/ContentItemHelper.java?rev=1243965&r1=1243964&r2=1243965&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/generic/servicesapi/src/main/java/org/apache/stanbol/enhancer/servicesapi/helper/ContentItemHelper.java (original)
+++ incubator/stanbol/trunk/enhancer/generic/servicesapi/src/main/java/org/apache/stanbol/enhancer/servicesapi/helper/ContentItemHelper.java Tue Feb 14 14:49:20 2012
@@ -216,9 +216,9 @@ public class ContentItemHelper {
             int nameValueSeparator = parameter.indexOf('=');
             if(nameValueSeparator>0 && parameter.length() > nameValueSeparator+2){
                 //keys are case insensitive (we use lower case)
-                String key = parameter.substring(0,nameValueSeparator).toLowerCase();
+                String key = parameter.substring(0,nameValueSeparator).toLowerCase().trim();
                 if(!parsed.containsKey(key)){ //do not override existing keys
-                    parsed.put(key,parameter.substring(nameValueSeparator+1));
+                    parsed.put(key,parameter.substring(nameValueSeparator+1).trim());
                 }
             }
         }

Modified: incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/fragment/EnhancerWebFragment.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/fragment/EnhancerWebFragment.java?rev=1243965&r1=1243964&r2=1243965&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/fragment/EnhancerWebFragment.java (original)
+++ incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/fragment/EnhancerWebFragment.java Tue Feb 14 14:49:20 2012
@@ -32,12 +32,14 @@ import org.apache.stanbol.commons.web.ba
 import org.apache.stanbol.commons.web.base.NavigationLink;
 import org.apache.stanbol.commons.web.base.ScriptResource;
 import org.apache.stanbol.commons.web.base.WebFragment;
+import org.apache.stanbol.enhancer.jersey.reader.ContentItemReader;
 import org.apache.stanbol.enhancer.jersey.resource.ChainsRootResource;
 import org.apache.stanbol.enhancer.jersey.resource.EnginesRootResource;
 import org.apache.stanbol.enhancer.jersey.resource.EnhancementEngineResource;
 import org.apache.stanbol.enhancer.jersey.resource.EnhancementEnginesRootResource;
 import org.apache.stanbol.enhancer.jersey.resource.EnhancerRootResource;
 import org.apache.stanbol.enhancer.jersey.resource.EnhancementChainResource;
+import org.apache.stanbol.enhancer.jersey.writers.ContentItemWriter;
 import org.apache.stanbol.enhancer.servicesapi.EnhancementJobManager;
 import org.osgi.framework.BundleContext;
 import org.osgi.service.component.ComponentContext;
@@ -90,6 +92,9 @@ public class EnhancerWebFragment impleme
         classes.add(ChainsRootResource.class);
         classes.add(EnhancementEnginesRootResource.class);
         classes.add(EnhancementEngineResource.class);
+        //Reader/Writer for ContentItems
+        classes.add(ContentItemReader.class);
+        classes.add(ContentItemWriter.class);
         return classes;
     }
 

Modified: incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/reader/ContentItemReader.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/reader/ContentItemReader.java?rev=1243965&r1=1243964&r2=1243965&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/reader/ContentItemReader.java (original)
+++ incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/reader/ContentItemReader.java Tue Feb 14 14:49:20 2012
@@ -1,94 +1,110 @@
 /*
-* Licensed to the Apache Software Foundation (ASF) under one or more
-* contributor license agreements.  See the NOTICE file distributed with
-* this work for additional information regarding copyright ownership.
-* The ASF licenses this file to You under the Apache License, Version 2.0
-* (the "License"); you may not use this file except in compliance with
-* the License.  You may obtain a copy of the License at
-*
-*     http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.apache.stanbol.enhancer.jersey.reader;
 
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.ENHANCEMENT_PROPERTIES_URI;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.PARSED_CONTENT_URIS;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.getEnhancementProperties;
 import static org.apache.stanbol.enhancer.servicesapi.helper.EnhancementEngineHelper.randomUUID;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
-import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
-import java.util.Map.Entry;
+import java.util.Map;
+import java.util.Set;
 
-import javax.print.attribute.standard.Media;
 import javax.servlet.ServletContext;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.ResponseBuilder;
 import javax.ws.rs.ext.MessageBodyReader;
+import javax.ws.rs.ext.Provider;
 
 import org.apache.clerezza.rdf.core.MGraph;
-import org.apache.clerezza.rdf.core.TripleCollection;
 import org.apache.clerezza.rdf.core.UriRef;
 import org.apache.clerezza.rdf.core.serializedform.Parser;
-import org.apache.clerezza.rdf.core.serializedform.ParsingProvider;
 import org.apache.clerezza.rdf.jena.parser.JenaParserProvider;
 import org.apache.commons.fileupload.FileItemIterator;
 import org.apache.commons.fileupload.FileItemStream;
 import org.apache.commons.fileupload.FileUpload;
 import org.apache.commons.fileupload.FileUploadException;
 import org.apache.commons.fileupload.RequestContext;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
 import org.apache.commons.io.IOUtils;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.entity.mime.HttpMultipart;
-import org.apache.http.entity.mime.MultipartEntity;
 import org.apache.stanbol.commons.indexedgraph.IndexedMGraph;
 import org.apache.stanbol.commons.web.base.ContextHelper;
-import org.apache.stanbol.enhancer.jersey.writers.ContentItemWriter;
+import org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper;
 import org.apache.stanbol.enhancer.servicesapi.Blob;
 import org.apache.stanbol.enhancer.servicesapi.ContentItem;
-import org.apache.stanbol.enhancer.servicesapi.helper.ContentItemHelper;
-import org.apache.stanbol.enhancer.servicesapi.helper.EnhancementEngineHelper;
 import org.apache.stanbol.enhancer.servicesapi.helper.InMemoryBlob;
 import org.apache.stanbol.enhancer.servicesapi.helper.InMemoryContentItem;
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+import org.codehaus.jettison.mapped.SimpleConverter;
+import org.mortbay.log.Log;
+import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.sun.jersey.core.util.MultivaluedMapImpl;
-import com.sun.jersey.core.util.StringKeyIgnoreCaseMultivaluedMap;
-
+@Provider
 public class ContentItemReader implements MessageBodyReader<ContentItem> {
     
     private static Logger log = LoggerFactory.getLogger(ContentItemReader.class);
     FileUpload fu = new FileUpload();
-    private ServletContext servletContext;
-    private Parser parser;
+    private Parser __parser;
+    private ServletContext context;
     
     public static final MediaType MULTIPART = MediaType.valueOf(MediaType.MULTIPART_FORM_DATA_TYPE.getType()+"/*");
 
     public ContentItemReader(@Context ServletContext context) {
-        this.servletContext = context;
-        if(context != null){
-            this.parser = ContextHelper.getServiceFromContext(Parser.class, context);
-        } else { //mainly for unit tests we want also allow initialisation without context
-            this.parser = new Parser();
-            parser.bindParsingProvider(new JenaParserProvider());
+        this.context = context;
+    }
+    /**
+     * Lazy initialisation for the parser.
+     * @return teh parser
+     */
+    protected final Parser getParser(){
+        /*
+         * Needed because Jersey tries to create an instance
+         * during initialisation. At that time the {@link BundleContext} required
+         * by {@link ContextHelper#getServiceFromContext(Class, ServletContext)}
+         * is not yet present resulting in an Exception.
+         */
+        if(__parser == null){
+            if(context != null){
+                __parser = ContextHelper.getServiceFromContext(Parser.class, context);
+            } else { //mainly for unit tests we want also allow initialisation without context
+                __parser = new Parser();
+                __parser.bindParsingProvider(new JenaParserProvider());
+            }
         }
+        return __parser;
     }
+    
     @Override
     public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
         return ContentItem.class.isAssignableFrom(type);
@@ -103,6 +119,7 @@ public class ContentItemReader implement
                                 InputStream entityStream) throws IOException, WebApplicationException {
         //boolean withMetadata = withMetadata(httpHeaders);
         ContentItem contentItem = null;
+        Set<String> parsedContentIds = new HashSet<String>();
         if(mediaType.isCompatible(MULTIPART)){
             //try to read ContentItem from "multipart/from-data"
             MGraph metadata = null;
@@ -126,7 +143,7 @@ public class ContentItemReader implement
                         }
                         metadata = new IndexedMGraph();
                         try {
-                            parser.parse(metadata, fis.openStream(), fis.getContentType());
+                            getParser().parse(metadata, fis.openStream(), fis.getContentType());
                         } catch (Exception e) {
                             throw new WebApplicationException(e, 
                                 Response.status(Response.Status.BAD_REQUEST)
@@ -137,7 +154,44 @@ public class ContentItemReader implement
                                 .build());
                         }
                     } else if(fis.getFieldName().equals("content")){
-                        contentItem = createContentItem(contentItemId, metadata, fis);
+                        contentItem = createContentItem(contentItemId, metadata, fis, parsedContentIds);
+                    } else if(fis.getFieldName().equals("properties") ||
+                            fis.getFieldName().equals(ENHANCEMENT_PROPERTIES_URI.getUnicodeString())){
+                        //parse the enhancementProperties
+                        if(contentItem == null){
+                            throw new WebApplicationException(
+                                Response.status(Response.Status.BAD_REQUEST)
+                                .entity("Multipart MIME parts for " +
+                                		"EnhancementProperties MUST BE after the " +
+                                		"MIME parts for 'metadata' AND 'content'")
+                                .build());
+                        }
+                        MediaType propMediaType = MediaType.valueOf(fis.getContentType());
+                        if(!APPLICATION_JSON_TYPE.isCompatible(propMediaType)){
+                            throw new WebApplicationException(
+                                Response.status(Response.Status.BAD_REQUEST)
+                                .entity("EnhancementProperties (Multipart MIME parts" +
+                                		"with the name '"+fis.getFieldName()+"') MUST " +
+                                		"BE encoded as 'appicaltion/json' (encountered: '" +
+                                		fis.getContentType()+"')!")
+                                .build());
+                        }
+                        String propCharset = propMediaType.getParameters().get("charset");
+                        if(propCharset == null){
+                            propCharset = "UTF-8";
+                        }
+                        Map<String,Object> enhancementProperties = getEnhancementProperties(contentItem); 
+                        try {
+                            enhancementProperties.putAll(toMap(new JSONObject(
+                                IOUtils.toString(fis.openStream(),propCharset))));
+                        } catch (JSONException e) {
+                            throw new WebApplicationException(e,
+                                Response.status(Response.Status.BAD_REQUEST)
+                                .entity("Unable to parse EnhancementProperties from" +
+                                		"Multipart MIME parts with the name 'properties'!")
+                                .build());
+                        }
+                        
                     } else { //additional metadata as serialised RDF
                         if(contentItem == null){
                             throw new WebApplicationException(
@@ -157,7 +211,7 @@ public class ContentItemReader implement
                         }
                         MGraph graph = new IndexedMGraph();
                         try {
-                            parser.parse(graph, fis.openStream(), fis.getContentType());
+                            getParser().parse(graph, fis.openStream(), fis.getContentType());
                         } catch (Exception e) {
                             throw new WebApplicationException(e, 
                                 Response.status(Response.Status.BAD_REQUEST)
@@ -166,30 +220,65 @@ public class ContentItemReader implement
                                         fis.getName(),fis.getContentType()))
                                 .build());
                         }
-                        contentItem.addPart(new UriRef(fis.getFieldName()), graph);
+                        UriRef contentPartId = new UriRef(fis.getFieldName());
+                        contentItem.addPart(contentPartId, graph);
                     }
                 }
+                if(contentItem == null){
+                    throw new WebApplicationException(
+                        Response.status(Response.Status.BAD_REQUEST)
+                        .entity("The parsed multipart content item does not contain "
+                            + "any content. The content is expected to be contained "
+                            + "in a MIME part with the name 'content'. This part can "
+                            + " be also a 'multipart/alternate' if multiple content "
+                            + "parts need to be included in requests.").build());
+                }
             } catch (FileUploadException e) {
                 throw new WebApplicationException(e, Response.Status.BAD_REQUEST);
             }
         } else { //normal content
             contentItem = new InMemoryContentItem(
                 IOUtils.toByteArray(entityStream), mediaType.toString());
+            //add the URI of the main content
+            parsedContentIds.add(contentItem.getPartUri(0).getUnicodeString());
         }
+        //set the parsed contentIDs to the EnhancementProperties
+        getEnhancementProperties(contentItem).put(PARSED_CONTENT_URIS, 
+            Collections.unmodifiableSet(parsedContentIds));
         return contentItem;
     }
-    private ContentItem createContentItem(String id, MGraph metadata, FileItemStream content) throws IOException, FileUploadException {
+    /**
+     * Creates a ContentItem
+     * @param id the ID or <code>null</code> if not known
+     * @param metadata the metadata or <code>null</code> if not parsed. NOTE that
+     * if <code>id == null</code> also <code>metadata == null</code> and 
+     * <code>id != null</code> also <code>metadata != null</code>.
+     * @param content the {@link FileItemStream} of the MIME part representing
+     * the content. If {@link FileItemStream#getContentType()} is compatible with
+     * "multipart/*" than this will further parse for multiple parsed content
+     * version. In any other case the contents of the parsed {@link FileItemStream}
+     * will be directly add as content for the {@link ContentItem} created by
+     * this method.
+     * @param parsedContentParts used to add the IDs of parsed contentParts 
+     * @return the created content item
+     * @throws IOException on any error while accessing the contents of the parsed
+     * {@link FileItemStream}
+     * @throws FileUploadException if the parsed contents are not correctly
+     * encoded Multipoart MIME
+     */
+    private ContentItem createContentItem(String id, MGraph metadata, FileItemStream content,Set<String> parsedContentParts) throws IOException, FileUploadException {
         MediaType partContentType = MediaType.valueOf(content.getContentType());
         ContentItem contentItem = null;
-        if(partContentType.isCompatible(MULTIPART)){
+        if(MULTIPART.isCompatible(partContentType)){
             //multiple contentParts are parsed
             FileItemIterator contentPartIterator = fu.getItemIterator(
                 new MessageBodyReaderContext(
                     content.openStream(), partContentType));
             while(contentPartIterator.hasNext()){
                 FileItemStream fis = contentPartIterator.next();
-                String contentPartUri = fis.getFieldName();
                 if(contentItem == null){
+                    log.debug("create ContentItem {} for content (type:{})",
+                        id,content.getContentType());
                     contentItem = new InMemoryContentItem(id, 
                         IOUtils.toByteArray(fis.openStream()),
                         fis.getContentType(), metadata);
@@ -203,31 +292,25 @@ public class ContentItemReader implement
                         //TODO maybe we should throw an exception instead
                         contentPartId = new UriRef("urn:contentpart:"+ randomUUID());
                     }
+                    log.debug("  ... add Blob {} to ContentItem {} with content (type:{})",
+                        new Object[]{contentPartId, id, fis.getContentType()});
                     contentItem.addPart(contentPartId, blob);
+                    parsedContentParts.add(contentPartId.getUnicodeString());
                 }
             }
         } else {
+            log.debug("create ContentItem {} for content (type:{})",
+                id,content.getContentType());
             contentItem = new InMemoryContentItem(id, 
                 IOUtils.toByteArray(content.openStream()),
                 content.getContentType(), metadata);
         }
+        //add the URI of the main content to the parsed contentParts
+        parsedContentParts.add(contentItem.getPartUri(0).getUnicodeString());
         return contentItem;
     }
     
     /**
-     * @param httpHeaders
-     * @return if this requests parses an contentItem with metadata
-     */
-    private boolean withMetadata(MultivaluedMap<String,String> httpHeaders) {
-        boolean withMetadata = httpHeaders.containsKey("inputWithMetadata");
-        if(withMetadata){
-            String value = httpHeaders.getFirst("inputWithMetadata");
-            //null empty or "true"
-            withMetadata = value == null || value.isEmpty() || Boolean.parseBoolean(value);
-        }
-        return withMetadata;
-    }
-    /**
      * Adapter from the parameter present in an {@link MessageBodyReader} to
      * the {@link RequestContext} as used by the commons.fileupload framework
      * @author rwesten
@@ -267,5 +350,40 @@ public class ContentItemReader implement
         }
         
     }
+    /**
+     * Converts a JSON object to a java Map. Nested JSONArrays are converted
+     * to collections and nested JSONObjects are converted to Maps.
+     * @param object
+     * @return
+     * @throws JSONException
+     */
+    private Map<String,Object> toMap(JSONObject object) throws JSONException {
+        Map<String,Object> data = new HashMap<String,Object>();
+        for(Iterator<?> keys = object.keys();keys.hasNext();){
+            String key = (String)keys.next();
+            data.put(key, getValue(object.get(key)));
+        }
+        
+        return data;
+    }
+    /**
+     * @param object
+     * @param data
+     * @param key
+     * @throws JSONException
+     */
+    private Object getValue(Object value) throws JSONException {
+        if(value instanceof JSONObject){
+            return toMap((JSONObject)value);
+        } else if(value instanceof JSONArray){
+            Collection<Object> values =  new ArrayList<Object>(((JSONArray)value).length());
+            for(int i=0;i<((JSONArray)value).length();i++){
+                values.add(getValue(((JSONArray)value).get(i)));
+            }
+            return values;
+        } else {
+            return value;
+        }
+    }
     
 }

Modified: incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource.java?rev=1243965&r1=1243964&r2=1243965&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource.java (original)
+++ incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource.java Tue Feb 14 14:49:20 2012
@@ -20,13 +20,19 @@ import static javax.ws.rs.core.MediaType
 import static javax.ws.rs.core.MediaType.TEXT_HTML;
 import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
 import static javax.ws.rs.core.MediaType.WILDCARD;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.*;
 import static org.apache.stanbol.commons.web.base.CorsHelper.addCORSOrigin;
 import static org.apache.stanbol.commons.web.base.CorsHelper.enableCORS;
 
 import java.io.IOException;
 import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.servlet.ServletContext;
@@ -38,6 +44,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
@@ -48,11 +55,13 @@ import org.apache.clerezza.rdf.core.Grap
 import org.apache.clerezza.rdf.core.MGraph;
 import org.apache.clerezza.rdf.core.NonLiteral;
 import org.apache.clerezza.rdf.core.TripleCollection;
+import org.apache.clerezza.rdf.core.UriRef;
 import org.apache.clerezza.rdf.core.access.TcManager;
 import org.apache.clerezza.rdf.core.serializedform.Serializer;
 import org.apache.stanbol.commons.web.base.ContextHelper;
 import org.apache.stanbol.commons.web.base.resource.BaseStanbolResource;
 import org.apache.stanbol.commons.web.base.utils.MediaTypeUtil;
+import org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper;
 import org.apache.stanbol.enhancer.servicesapi.Chain;
 import org.apache.stanbol.enhancer.servicesapi.ChainException;
 import org.apache.stanbol.enhancer.servicesapi.ChainManager;
@@ -81,7 +90,7 @@ import com.sun.jersey.api.view.Viewable;
 @Path("/enhancer")
 public class EnhancerRootResource extends BaseStanbolResource {
 
-    private final Logger log = LoggerFactory.getLogger(getClass());
+    public final Logger log = LoggerFactory.getLogger(getClass());
 
     protected EnhancementJobManager jobManager;
 
@@ -245,7 +254,8 @@ public class EnhancerRootResource extend
                                     @Context HttpHeaders headers) throws EnhancementException, IOException {
         log.info("enhance from From: " + content);
         ContentItem ci = new InMemoryContentItem(content.getBytes("UTF-8"), TEXT_PLAIN);
-        return enhanceAndBuildResponse(format, headers, ci, false, buildAjaxview);
+//        ci.addPart(ENHANCEMENT_PROPERTIES_URI, Collections.singletonMap(INCLUDE_EXECUTION_METADATA, false));
+        return enhanceAndBuildResponse(format, headers, ci, buildAjaxview);
     }
 
     /**
@@ -261,28 +271,64 @@ public class EnhancerRootResource extend
      */
     @POST
     @Consumes(WILDCARD)
-    public Response enhanceFromData(byte[] data,
+    public Response enhanceFromData(ContentItem ci,
                                     @QueryParam(value = "uri") String uri,
                                     @QueryParam(value = "executionmetadata") boolean inclExecMetadata,
+                                    @QueryParam(value = "outputContent") Set<String> mediaTypes,
+                                    @QueryParam(value = "omitParsed") boolean omitParsed,
+                                    @QueryParam(value = "outputContentPart") Set<String> contentParts,
+                                    @QueryParam(value = "omitMetadata") boolean omitMetadata,
+                                    @QueryParam(value = "rdfFormat") String rdfFormat,
                                     @Context HttpHeaders headers) throws EnhancementException, IOException {
-        String format = TEXT_PLAIN;
-        if (headers.getMediaType() != null) {
-            format = headers.getMediaType().toString();
-        }
-        if (uri != null && uri.isEmpty()) {
-            // let the store build an internal URI based on the content
-            uri = null;
+        Map<String,Object> enhancementProperties = getEnhancementProperties(ci);
+        enhancementProperties.put(INCLUDE_EXECUTION_METADATA, inclExecMetadata);
+        if(mediaTypes != null && !mediaTypes.isEmpty()){
+            enhancementProperties.put(OUTPUT_CONTENT, mediaTypes);
+        }
+        enhancementProperties.put(OMIT_PARSED_CONTENT, omitParsed);
+        if(contentParts != null && !contentParts.isEmpty()){
+            Set<UriRef> outputContentParts = new HashSet<UriRef>();
+            for(String contentPartUri : contentParts){
+                if(contentPartUri != null && !contentPartUri.isEmpty()){
+                    if("*".equals(contentPartUri)){
+                        outputContentParts.add(null); //indicated wildcard
+                    } else {
+                        outputContentParts.add(new UriRef(contentPartUri));
+                    }
+                }
+            }
+            enhancementProperties.put(OUTPUT_CONTENT_PART, outputContentParts);
+        }
+        enhancementProperties.put(OMIT_METADATA, omitMetadata);
+        if(rdfFormat != null && !rdfFormat.isEmpty()){
+            try {
+                enhancementProperties.put(RDF_FORMAT,MediaType.valueOf(rdfFormat).toString());
+            } catch (IllegalArgumentException e) {
+                throw new WebApplicationException(e, 
+                    Response.status(Response.Status.BAD_REQUEST)
+                    .entity(String.format("Unable to parse MediaType form parameter" +
+                    		"rdfFormat=%s",rdfFormat))
+                    .build());
+            }
         }
-        ContentItem ci = new InMemoryContentItem(uri, data, format);
-        return enhanceAndBuildResponse(null, headers, ci, inclExecMetadata, false);
+//        String format = TEXT_PLAIN;
+//        if (headers.getMediaType() != null) {
+//            format = headers.getMediaType().toString();
+//        }
+//        if (uri != null && uri.isEmpty()) {
+//            // let the store build an internal URI based on the content
+//            uri = null;
+//        }
+//        ContentItem ci = new InMemoryContentItem(uri, data, format);
+        return enhanceAndBuildResponse(null, headers, ci, false);
     }
 
     protected Response enhanceAndBuildResponse(String format,
                                                HttpHeaders headers,
                                                ContentItem ci,
-                                               boolean inclExecMetadata,
                                                boolean buildAjaxview) throws EnhancementException,
                                                                      IOException {
+        Map<String,Object> enhancementPropertis = EnhancementPropertiesHelper.getEnhancementProperties(ci);
         if (jobManager != null) {
             jobManager.enhanceContent(ci, chain);
         }
@@ -299,14 +345,15 @@ public class EnhancerRootResource extend
         }
 
         MGraph graph = ci.getMetadata();
-        if (inclExecMetadata) {
+        Boolean includeExecutionMetadata = (Boolean)enhancementPropertis.get(INCLUDE_EXECUTION_METADATA);
+        if (includeExecutionMetadata != null && includeExecutionMetadata.booleanValue()) {
             try {
                 graph.addAll(ci.getPart(ExecutionMetadata.CHAIN_EXECUTION, MGraph.class));
             } catch (NoSuchPartException e) {
                 // no executionMetadata available
             }
         }
-        ResponseBuilder rb = Response.ok(graph);
+        ResponseBuilder rb = Response.ok(ci);
         // List<String> accepted = headers.getRequestHeader(HttpHeaders.ACCEPT);
         MediaType mediaType = MediaTypeUtil.getAcceptableMediaType(headers, null);
         // This can be used to create a customised WebExection. Jersey will sent

Added: incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/utils/EnhancementPropertiesHelper.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/utils/EnhancementPropertiesHelper.java?rev=1243965&view=auto
==============================================================================
--- incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/utils/EnhancementPropertiesHelper.java (added)
+++ incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/utils/EnhancementPropertiesHelper.java Tue Feb 14 14:49:20 2012
@@ -0,0 +1,196 @@
+package org.apache.stanbol.enhancer.jersey.utils;
+
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.clerezza.rdf.core.TripleCollection;
+import org.apache.clerezza.rdf.core.UriRef;
+import org.apache.stanbol.enhancer.servicesapi.Blob;
+import org.apache.stanbol.enhancer.servicesapi.ContentItem;
+import org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionMetadata;
+import org.apache.stanbol.enhancer.servicesapi.rdf.ExecutionPlan;
+
+/**
+ * Defines Constants and utilities for using EnhancementProperties
+ */
+public final class EnhancementPropertiesHelper {
+
+    private EnhancementPropertiesHelper(){/* no instances allowed*/}
+    /**
+     * URI used to register an {@link ContentItem#getPart(int, Class) contentPart}
+     * of the type {@link Map Map&lt;String,Objext&gt;} that contains properties
+     * for the enhancement process. <p>
+     * TODO: This might move to servicesapi and also accessible to enhancement
+     * engines
+     */
+    public static final UriRef ENHANCEMENT_PROPERTIES_URI = new UriRef(
+            "urn:apache.org:stanbol.web:enhancement.properties");
+    /**
+     * Boolean switch parsed as {@link QueryParam} tha allows to deactivate the
+     * inclusion of the {@link ContentItem#getMetadata()} in the Response
+     */
+    public static final String OMIT_METADATA = "stanbol.enhancer.web.omitMetadata";
+    /**
+     * {@link Set Set&lt;String&gt;} containing all the URIs of the
+     * {@link ContentItem#getPart(UriRef, Class) ContentParts} representing 
+     * RDF data (compatible to Clerezza {@link TripleCollection}). If the 
+     * returned set contains '*' than all such content parts need to be returned.<p>
+     * NOTE: This can also be used to include the EnhancementProperties
+     * as "applciation/json" in the Response by adding this
+     * {@link EnhancementPropertiesHelper#ENHANCEMENT_PROPERTIES_URI uri}.
+     */
+    public static final String OUTPUT_CONTENT_PART = "stanbol.enhancer.web.outputContentPart";
+    /**
+     * Allows to omit all parsed content parts regardless of the {@link #OUTPUT_CONTENT_PART}
+     * configuration
+     */
+    public static final String OMIT_PARSED_CONTENT = "stanbol.enhancer.web.omitParsed";
+    /**
+     * {@link Collection Collection&lt;String&gt;} containing mime types. This
+     * allows to specify what versions of the parsed content to be included in
+     * the response. e.g. ["text/*","application/pdf"] would include all text
+     * formats and PDF.
+     */
+    public static final String OUTPUT_CONTENT = "stanbol.enhancer.web.outputContent";
+    /**
+     * This allows to copy the {@link ExecutionMetadata} and {@link ExecutionPlan}
+     * data stored in a {@link ContentItem#getPart(UriRef, Class) contentPart} with
+     * the URI {@link ExecutionMetadata#CHAIN_EXECUTION} over to the
+     * {@link ContentItem#getMetadata() metadata} of the content item.<p>
+     * This feature is intended to allow users to retrieve such meta information
+     * without the need to use parse Multipart MIME responses.
+     */
+    public static final String INCLUDE_EXECUTION_METADATA = "stanbol.enhancer.web.executionmetadata";
+    /**
+     * The used format to encode RDF graphs for "multipart/*" responses. This
+     * needs to be parsed separately, because the Accept header needs to be
+     * set to "multipart/from-data" in such cases
+     */
+    public static final String RDF_FORMAT = "stanbol.enhancer.web.rdfFormat";
+    /**
+     * {@link Set Set&lt;String&gt;} containing all the {@link UriRef}s of 
+     * {@link ContentItem#getPart(int, Class) ContentItem.getPart}(uri,{@link Blob})
+     * that where parsed with the request.
+     */
+    public static final String PARSED_CONTENT_URIS = "stanbol.enhancer.web.parsedContentURIs";
+    
+    /**
+     * Getter for the EnhancementProperties for an {@link ContentItem}. If they
+     * do not already exist they are created and added to the ContentItem as
+     * contentPart with the URI {@link #ENHANCEMENT_PROPERTIES_URI}
+     * @param ci the contentItem MUST NOT be NULL
+     * @return the enhancement properties
+     * @throws IllegalArgumentException if <code>null</code> is parsed as {@link ContentItem}.
+     */
+    @SuppressWarnings("unchecked")
+    public static Map<String,Object> getEnhancementProperties(ContentItem ci){
+        if(ci == null){
+            throw new IllegalArgumentException("The parsed ContentItem MUST NOT be NULL!");
+        }
+        Map<String,Object> enhancementProperties;
+        try {
+            enhancementProperties = ci.getPart(ENHANCEMENT_PROPERTIES_URI, Map.class);
+        } catch (RuntimeException e) {
+           enhancementProperties = new HashMap<String,Object>();
+           ci.addPart(ENHANCEMENT_PROPERTIES_URI, enhancementProperties);
+        }
+        return enhancementProperties;
+    }
+    
+    
+    private static Object get(Map<String,Object> enhancementProperties,String key){
+        return enhancementProperties == null ? null : enhancementProperties.get(key);
+    }
+    /**
+     * Getter for the value of the parsed type for a given key.
+     * @param enhancementProperties the enhancement properties
+     * @param key the key
+     * @param type the type MUST NOT be <code>null</code>
+     * @return the values
+     * @throws ClassCastException if the value is not compatible to the
+     * parsed type
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T get(Map<String,Object> enhancementProperties,String key,Class<T> type){
+        if(type == null){
+            throw new IllegalArgumentException("The parsed type MUST NOT be NULL!");
+        }
+        Object value = get(enhancementProperties, key);
+        if(value == null){
+            return null;
+        } else if(type.isAssignableFrom(value.getClass())){
+            return (T)value;
+        } else {
+            throw new ClassCastException("EnhancementProperties value for key '"
+                    + key +"' is not of the expected type "+type+" but was"
+                    + value.getClass());
+        }
+    }
+    /**
+     * Getter for the boolean state based on the value of the parsed key.
+     * If the value is not of type {@link Boolean} the 
+     * {@link Boolean#parseBoolean(String)} is used on the {@link Object#toString()}
+     * method of the value.
+     * @param enhancementProperties the enhancementProperties
+     * @param key the key
+     * @return the state
+     */
+    public static boolean getState(Map<String,Object> enhancementProperties,String key){
+        Object state = get(enhancementProperties, key);
+        return state == null ? false : 
+            state instanceof Boolean ? ((Boolean)state).booleanValue() : 
+                Boolean.parseBoolean(state.toString());
+    }
+    public static boolean isOmitParsedContent(Map<String,Object> enhancementProperties){
+        return getState(enhancementProperties, OMIT_PARSED_CONTENT);
+    }
+    public static boolean isIncludeExecutionMetadata(Map<String,Object> enhancementProperties){
+        return getState(enhancementProperties, INCLUDE_EXECUTION_METADATA);
+    }
+    public static boolean isOmitMetadata(Map<String,Object> enhancementProperties){
+        return getState(enhancementProperties, OMIT_METADATA);
+    }
+    /**
+     * 
+     * @param enhancementProperties
+     * @return
+     * @throws ClassCastException if the value is not an Set
+     */
+    @SuppressWarnings("unchecked")
+    public static Collection<String> getParsedContentURIs(Map<String,Object> enhancementProperties){
+        return (Collection<String>)get(enhancementProperties, PARSED_CONTENT_URIS, Collection.class);
+    }
+    /**
+     * 
+     * @param enhancementProperties
+     * @return
+     * @throws ClassCastException if the value is not an {@link Set}
+     */
+    @SuppressWarnings("unchecked")
+    public static Collection<String> getOutputContentParts(Map<String,Object> enhancementProperties){
+        return (Collection<String>)get(enhancementProperties, OUTPUT_CONTENT_PART, Collection.class);
+    }
+    /**
+     * 
+     * @param enhancementProperties
+     * @return
+     * @throws ClassCastException if the value is not an {@link Collections}
+     */
+    @SuppressWarnings("unchecked")
+    public static Collection<String> getOutputContent(Map<String,Object> enhancementProperties){
+        return (Collection<String>)get(enhancementProperties, OUTPUT_CONTENT, Collection.class);
+    }
+    /**
+     * 
+     * @param enhancementProperties
+     * @return
+     * @throws ClassCastException if the value is not an {@link Collections}
+     */
+    public static String getRdfFormat(Map<String,Object> enhancementProperties){
+        return (String) get(enhancementProperties,RDF_FORMAT,String.class);
+    }
+}
\ No newline at end of file

Propchange: incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/utils/EnhancementPropertiesHelper.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/writers/ContentItemWriter.java
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/writers/ContentItemWriter.java?rev=1243965&r1=1243964&r2=1243965&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/writers/ContentItemWriter.java (original)
+++ incubator/stanbol/trunk/enhancer/jersey/src/main/java/org/apache/stanbol/enhancer/jersey/writers/ContentItemWriter.java Tue Feb 14 14:49:20 2012
@@ -1,32 +1,59 @@
 /*
-* Licensed to the Apache Software Foundation (ASF) under one or more
-* contributor license agreements.  See the NOTICE file distributed with
-* this work for additional information regarding copyright ownership.
-* The ASF licenses this file to You under the Apache License, Version 2.0
-* (the "License"); you may not use this file except in compliance with
-* the License.  You may obtain a copy of the License at
-*
-*     http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package org.apache.stanbol.enhancer.jersey.writers;
 
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM;
+import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
+import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA_TYPE;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN;
+import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
+import static javax.ws.rs.core.MediaType.WILDCARD_TYPE;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.ENHANCEMENT_PROPERTIES_URI;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.getEnhancementProperties;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.getOutputContent;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.getOutputContentParts;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.getParsedContentURIs;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.isOmitMetadata;
+import static org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper.isOmitParsedContent;
+import static org.apache.stanbol.enhancer.servicesapi.helper.ContentItemHelper.getBlob;
 import static org.apache.stanbol.enhancer.servicesapi.helper.ContentItemHelper.getContentParts;
 import static org.apache.stanbol.enhancer.servicesapi.helper.ContentItemHelper.getMimeTypeWithParameters;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 
 import javax.servlet.ServletContext;
 import javax.ws.rs.WebApplicationException;
@@ -34,55 +61,80 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
 import javax.ws.rs.ext.MessageBodyWriter;
 import javax.ws.rs.ext.Provider;
 
-import org.apache.clerezza.rdf.core.MGraph;
-import org.apache.clerezza.rdf.core.NonLiteral;
 import org.apache.clerezza.rdf.core.TripleCollection;
 import org.apache.clerezza.rdf.core.UriRef;
-import org.apache.clerezza.rdf.core.impl.TripleImpl;
 import org.apache.clerezza.rdf.core.serializedform.Serializer;
 import org.apache.clerezza.rdf.core.serializedform.SupportedFormat;
 import org.apache.clerezza.rdf.jena.serializer.JenaSerializerProvider;
-import org.apache.clerezza.rdf.ontologies.RDF;
+import org.apache.commons.io.IOUtils;
 import org.apache.http.entity.mime.FormBodyPart;
 import org.apache.http.entity.mime.HttpMultipart;
-import org.apache.http.entity.mime.MultipartEntity;
+import org.apache.http.entity.mime.MIME;
 import org.apache.http.entity.mime.content.AbstractContentBody;
 import org.apache.http.entity.mime.content.ContentBody;
 import org.apache.http.entity.mime.content.ContentDescriptor;
-import org.apache.http.entity.mime.content.InputStreamBody;
+import org.apache.http.entity.mime.content.StringBody;
 import org.apache.stanbol.commons.web.base.ContextHelper;
+import org.apache.stanbol.commons.web.base.writers.JsonLdSerializerProvider;
+import org.apache.stanbol.enhancer.jersey.utils.EnhancementPropertiesHelper;
 import org.apache.stanbol.enhancer.servicesapi.Blob;
 import org.apache.stanbol.enhancer.servicesapi.ContentItem;
-import org.apache.stanbol.enhancer.servicesapi.helper.ExecutionMetadataHelper;
-import org.apache.stanbol.enhancer.servicesapi.helper.ExecutionPlanHelper;
-import org.apache.stanbol.enhancer.servicesapi.helper.InMemoryBlob;
-import org.apache.stanbol.enhancer.servicesapi.helper.InMemoryContentItem;
-
-import com.sun.jersey.core.util.StringKeyIgnoreCaseMultivaluedMap;
+import org.apache.stanbol.enhancer.servicesapi.helper.ContentItemHelper;
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
 
 @Provider
 public class ContentItemWriter implements MessageBodyWriter<ContentItem> {
 
-    protected ServletContext servletContext;
+    /**
+     * The "multipart/*" wilrcard
+     */
+    private static final MediaType MULTIPART = MediaType.valueOf(MULTIPART_FORM_DATA_TYPE.getType()+"/*");
+    private static final String CONTENT_ITEM_BOUNDARY = "contentItem";
+    private static final Charset UTF8 = Charset.forName("UTF-8");
+    private static final MediaType DEFAULT_RDF_FORMAT = new MediaType(
+        APPLICATION_JSON_TYPE.getType(),
+        APPLICATION_JSON_TYPE.getSubtype(),
+        Collections.singletonMap("charset", UTF8.toString()));
+    
+    private Serializer __serializer;
     
-    protected Serializer serializer;
+    private ServletContext context;
 
-    public ContentItemWriter(@Context ServletContext servletContext){
-        this.servletContext = servletContext;
-        if(servletContext != null){
-            serializer = ContextHelper.getServiceFromContext(Serializer.class, servletContext);
-        } else {
-            serializer = new Serializer();
-            serializer.bindSerializingProvider(new JenaSerializerProvider());
+    public ContentItemWriter(@Context ServletContext context){
+        this.context = context;
+    }
+    /**
+     * Lazzy initialisation for the {@link Serializer}
+     * @return the {@link Serializer}
+     */
+    protected final Serializer getSerializer(){
+        /*
+         * Needed because Jersey tries to create an instance
+         * during initialisation. At that time the {@link BundleContext} required
+         * by {@link ContextHelper#getServiceFromContext(Class, ServletContext)}
+         * is not yet present resulting in an Exception.
+         */
+        if(__serializer == null){
+            if(context != null){
+                __serializer = ContextHelper.getServiceFromContext(Serializer.class, context);
+            } else {
+                __serializer = new Serializer();
+                __serializer.bindSerializingProvider(new JenaSerializerProvider());
+                __serializer.bindSerializingProvider(new JsonLdSerializerProvider());
+            }
         }
+        return __serializer;
     }
     
     @Override
     public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
-        return MediaType.MULTIPART_FORM_DATA_TYPE.isCompatible(mediaType) &&
+        return //MediaType.MULTIPART_FORM_DATA_TYPE.isCompatible(mediaType) &&
                 ContentItem.class.isAssignableFrom(type);
     }
 
@@ -104,39 +156,263 @@ public class ContentItemWriter implement
                         MultivaluedMap<String,Object> httpHeaders,
                         OutputStream entityStream) throws IOException, WebApplicationException {
 
-        String boundary = "contentItem";
-        String charsetName = mediaType.getParameters().get("charset");
-        if(charsetName == null){
-            charsetName = "UTF-8";
-        }
-        Charset charset = Charset.forName(charsetName);
-        String contentType = String.format("%s/%s; charset=%s; boundary=%s",
-            mediaType.getType(),mediaType.getSubtype(),charset.toString(),boundary);
-        HttpMultipart entity = new HttpMultipart("from-data", charset,boundary);
-        entity.addBodyPart(new FormBodyPart("metadata", new ClerezzaContentBody(
-            ci.getUri().getUnicodeString(), ci.getMetadata(),
-            //TODO: find a way to parse the intended RDF serialisation format
-            SupportedFormat.RDF_XML)));
-        
-        HttpMultipart content = new HttpMultipart("alternate", Charset.forName("UTF-8"),"contentParts");
-        for(Entry<UriRef,Blob> entry : getContentParts(ci,Blob.class).entrySet()){
-            content.addBodyPart(new FormBodyPart(entry.getKey().getUnicodeString(), 
-                new InputStreamBody(
-                    entry.getValue().getStream(),
-                    getMimeTypeWithParameters(entry.getValue()),
-                    null))); //no file name
-        }
-        //add all the blobs
-        entity.addBodyPart(new FormBodyPart("content",new MultipartContentBody(content, null)));
-        //add additional metadata stored in contentParts
-        for(Entry<UriRef,TripleCollection> entry : getContentParts(ci, TripleCollection.class).entrySet()){
-            entity.addBodyPart(new FormBodyPart(entry.getKey().getUnicodeString(), 
-                new ClerezzaContentBody(null, //no file name
-                    entry.getValue(),SupportedFormat.RDF_XML)));
-        }
-        entity.writeTo(entityStream);
-        httpHeaders.put(HttpHeaders.CONTENT_TYPE, 
-            Collections.singletonList((Object)contentType));
+        //(0) handle default dataType
+        Map<String,Object> properties = getEnhancementProperties(ci);
+        boolean omitMetadata = isOmitMetadata(properties);
+        if(!MULTIPART.isCompatible(mediaType)){ //two possible cases
+            if(!omitMetadata){ //  (1) just return the RDF data
+                //(1.a) Backward support for default dataType if no Accept header is set
+                if (mediaType.isWildcardType() || 
+                        TEXT_PLAIN_TYPE.isCompatible(mediaType) || 
+                        APPLICATION_OCTET_STREAM_TYPE.isCompatible(mediaType)) {
+                    mediaType = new MediaType(APPLICATION_JSON_TYPE.getType(), 
+                        APPLICATION_JSON_TYPE.getSubtype(),
+                        //Clerezza serialisers are hard coded to use UTF-8
+                        Collections.singletonMap("charset", UTF8.toString()));
+                    httpHeaders.putSingle("Content-Type", mediaType.toString());
+                }
+                getSerializer().serialize(entityStream, ci.getMetadata(), mediaType.toString());
+            } else { //  (2) return a single content part
+                Entry<UriRef,Blob> contentPart = getBlob(ci, Collections.singleton(mediaType.toString()));
+                if(contentPart == null){ //no alternate content with the requeste media type
+                    throw new WebApplicationException(
+                        Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE)
+                        .entity("The requested enhancement chain has not created an " +
+                        		"version of the parsed content in the reuqest media " +
+                        		"type "+mediaType.toString())
+                        .build());
+                } else { //found -> stream the content to the client
+                    //NOTE: This assumes that the presence of a charset
+                    //      implies reading/writing character streams
+                    String requestedCharset = mediaType.getParameters().get("charset");
+                    String blobCharset = contentPart.getValue().getParameter().get("charset");
+                    Charset readerCharset = blobCharset == null ? UTF8 : Charset.forName(blobCharset);
+                    Charset writerCharset = requestedCharset == null ? null : Charset.forName(requestedCharset);
+                    if(writerCharset != null && !writerCharset.equals(readerCharset)){
+                        //we need to transcode
+                        Reader reader = new InputStreamReader(
+                            contentPart.getValue().getStream(),readerCharset);
+                        Writer writer = new OutputStreamWriter(entityStream, writerCharset);
+                        IOUtils.copy(reader, writer);
+                        IOUtils.closeQuietly(reader);
+                    } else { //no transcoding
+                        if(requestedCharset == null && blobCharset != null){
+                            httpHeaders.putSingle(HttpHeaders.CONTENT_TYPE, 
+                                mediaType.toString()+"; charset="+blobCharset);
+                        }
+                        InputStream in  = contentPart.getValue().getStream();
+                        IOUtils.copy(in, entityStream);
+                        IOUtils.closeQuietly(in);
+                    }
+                }
+            }
+        } else { // multipart mime requested!
+            final String charsetName = mediaType.getParameters().get("charset");
+            final Charset charset = charsetName != null ? Charset.forName(charsetName) : UTF8;
+            MediaType rdfFormat;
+            String rdfFormatString = EnhancementPropertiesHelper.getRdfFormat(properties);
+            if(rdfFormatString == null || rdfFormatString.isEmpty()){
+                rdfFormat = DEFAULT_RDF_FORMAT;
+            } else {
+                try {
+                    rdfFormat = MediaType.valueOf(rdfFormatString);
+                    if(rdfFormat.getParameters().get("charset") == null){
+                        //use the charset of the default RDF format
+                        rdfFormat = new MediaType(
+                            rdfFormat.getType(), rdfFormat.getSubtype(), 
+                            DEFAULT_RDF_FORMAT.getParameters());
+                    }
+                } catch (IllegalArgumentException e) {
+                    throw new WebApplicationException(e, 
+                        Response.status(Response.Status.BAD_REQUEST)
+                        .entity(String.format("The specified RDF format '%s' (used "
+                            + " to serialize all RDF parts of multipart MIME responses)"
+                            + " is not a well formated MIME type",rdfFormatString))
+                        .build());
+                }
+            }
+            //(1) setting the correct header
+            String contentType = String.format("%s/%s; charset=%s; boundary=%s",
+                mediaType.getType(),mediaType.getSubtype(),charset.toString(),CONTENT_ITEM_BOUNDARY);
+            httpHeaders.putSingle(HttpHeaders.CONTENT_TYPE,contentType);
+            HttpMultipart entity = new HttpMultipart("from-data", charset ,CONTENT_ITEM_BOUNDARY);
+            //(2) serialising the metadata
+            if(!isOmitMetadata(properties)){
+                entity.addBodyPart(new FormBodyPart("metadata", new ClerezzaContentBody(
+                    ci.getUri().getUnicodeString(), ci.getMetadata(),
+                    rdfFormat)));
+            }
+            //(3) serialising the Content (Bloby)
+            //(3.a) Filter based on parameter
+            List<Entry<UriRef,Blob>> includedBlobs = filterBlobs(ci, properties);
+            //(3.b) Serialise the filtered
+            if(!includedBlobs.isEmpty()) {
+                HttpMultipart content = new HttpMultipart("alternate", UTF8 ,"contentParts");
+                for(Entry<UriRef,Blob> entry : includedBlobs){
+                    content.addBodyPart(new FormBodyPart(entry.getKey().getUnicodeString(), 
+                        new BlobContentBody(entry.getValue()))); //no file name
+                }
+                //add all the blobs
+                entity.addBodyPart(new FormBodyPart("content",new MultipartContentBody(content, null)));
+            } //else no content to include
+            Set<String> includeContentParts = getIncludedContentPartURIs(properties);
+            if(includeContentParts != null){
+                //(4) serialise EnhancementProperties
+                if(includeContentParts.isEmpty() || includeContentParts.contains(
+                    ENHANCEMENT_PROPERTIES_URI.getUnicodeString())) {
+                    JSONObject object;
+                    try {
+                        object = toJson(properties);
+                    } catch (JSONException e) {
+                        throw new WebApplicationException(e,
+                            Response.status(Response.Status.INTERNAL_SERVER_ERROR)
+                            .entity("Unable to convert EnhancementProperties to " +
+                            		"JSON (values : "+properties+")!").build());
+                    }
+                    entity.addBodyPart(new FormBodyPart(
+                        ENHANCEMENT_PROPERTIES_URI.getUnicodeString(), 
+                        new StringBody(object.toString(),MediaType.APPLICATION_JSON,UTF8)));
+                }
+                //(5) additional RDF metadata stored in contentParts
+                for(Entry<UriRef,TripleCollection> entry : getContentParts(ci, TripleCollection.class).entrySet()){
+                    if(includeContentParts.isEmpty() || includeContentParts.contains(
+                        entry.getKey())){
+                        entity.addBodyPart(new FormBodyPart(entry.getKey().getUnicodeString(), 
+                            new ClerezzaContentBody(null, //no file name
+                                entry.getValue(),rdfFormat)));
+                    } // else ignore this content part
+                }
+            }
+            entity.writeTo(entityStream);
+        }   
+            
+    }
+    /**
+     * @param properties
+     * @return
+     */
+    private JSONObject toJson(Map<?,?> map) throws JSONException {
+        JSONObject object = new JSONObject();
+        for(Entry<?,?> entry : map.entrySet()){
+            Object value = getValue(entry.getValue());
+            object.put(entry.getKey().toString(),value);
+        }
+        return object;
+    }
+    /**
+     * @param entry
+     * @return
+     * @throws JSONException
+     */
+    private Object getValue(Object javaValue) throws JSONException {
+        Object value;
+        if(javaValue instanceof Collection<?>){
+            value = new JSONArray();
+            for(Object o : (Collection<?>)javaValue){
+                ((JSONArray)value).put(getValue(o));
+            }
+        } else if(javaValue instanceof Map<?,?>){
+            value = toJson((Map<?,?>)javaValue);
+        } else {
+            value = javaValue;
+        }
+        return value;
+    }
+    /**
+     * @param properties
+     * @return
+     */
+    private Set<String> getIncludedContentPartURIs(Map<String,Object> properties) {
+        Collection<String> ocp = getOutputContentParts(properties);
+        if(ocp == null || ocp.isEmpty()){
+            return null;
+        }
+        Set<String> includeContentParts = new HashSet<String>(ocp);
+        if(includeContentParts != null){
+            if(includeContentParts.isEmpty()){ //empty == none
+                includeContentParts = null;
+            } else if (includeContentParts.contains("*")){ // * == all -> empty list
+                includeContentParts = Collections.emptySet();
+            }
+        }
+        return includeContentParts;
+    }
+    /**
+     * @param ci
+     * @param properties
+     * @return
+     */
+    private List<Entry<UriRef,Blob>> filterBlobs(ContentItem ci, Map<String,Object> properties) {
+        final List<Entry<UriRef,Blob>> includedContentPartList;
+        Set<MediaType> includeMediaTypes = getIncludedMediaTypes(properties);
+        if(includeMediaTypes == null){
+            includedContentPartList = Collections.emptyList();
+        } else {
+            includedContentPartList = new ArrayList<Map.Entry<UriRef,Blob>>();
+            Set<String> ignoreContentPartUris = getIgnoredContentURIs(properties);
+            nextContentPartEntry: 
+            for(Entry<UriRef,Blob> entry : getContentParts(ci,Blob.class).entrySet()){
+                if(!ignoreContentPartUris.contains(entry.getKey().getUnicodeString())){
+                    Blob blob = entry.getValue();
+                    MediaType blobMediaType = MediaType.valueOf(blob.getMimeType());
+                    for(MediaType included : includeMediaTypes) {
+                        if(blobMediaType.isCompatible(included)){
+                            includedContentPartList.add(entry);
+                            continue nextContentPartEntry;
+                        }
+                    }
+                } //else ignore this Blob
+            }
+        }
+        return includedContentPartList;
+    }
+    /**
+     * @param properties
+     * @return
+     */
+    private Set<String> getIgnoredContentURIs(Map<String,Object> properties) {
+        Set<String> ignoreContentPartUris = isOmitParsedContent(properties) ?
+                new HashSet<String>(getParsedContentURIs(properties)) : null;
+        if(ignoreContentPartUris == null){
+            ignoreContentPartUris = Collections.emptySet();
+        }
+        return ignoreContentPartUris;
+    }
+    /**
+     * @param properties
+     * @return
+     */
+    private Set<MediaType> getIncludedMediaTypes(Map<String,Object> properties) throws WebApplicationException {
+        Collection<String> includeMediaTypeStrings = getOutputContent(properties);
+        if(includeMediaTypeStrings == null){
+            return null;
+        }
+        Set<MediaType> includeMediaTypes = new HashSet<MediaType>(includeMediaTypeStrings.size());
+        for(String includeString : includeMediaTypeStrings){
+            if(includeString != null){
+                includeString.trim();
+                if(!includeString.isEmpty()){
+                    if("*".equals(includeString)){ //also support '*' for '*/*'
+                        includeMediaTypes.add(WILDCARD_TYPE);
+                    } else {
+                        try {
+                            includeMediaTypes.add(MediaType.valueOf(includeString));
+                        } catch (IllegalArgumentException e){
+                            throw new WebApplicationException(e, 
+                                Response.status(Response.Status.BAD_REQUEST)
+                                .entity("The parsed outputContent parameter "
+                                    + includeMediaTypeStrings +" contain an "
+                                    + "illegal formated MediaType!")
+                                .build());
+                        }
+                    }
+                }
+            }
+        }
+        if(includeMediaTypes.contains(WILDCARD_TYPE)){
+            includeMediaTypes = Collections.singleton(WILDCARD_TYPE);
+        }
+        return includeMediaTypes;
     }
 
     /**
@@ -162,7 +438,7 @@ public class ContentItemWriter implement
 
         @Override
         public String getTransferEncoding() {
-            return "7bit";
+            return MIME.ENC_8BIT;
         }
 
         @Override
@@ -189,23 +465,27 @@ public class ContentItemWriter implement
     private class ClerezzaContentBody extends AbstractContentBody implements ContentBody,ContentDescriptor {
 
         private TripleCollection graph;
+        private String charset;
         private String name;
 
-        protected ClerezzaContentBody(String name, TripleCollection graph, String mimeType){
-            super(mimeType);
+        protected ClerezzaContentBody(String name, TripleCollection graph, MediaType mimeType){
+            super(mimeType.getType()+'/'+mimeType.getSubtype());
+            charset = mimeType.getParameters().get("charset");
+            if(charset == null || charset.isEmpty()){
+                charset = UTF8.toString();
+            }
             this.name = name;
             this.graph = graph;
         }
 
         @Override
         public String getCharset() {
-            return "UTF-8"; //clerezza uses statically UTF-8
+            return charset;
         }
 
         @Override
         public String getTransferEncoding() {
-            // TODO Javadoc says 7bit; constants is MIMETYPE define 8bit and binary
-            return "7bit";
+            return MIME.ENC_8BIT;
         }
 
         @Override
@@ -220,9 +500,46 @@ public class ContentItemWriter implement
 
         @Override
         public void writeTo(OutputStream out) throws IOException {
-            serializer.serialize(out, graph, getMediaType()+'/'+getSubType());
+            getSerializer().serialize(out, graph, getMediaType()+'/'+getSubType());
         }
+    }
+    private class BlobContentBody extends AbstractContentBody {
         
+        private Blob blob;
+
+        public BlobContentBody(Blob blob) {
+            super(blob.getMimeType());
+            this.blob = blob;
+        }
+
+        @Override
+        public String getFilename() {
+            return null;
+        }
+
+        @Override
+        public void writeTo(OutputStream out) throws IOException {
+            InputStream in = blob.getStream();
+            IOUtils.copy(in, out);
+            IOUtils.closeQuietly(in);
+        }
+
+        @Override
+        public String getCharset() {
+            return blob.getParameter().get("charset");
+        }
+
+        @Override
+        public String getTransferEncoding() {
+            return blob.getParameter().get("charset") == null ?
+                    MIME.ENC_BINARY : MIME.ENC_8BIT;
+        }
+
+        @Override
+        public long getContentLength() {
+            return -1;
+        }
         
     }
+    
 }

Modified: incubator/stanbol/trunk/enhancer/jersey/src/main/resources/org/apache/stanbol/enhancer/jersey/templates/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource/index.ftl
URL: http://svn.apache.org/viewvc/incubator/stanbol/trunk/enhancer/jersey/src/main/resources/org/apache/stanbol/enhancer/jersey/templates/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource/index.ftl?rev=1243965&r1=1243964&r2=1243965&view=diff
==============================================================================
--- incubator/stanbol/trunk/enhancer/jersey/src/main/resources/org/apache/stanbol/enhancer/jersey/templates/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource/index.ftl (original)
+++ incubator/stanbol/trunk/enhancer/jersey/src/main/resources/org/apache/stanbol/enhancer/jersey/templates/org/apache/stanbol/enhancer/jersey/resource/EnhancerRootResource/index.ftl Tue Feb 14 14:49:20 2012
@@ -176,7 +176,8 @@ curl -X POST -H "Accept: text/turtle" -H
 <li><code>text/rdf+nt</code> (N-TRIPLES)</li>
 </ul> 
 
-<p> Additional supported QueryParameters:<ul>
+<h3> Additional supported QueryParameters:</h3>
+<ul>
 <li><code>uri={content-item-uri}</code>: By default the URI of the content 
     item being enhanced is a local, non de-referencable URI automatically built 
     out of a hash digest of the binary content. Sometimes it might be helpful 
@@ -190,6 +191,8 @@ curl -X POST -H "Accept: text/turtle" -H
     is <code>false</code>.</li>
 </ul>
 
+<h4>Example</h4>
+
 <p>The following example shows how to send an enhancement request with a
 custom content item URI that will include the execution metadata in the
 response.</p>
@@ -200,6 +203,119 @@ curl -X POST -H "Accept: text/turtle" -H
      "${it.serviceUrl}?uri=urn:fise-example-content-item&executionmetadata=true"
 </pre> 
 
+<h3>MultiPart ContentItem support</h3>
+
+<p>This extension adds support for MultiPart ContentItems to the RESTful API
+of the Stanbol Enhancer. (see also 
+<a href="https://issues.apache.org/jira/browse/STANBOL-481">STANBOL-481</a>)</p>
+<#-- TODO: replace with a link to the Documentation as soon as available on the
+Stanbol homepage. -->
+
+<ul>
+<li><code>outputContentType=[mediaType]</code>: Allows to specify the Mimetypes
+of content included within the Response of the Stanbol Enhancer. This parameter
+supports wild cards (e.g. '*' ... all, 'text/*'' ... all text versions, 
+'text/plain' ... only the plain text version). This parameter can be used
+multiple times.<br>
+Responses to requests with this parameter will be encoded as 
+<code>multipart/from-data</code>. If the "Accept" header of the request is not
+compatible to <code>multipart/from-data</code> it is assumed as a #
+<code>400 BAD_REQUEST</code>. The selected content variants will 
+be included in a content part with the name "content" and the Mimetype 
+<code>multipart/alternate</code>.</li>
+<li><code>omitParsed=[true/false]</code>: Makes only sense in combination with 
+the <code>outputContentType</code> parameter. This allows to exclude all
+content included in the request from the response. A typical combination is
+<code>outputContentType=*/*&omitParsed=true</code>. The default value of this
+parameter is <code>false</code></li>
+<li><code>outputContentPart=[uri/'*']</code>: This parameter allows to
+explicitly include content parts with a specific URI in the response. Currently
+this only supports ContentParts that are stored as RDF graphs.<br>
+See the developer documentation for ContentItems for more information about
+ContentParts.<br>
+Responses to requests with this parameter will be encoded as 
+<code>multipart/from-data</code>. If the "Accept" header of the request is not
+compatible to <code>multipart/from-data</code> it is assumed as a #
+<code>400 BAD_REQUEST</code>. The selected content parts will be included as
+MIME parts. The URI of the part will be used as name. Such parts will be added
+after the "metadata" and the "content" (if present).</li>
+<li><code>omitMetadata=[true/false]</code>: This allows to enable/disable the
+inclusion of the metadata in the response. The default is <code>false</code>.<br>
+Typically <code>omitMetadata=true</code> is used when users want to use the
+Stanbol Enhancer just to get one or more ContentParts as an response. Note that
+Requests that use an <code>Accept: {mimeType}</code> header AND 
+<code>omitMetadata=true</code> will directly return the content verison of 
+<code>{mimeType}</code> and NOT wrap the result as 
+<code>multipart/from-data</code></li>
+<li><code>rdfFormat=[rdfMimeType]</code>: This allows for requests that result
+in <code>multipart/from-data</code> encoded responses to specify the used RDF
+serialization format. Supported formats and defaults are the same as for
+normal Enhancer Requests. 
+</ul>
+
+<p><code>multipart/from-data</code> can also be used as <code>Content-Type</code>
+for requests to parsed multiple content variants or pre-existing metadata
+(such as user tags). See the documentation provided by 
+<a href="https://issues.apache.org/jira/browse/STANBOL-481">STANBOL-481</a>
+for details on how to represent content items as Multipart MIME.</p>
+</p>
+<#-- TODO: replace with a link to the Documentation as soon as available on the
+Stanbol homepage. -->
+
+<h4>Examples</h4>
+
+<p>The following examples show some typical usages of the MultiPart ContentItem
+RESTful API. For better readability the values of the query parameters are
+not URLEncoded.</p>
+
+<p>Return Metadata and transformed Content versions</p>
+<pre>
+curl -v -X POST -H "Accept: multipart/from-data" \
+    -H "Content-type: text/html; charset=UTF-8"  \
+    --data "&lt;html&gt;&lt;body&gt;&lt;p&gt;John Smith was born in London.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;" \
+    "${it.serviceUrl}?outputContent=*/*&omitParsed=true&rdfFormat=application/rdf+xml"
+</pre> 
+<p>This will result in an Response with the mime type 
+<code>"Content-Type: multipart/from-data; charset=UTF-8; boundary=contentItem"</code>
+and the Metadata as well as the plain text version of the parsed HTML document
+as content.</p>
+<pre>
+    --contentItem
+    Content-Disposition: form-data; name="metadata"; filename="urn:content-item-sha1-76e44d4b51c626bbed38ce88370be88702de9341"
+    Content-Type: application/rdf+xml; charset=UTF-8;
+    Content-Transfer-Encoding: 8bit
+
+    &lt;rdf:RDF
+        xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    [..the metadata formatted as RDF+XML..]
+    &lt;/rdf:RDF&gt;
+
+    --contentItem
+    Content-Disposition: form-data; name="content"
+    Content-Type: multipart/alternate; boundary=contentParts; charset=UTF-8
+    Content-Transfer-Encoding: 8bit
+
+    --contentParts
+    Content-Disposition: form-data; name="urn:metaxa:plain-text:2daba9dc-21f6-7ea1-70dd-a2b0d5c6cd08"
+    Content-Type: text/plain; charset=UTF-8
+    Content-Transfer-Encoding: 8bit
+
+    John Smith was born in London.
+    --contentParts--
+
+    --contentItem--
+</pre> 
+
+<p>This request will directly return the text/plain version</p>
+<pre>
+curl -v -X POST -H "Accept: text/plain" \
+    -H "Content-type: text/html; charset=UTF-8" \
+    --data "&lt;html&gt;&lt;body&gt;&lt;p&gt;John Smith was born in London.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;" \
+    "${it.serviceUrl}?omitMetadata=true"
+</pre> 
+<p>The response will be of type <code>text/plain</code> and return the string
+<code>"John Smith was born in London."</code>.
+
 </div>