You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@marmotta.apache.org by ss...@apache.org on 2013/02/22 16:21:37 UTC

[4/37] MARMOTTA-105: renamed packages in marmotta-core

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/io/ImportWebService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/io/ImportWebService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/io/ImportWebService.java
new file mode 100644
index 0000000..c3e4c45
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/io/ImportWebService.java
@@ -0,0 +1,310 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * Licensed 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.marmotta.platform.core.webservices.io;
+
+import org.apache.marmotta.platform.core.api.importer.ImportService;
+import org.apache.marmotta.platform.core.api.task.Task;
+import org.apache.marmotta.platform.core.api.task.TaskInfo;
+import org.apache.marmotta.platform.core.api.task.TaskManagerService;
+import org.apache.marmotta.platform.core.api.triplestore.ContextService;
+import org.apache.marmotta.platform.core.api.user.UserService;
+import org.apache.marmotta.platform.core.exception.io.LMFImportException;
+import org.openrdf.model.URI;
+import org.openrdf.rio.RDFFormat;
+import org.openrdf.rio.Rio;
+import org.slf4j.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A webservice offering functionality to import data from the KiWi knowledge base.
+ * <p/>
+ * User: sschaffe
+ */
+@ApplicationScoped
+@Path("/import")
+public class ImportWebService {
+
+    @Inject
+    private Logger log;
+
+    @Inject
+    private ImportService importService;
+
+    @Inject
+    private ContextService contextService;
+
+    @Inject
+    private TaskManagerService taskManagerService;
+
+    @Inject
+    private UserService userService;
+
+    private static final ThreadGroup IMPORTER_THREADS = new ThreadGroup("asynchronous imports");
+    private static final String TASK_GROUP_NAME = "Imports";
+
+    /**
+     * Return a set of all mime types that are acceptable by the importer.
+     * @return a set of all mime types that are acceptable by the importer.
+     */
+    @GET
+    @Path("/types")
+    @Produces("application/json")
+    public List<String> getTypes(@QueryParam("filename") String filename) {
+        if(filename == null)
+            return new ArrayList<String>(importService.getAcceptTypes());
+        else {
+            List<String> result = new ArrayList<String>();
+            RDFFormat format = Rio.getParserFormatForFileName(filename);
+            if(format != null) {
+                result.addAll(format.getMIMETypes());
+            }
+            return result;
+        }
+    }
+
+    /**
+     * Upload content and import it into the LMF system. The importer is selected based on the Content-Type header
+     * of the HTTP request. Calling the service spawns a separate asynchronous thread. Its status can be queried by
+     * calling the /status webservice.
+     *
+     * @param type the content type of the uploaded content
+     * @param request the request data of the uploaded file
+     * @return OK after starting a thread for importing the data, or error if the import cannot be started
+     * @HTTP 200 if the import was successfully started
+     * @HTTP 412 if the content-type header is not present or contains unsupported mime types
+     */
+    @POST
+    @Path("/upload")
+    public Response uploadData(@HeaderParam("Content-Type") String type, @Context HttpServletRequest request, @QueryParam("context") String context_string) throws IOException, LMFImportException {
+        if(type != null && type.lastIndexOf(';') >= 0) {
+            type = type.substring(0,type.lastIndexOf(';'));
+        }
+        if (type == null || !importService.getAcceptTypes().contains(type)) return Response.status(412).entity("define a valid content-type (types: "+importService.getAcceptTypes()+")").build();
+        final String finalType = type;
+        final InputStream in = request.getInputStream();
+
+        Task t = taskManagerService.createTask(String.format("Upload-Import from %s (%s)", request.getRemoteHost(), finalType), TASK_GROUP_NAME);
+        t.updateMessage("preparing import...");
+        t.updateDetailMessage("type", finalType);
+        try {
+            //create context
+            URI context = getContext(context_string);
+            if (context != null) {
+                t.updateDetailMessage("context", context.toString());
+            }
+
+            t.updateMessage("importing data...");
+            importService.importData(in,finalType, userService.getCurrentUser(), context);
+            t.updateMessage("import complete");
+
+            return Response.ok().entity("import of content successful\n").build();
+        } catch(Exception ex) {
+            log.error("error while importing", ex);
+            return Response.status(500).entity("error while importing: " + ex.getMessage()).build();
+        } finally {
+            taskManagerService.endTask(t);
+        }
+    }
+
+    /**
+     * Fetch content from an external resource and import it into the LMF system. The importer is selected based on
+     * the Content-Type header of the HTTP request. Calling the service spawns a separate asynchronous thread. Its
+     * status can be queried by calling the /status webservice.
+     *
+     * @param type the content type of the uploaded content
+     * @param url an optional URL of a remote resource to import
+     * @return OK if the import was successfully started
+     * @HTTP 200 if the import was successfully started
+     * @HTTP 400 if the URL argument is not valid
+     * @HTTP 412 if the content-type header is not present or contains unsupported mime types
+     * @HTTP 502 if a connection to the URL of the external source cannot be established
+     */
+    @POST
+    @Path("/external")
+    public Response externalData(@HeaderParam("Content-Type") String type, @QueryParam("url") String url, @QueryParam("context") String context_string) throws IOException, LMFImportException {
+        try {
+            log.debug("Received 'external' request for {} with {}%n", type, url);
+            if(type != null && type.lastIndexOf(';') >= 0) {
+                type = type.substring(0,type.lastIndexOf(';'));
+            }
+            if(type==null || !importService.getAcceptTypes().contains(type)) return Response.status(412).entity("define a valid content-type (types: "+importService.getAcceptTypes()+")").build();
+            final URL finalUrl = new URL(url);
+            final URI context = getContext(context_string);
+
+            try {
+                URLConnection con = finalUrl.openConnection();
+                con.connect();
+            } catch(IOException ex) {
+                return Response.status(502).entity("the URL passed as argument cannot be retrieved").build();
+            }
+
+            final String finalType = type;
+            Runnable r = new Runnable() {
+
+                @Override
+                public void run() {
+                    Task task = taskManagerService.createTask("Import from external source", TASK_GROUP_NAME);
+                    task.updateDetailMessage("source", finalUrl.toExternalForm());
+                    task.updateDetailMessage("type", finalType);
+                    if (context != null) {
+                        task.updateDetailMessage("context", context.toString());
+                    }
+                    try {
+                        importService.importData(finalUrl,finalType,userService.getCurrentUser(),context);
+                    } catch(Exception e) {
+                        log.error("exception while asynchronously importing data",e);
+                    } finally {
+                        taskManagerService.endTask(task);
+                    }
+                }
+            };
+
+            Thread t = new Thread(IMPORTER_THREADS, r);
+            t.setName("Import(start:" + new Date() + ",url:" + url + ")");
+            t.setDaemon(true);
+            t.start();
+
+            return Response.ok().entity(String.format("{\"tname\":\"%s\"}", t.getName())).build();
+        } catch(MalformedURLException ex) {
+            return Response.status(400).entity("the URL passed as argument is not valid").build();
+        }
+    }
+
+    /**
+     * Stop the importer thread with the ID passed as query argument.
+     *
+     * @param tname the thread ID of the importer thread to stop
+     * @return OK if thread has been stopped or no longer exists
+     * @throws UnsupportedEncodingException
+     */
+    @DELETE
+    @Path("/cancel")
+    public Response stopArticleXMLImport(@QueryParam("tname")String tname) throws UnsupportedEncodingException {
+        tname = URLDecoder.decode(tname, "utf-8");
+        Thread[] threads = new Thread[IMPORTER_THREADS.activeCount()];
+        IMPORTER_THREADS.enumerate(threads);
+        for(Thread t : threads) {
+            if(t!=null&&t.getName().equals(tname)) {
+                //TODO
+                t.interrupt();
+                return Response.ok().build();
+            }
+        }
+        return Response.ok().entity("thread does not exist or is already stopped").build();
+    }
+
+    /**
+     * Get the status of the importer thread with the ID passed as query argument.
+     *
+     * @param tname the thread ID of the importer thread to query
+     * @return the status of the importer thread as a JSON object
+     * @throws UnsupportedEncodingException
+     */
+    @GET
+    @Path("/status")
+    @Produces("application/json")
+    public Status isActiveImport(@QueryParam("tname")String tname) throws UnsupportedEncodingException {
+        tname = URLDecoder.decode(tname,"utf-8");
+        Thread[] threads = new Thread[IMPORTER_THREADS.activeCount()];
+        IMPORTER_THREADS.enumerate(threads);
+        for(Thread t : threads) {
+            if(t!=null&&t.getName().equals(tname)) {
+                if(t.isAlive())
+                    return new Status(tname,true,"success","import is running");
+                else if(t.isInterrupted()) return new Status(tname,false,"error","import was not successful");
+                return new Status(tname,false,"success","import was successful");
+            }
+        }
+        return new Status(tname,false,"undefined","thread does not exist or is already stopped");
+    }
+
+    @GET
+    @Path("/list")
+    @Produces("application/json")
+    public List<TaskInfo> listRunningImport() throws UnsupportedEncodingException {
+        return taskManagerService.getTasksByGroup().get(TASK_GROUP_NAME);
+        // List<String> running = new LinkedList<String>();
+        // for (Task t : taskManagerService.listTasks(TASK_GROUP_NAME)) {
+        // running.add(String.format("[%d] %s (%s) %.2f%% ETA: %tF %<tT",
+        // t.getId(), t.getName(), t.getStatus(), 100 * t.getRelProgress(),
+        // t.getETA()));
+        // }
+        // return running;
+    }
+
+    private URI getContext(String context_string) {
+        if(context_string != null)
+            return contextService.createContext(context_string);
+        else
+            return contextService.getDefaultContext();
+    }
+
+    protected static class Status {
+        boolean isRunning;
+        String status;
+        String message;
+        String tname;
+
+        Status(String tname, boolean running, String status, String message) {
+            this.tname = tname;
+            isRunning = running;
+            this.message = message;
+            this.status = status;
+        }
+
+        public boolean isRunning() {
+            return isRunning;
+        }
+
+        public void setRunning(boolean running) {
+            isRunning = running;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        public void setMessage(String message) {
+            this.message = message;
+        }
+
+        public String getStatus() {
+            return status;
+        }
+
+        public void setStatus(String status) {
+            this.status = status;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/modules/ModuleWebService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/modules/ModuleWebService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/modules/ModuleWebService.java
new file mode 100644
index 0000000..81fa50d
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/modules/ModuleWebService.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * Licensed 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.marmotta.platform.core.webservices.modules;
+
+import org.apache.marmotta.platform.core.api.modules.ModuleService;
+import org.apache.marmotta.platform.core.model.module.ModuleConfiguration;
+import org.apache.commons.configuration.Configuration;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Give information about modules registered in the system.
+ * <p/>
+ * User: sschaffe
+ */
+@Path("/modules")
+public class ModuleWebService {
+
+    @Inject
+    private ModuleService moduleService;
+
+
+    /**
+     * Return a list of names of the LMF modules that are currently enabled in the LMF installation.
+     *
+     * @return  a JSON list of strings
+     */
+    @Path("/list")
+    @GET
+    @Produces("application/json")
+    public Collection<String> listModules() {
+        return moduleService.listModules();
+    }
+
+
+    /**
+     * Return the configuration of the module identified by the name passed as query argument. The module will be
+     * a map containing the values specified in the kiwi-module.properties file of the module.
+     *
+     * @param moduleName the name of the module for which to return the configuration
+     * @return a map with key/value pairs representing the module configuration as contained in kiwi-module.properties
+     */
+    @Path("/module")
+    @GET
+    @Produces("application/json")
+    public Map<String,Object> getConfiguration(@QueryParam("name") String moduleName) {
+        Configuration cfg = null;
+        try {
+            cfg = moduleService.getModuleConfiguration(URLDecoder.decode(moduleName, "UTF-8")).getConfiguration();
+        } catch (UnsupportedEncodingException e) {
+            return null;
+        }
+        if(cfg != null) {
+            Map<String,Object> result = new HashMap<String, Object>();
+            for(Iterator<String> it = cfg.getKeys() ; it.hasNext(); ) {
+                String key = it.next();
+                result.put(key,cfg.getProperty(key));
+            }
+            return result;
+        } else
+            return null;
+    }
+
+    /**
+     * Return the configuration of the module identified by the name passed as query argument. The
+     * module will be
+     * a map containing the values specified in the kiwi-module.properties file of the module.
+     * 
+     * @return a map with key/value pairs representing the module configuration as contained in
+     *         kiwi-module.properties
+     */
+    @Path("/buildinfo")
+    @GET
+    @Produces("application/json")
+    public Map<String, Map<String, String>> getBuildInfo() {
+        HashMap<String, Map<String, String>> mods = new HashMap<String, Map<String, String>>();
+
+        for (String moduleName : moduleService.listModules()) {
+            Configuration cfg = moduleService.getModuleConfiguration(moduleName).getConfiguration();
+            if (cfg != null) {
+                ModuleConfiguration mCfg = new ModuleConfiguration(cfg);
+                if (mCfg.hasBuildInfo()) {
+                    Map<String, String> result = new LinkedHashMap<String, String>();
+
+                    result.put("id", mCfg.getModuleId());
+                    result.put("version", mCfg.getModuleVersion());
+                    result.put("timestamp", mCfg.getBuildTimestamp());
+                    result.put("revNumber", mCfg.getBuildRevisionNumber());
+                    result.put("revHash", mCfg.getBuildRevisionHash());
+                    result.put("user", mCfg.getBuildUser());
+                    result.put("host", mCfg.getBuildHost());
+                    result.put("os", mCfg.getBuildOS());
+
+                    final List<String> adminPages = moduleService.getAdminPages(moduleName);
+                    if (adminPages != null && adminPages.size() > 0 && adminPages.get(0).trim().length() > 0) {
+                        result.put("admin",
+                                moduleService.getModuleWeb(moduleName) +
+                                adminPages.get(0));
+                    }
+
+                    mods.put(moduleName, result);
+                } else {
+                    mods.put(moduleName, null);
+                }
+            } else {
+                mods.put(moduleName, null);
+            }
+        }
+        return mods;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/prefix/PrefixWebService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/prefix/PrefixWebService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/prefix/PrefixWebService.java
new file mode 100644
index 0000000..aabfdf9
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/prefix/PrefixWebService.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * Licensed 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.marmotta.platform.core.webservices.prefix;
+
+import org.apache.marmotta.platform.core.api.prefix.PrefixService;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Response;
+
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Prefix web service
+ * 
+ * @author Sergio Fernández
+ * 
+ */
+@ApplicationScoped
+@Path("/prefix")
+public class PrefixWebService {
+
+    @Inject
+    private Logger log;
+
+    @Inject
+    private PrefixService prefixService;
+
+    private static final String PREFIX_PATTERN = "{prefix: [a-z][a-zA-Z0-9]+}";
+
+    /**
+     * Get all prefixes mappings
+     * 
+     * @return all current prefixes mappings
+     */
+    @GET
+    @Produces("application/json")
+    public Response getMappings() {
+        Map<String, String> mappings = (prefixService != null ? prefixService.getMappings() : new HashMap<String,String>());
+        return Response.ok().entity(mappings).build();
+    }
+
+    /**
+     * Get namespace
+     * 
+     * @param prefix prefix
+     * @return Response with the mapping, if exists
+     */
+    @GET
+    @Path("/" + PREFIX_PATTERN)
+    @Produces("application/json")
+    public Response getMapping(@PathParam("prefix") String prefix) {
+        if (prefixService.containsPrefix(prefix)) {
+            Map<String, String> result = new HashMap<String, String>();
+            result.put(prefix, prefixService.getNamespace(prefix));
+            return Response.ok().entity(result).build();
+        } else {
+            log.error("prefix " + prefix + " mapping not found");
+            return Response.status(Response.Status.NOT_FOUND).entity("prefix " + prefix + " mapping not found").build();
+        }
+    }
+
+    /**
+     * Add new mapping
+     * 
+     * @param prefix prefix
+     * @param namespace uri
+     * @return operation result
+     */
+    @POST
+    @Path("/" + PREFIX_PATTERN)
+    public Response addMapping(@PathParam("prefix") String prefix, @QueryParam("uri") @NotNull String namespace) {
+        try {
+            prefixService.add(prefix, namespace);
+            return Response.status(Response.Status.CREATED).build();
+        } catch (URISyntaxException e) {
+            return Response.status(Response.Status.NOT_ACCEPTABLE).entity(e.getMessage()).build();
+        } catch (IllegalArgumentException e) {
+            return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
+        }
+    }
+
+    /**
+     * Reverse prefix lookup
+     * 
+     * @param uri namespace
+     * @return Response with the result of the reverse search
+     */
+    @GET
+    @Path("/reverse")
+    @Produces("application/json")
+    public Response getPrefix(@QueryParam("uri") @NotNull String uri) {
+        if (StringUtils.isNotBlank(uri)) {
+            if (prefixService.containsNamespace(uri)) {
+                Map<String, String> result = new HashMap<String, String>();
+                result.put(uri, prefixService.getPrefix(uri));
+                return Response.ok().entity(result).build();
+            } else {
+                log.error("namespace " + uri + " mapping not found");
+                return Response.status(Response.Status.NOT_FOUND).entity("namespace " + uri + " mapping not found").build();
+            }
+        } else {
+            log.error("Empty namespace requested");
+            return Response.status(Response.Status.BAD_REQUEST).build();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/AnonResourceWebService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/AnonResourceWebService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/AnonResourceWebService.java
new file mode 100644
index 0000000..a800eed
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/AnonResourceWebService.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * Licensed 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.marmotta.platform.core.webservices.resource;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.Path;
+
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+
+/**
+ * The AnonResourceWebService offers functionality to work with resources, i.e. creation, updating, retrieval and
+ * deletion. The ids of anonymous resources created by the KiWi system always can be used to reach the resource via
+ * this webservice.
+ * <p>
+ * User: Thomas Kurz
+ * Date: 24.01.11
+ * Time: 12:32
+ */
+@ApplicationScoped
+@Path("/" + ConfigurationService.ANONYMOUS_PATH)
+public class AnonResourceWebService {
+
+	//TODO implement
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/ContentWebService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/ContentWebService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/ContentWebService.java
new file mode 100644
index 0000000..3429c59
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/ContentWebService.java
@@ -0,0 +1,328 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * Licensed 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.marmotta.platform.core.webservices.resource;
+
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+import org.apache.marmotta.platform.core.api.content.ContentService;
+import org.apache.marmotta.platform.core.api.io.LMFIOService;
+import org.apache.marmotta.platform.core.api.triplestore.SesameService;
+import org.apache.marmotta.platform.core.events.ContentCreatedEvent;
+import org.apache.marmotta.platform.core.exception.LMFException;
+import org.apache.marmotta.platform.core.exception.WritingNotSupportedException;
+import org.apache.marmotta.platform.core.qualifiers.event.ContentCreated;
+
+import org.apache.marmotta.commons.sesame.repository.ResourceUtils;
+import org.openrdf.model.Resource;
+import org.openrdf.model.URI;
+import org.openrdf.repository.RepositoryConnection;
+import org.openrdf.repository.RepositoryException;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Event;
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Content Web Services
+ *
+ * @author Thomas Kurz
+ * @author Sergio Fernández
+ */
+@ApplicationScoped
+@Path("/" + ConfigurationService.CONTENT_PATH)
+public class ContentWebService {
+
+    private static final String ENHANCER_STANBOL_ENHANCER_ENABLED = "enhancer.stanbol.enhancer.enabled";
+
+    @Inject
+    private ConfigurationService configurationService;
+
+    @Inject
+    private ContentService contentService;
+
+    @Inject @ContentCreated
+    private Event<ContentCreatedEvent> afterContentCreated;
+
+    @Inject
+    private LMFIOService kiWiIOService;
+
+    @Inject
+    private SesameService sesameService;
+
+    /**
+     * Returns local resource content with the given uuid and an accepted return
+     * type (mimetype)
+     *
+     * @param uuid
+     *            , a unique identifier (must not contain url specific
+     *            characters like /,# etc.)
+     * @param mimetype
+     *            , accepted mimetype follows the pattern .+/.+
+     * @return a local resource's content (body is the resource content in
+     *         requested format)
+     * @HTTP 200 resource content found and returned
+     * @HTTP 404 resource cannot be found
+     * @HTTP 406 resource cannot be found in the given format
+     * @HTTP 500 Internal Error
+     * @ResponseHeader Content-Type (for HTTP 406) available content type (if
+     *                 resource has content)
+     */
+    @GET
+    @Path(ResourceWebService.MIME_PATTERN + ResourceWebService.UUID_PATTERN)
+    public Response getContentLocal(@PathParam("uuid") String uuid, @PathParam("mimetype") String mimetype, @HeaderParam("Range") String range) throws UnsupportedEncodingException {
+        String uri = configurationService.getBaseUri() + "resource/" + uuid;
+        return getContent(uri, mimetype, uuid, range);
+    }
+
+    /**
+     * Returns remote resource content with the given uri and an accepted return
+     * type (mimetype)
+     *
+     * @param uri
+     *            , the fully-qualified URI of the resource to create in the
+     *            triple store
+     * @param mimetype
+     *            , accepted mimetype follows the pattern .+/.+
+     * @return a remote resource's content (body is the resource content in
+     *         requested format)
+     * @HTTP 200 resource content found and returned
+     * @HTTP 400 bad request (maybe uri is not defined)
+     * @HTTP 404 resource cannot be found
+     * @HTTP 406 resource cannot be found in the given format
+     * @HTTP 500 Internal Error
+     * @ResponseHeader Content-Type (for HTTP 406) available content type (if
+     *                 resource has content)
+     */
+    @GET
+    @Path(ResourceWebService.MIME_PATTERN)
+    public Response getContentRemote(@QueryParam("uri") @NotNull String uri, @PathParam("mimetype") String mimetype, @HeaderParam("Range") String range) throws UnsupportedEncodingException {
+        return getContent(URLDecoder.decode(uri, "utf-8"), mimetype, null, range);
+    }
+
+    /**
+     * Sets content to a given locale resource
+     *
+     * @param uuid
+     *            , a unique identifier (must not contain url specific
+     *            characters like /,# etc.)
+     * @param mimetype
+     *            content-type of the body (content) follows the pattern .+/.+
+     * @return HTTP response (success or error)
+     * @HTTP 200 put was successful
+     * @HTTP 400 bad request (e.g. body is empty)
+     * @HTTP 404 resource cannot be found
+     * @HTTP 415 Content-Type is not supported
+     * @HTTP 500 Internal Error
+     */
+    @PUT
+    @Path(ResourceWebService.MIME_PATTERN + ResourceWebService.UUID_PATTERN)
+    public Response putContentLocal(@PathParam("uuid") String uuid, @PathParam("mimetype") String mimetype, @Context HttpServletRequest request) {
+        String uri = configurationService.getBaseUri() + "resource/" + uuid;
+        return putContent(uri, mimetype, request);
+    }
+
+
+    /**
+     * Sets content to a given remote resource
+     *
+     * @param uri
+     *            , the fully-qualified URI of the resource to create in the
+     *            triple store
+     * @param mimetype
+     *            content-type of the body (content) follows the pattern .+/.+
+     * @return HTTP response (success or error)
+     * @HTTP 200 put was successful
+     * @HTTP 400 bad request (e.g. uri is null)
+     * @HTTP 404 resource cannot be found
+     * @HTTP 415 Content-Type is not supported
+     * @HTTP 500 Internal Error
+     */
+    @PUT
+    @Path(ResourceWebService.MIME_PATTERN)
+    public Response putContentRemote(@QueryParam("uri") @NotNull String uri, @PathParam("mimetype") String mimetype, @Context HttpServletRequest request)
+            throws UnsupportedEncodingException {
+        return putContent(URLDecoder.decode(uri, "utf-8"), mimetype, request);
+    }
+
+    /**
+     * Delete content of remote resource with given uri
+     *
+     * @param uri
+     *            , the fully-qualified URI of the resource to create in the
+     *            triple store
+     * @return HTTP response (success or error)
+     * @HTTP 200 resource content deleted
+     * @HTTP 400 bad request (e.g, uri is null)
+     * @HTTP 404 resource or resource content not found
+     */
+    @DELETE
+    public Response deleteContentRemote(@QueryParam("uri") @NotNull String uri) throws UnsupportedEncodingException {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                conn.begin();
+                Resource resource = ResourceUtils.getUriResource(conn,URLDecoder.decode(uri, "utf-8"));
+                if (resource != null) {
+                    if (contentService.deleteContent(resource)) return Response.ok().build();
+                    else
+                        return ResourceWebServiceHelper.buildErrorPage(uri, configurationService.getBaseUri(), Response.Status.NOT_FOUND, "no content found for this resource in LMF right now, but may be available again in the future");
+                }
+                return Response.status(Response.Status.NOT_FOUND).build();
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (LMFException ex) {
+            return Response.serverError().entity(ex.getMessage()).build();
+        } catch (RepositoryException ex) {
+            return Response.serverError().entity(ex.getMessage()).build();
+        }
+    }
+
+    /**
+     * Delete content of local resource with given uuid
+     *
+     * @param uuid
+     *            , a unique identifier (must not contain url specific
+     *            characters like /,# etc.)
+     * @return HTTP response (success or error)
+     * @HTTP 200 resource deleted
+     * @HTTP 404 resource or resource content not found
+     */
+    @DELETE
+    @Path(ResourceWebService.UUID_PATTERN)
+    public Response deleteContentLocal(@PathParam("uuid") String uuid) throws UnsupportedEncodingException {
+        String uri = configurationService.getBaseUri() + "resource/" + uuid;
+        return deleteContentRemote(uri);
+    }
+
+    private Response getContent(String uri, String mimetype, String uuid, String range) throws UnsupportedEncodingException {
+        try {
+            // FIXME String appendix = uuid == null ? "?uri=" + URLEncoder.encode(uri, "utf-8") :
+            // "/" + uuid;
+            final RepositoryConnection conn = sesameService.getConnection();
+            try {
+                conn.begin();
+                URI resource = ResourceUtils.getUriResource(conn, uri);
+                conn.commit();
+                if (contentService.hasContent(resource, mimetype)) {
+
+                    InputStream is = contentService.getContentStream(resource, mimetype);
+                    long length = contentService.getContentLength(resource,mimetype);
+
+                    // build response
+                    Response response = null;
+                    long fromL = 0;
+                    if(range != null) {
+                        response = Response.status(206).entity(is).build();
+                        Pattern p = Pattern.compile("bytes=([0-9]+)-([0-9]+)?");
+                        Matcher m = p.matcher(range);
+                        if(m.matches()) {
+                            String from = m.group(1);
+                            try {
+                                fromL = Long.parseLong(from);
+                                is.skip(fromL);
+                                response.getMetadata().add("Content-Range","bytes "+fromL+"-"+(length-1)+"/"+length);
+                            } catch(NumberFormatException ex) {
+                                response.getMetadata().add("Content-Range","bytes 0-"+(length-1)+"/"+length);
+                            }
+                        } else {
+                            response.getMetadata().add("Content-Range","bytes 0-"+(length-1)+"/"+length);
+                        }
+                        response.getMetadata().add("Accept-Ranges","bytes");
+                    } else {
+                        response = Response.ok(is).build();
+                        response.getMetadata().add("Accept-Ranges","bytes");
+                    }
+
+                    if(mimetype.startsWith("text") || mimetype.startsWith("application/json")) {
+                        // Content-Encoding is not what it seems, known values are: gzip, compress,
+                        // deflate, identity
+                        // response.getMetadata().add("Content-Encoding", "utf-8");
+                        response.getMetadata().add("Content-Type", mimetype + "; charset=utf-8");
+                    } else {
+                        response.getMetadata().add("Content-Type", mimetype);
+                    }
+                    if(length > 0) {
+                        response.getMetadata().add("Content-Length",length-fromL);
+                    }
+
+                    // append data links
+                    String s = ResourceWebServiceHelper.buildMetaLinks(resource, uuid, kiWiIOService.getProducedTypes(), configurationService);
+                    if (s != null) {
+                        response.getMetadata().add("Links", s);
+                    }
+                    return response;
+                } else {
+                    Response response = ResourceWebServiceHelper.buildErrorPage(uri, configurationService.getBaseUri(), Status.NOT_ACCEPTABLE, "no content for mimetype " + mimetype);
+                    ResourceWebServiceHelper.addHeader(response, "Content-Type", ResourceWebServiceHelper.appendContentTypes(contentService.getContentType(resource)));
+                    return response;
+                }
+            } finally {
+                conn.close();
+            }
+        } catch (IOException e) {
+            return Response.serverError().entity(e.getMessage()).build();
+        } catch (RepositoryException e) {
+            return Response.serverError().entity(e.getMessage()).build();
+        }
+    }
+
+    public Response putContent(String uri, String mimetype, HttpServletRequest request) {
+        try {
+            final RepositoryConnection conn = sesameService.getConnection();
+            try {
+                conn.begin();
+                URI resource = ResourceUtils.getUriResource(conn, uri);
+                conn.commit();
+                return putContent(resource, mimetype, request);
+            } finally {
+                conn.close();
+            }
+        } catch (RepositoryException e) {
+            return Response.serverError().entity(e.getMessage()).build();
+        }
+    }
+
+    public Response putContent(URI resource, String mimetype, HttpServletRequest request) {
+        try {
+            if (request.getContentLength() == 0)
+                return ResourceWebServiceHelper.buildErrorPage(resource.stringValue(), configurationService.getBaseUri(), Status.BAD_REQUEST, "content may not be empty for writting");
+            contentService.setContentStream(resource, request.getInputStream(), mimetype); // store content
+            if(configurationService.getBooleanConfiguration(ENHANCER_STANBOL_ENHANCER_ENABLED, false)) {
+                afterContentCreated.fire(new ContentCreatedEvent(resource)); //enhancer
+            }
+            return Response.ok().build();
+        } catch (IOException e) {
+            return ResourceWebServiceHelper.buildErrorPage(resource.stringValue(), configurationService.getBaseUri(), Status.BAD_REQUEST, "could not read request body");
+        } catch (WritingNotSupportedException e) {
+            return ResourceWebServiceHelper.buildErrorPage(resource.stringValue(), configurationService.getBaseUri(), Status.FORBIDDEN, "writting this content is not supported");
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/InspectionWebService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/InspectionWebService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/InspectionWebService.java
new file mode 100644
index 0000000..f3ba215
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/InspectionWebService.java
@@ -0,0 +1,549 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * Licensed 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.marmotta.platform.core.webservices.resource;
+
+import static org.apache.marmotta.commons.sesame.repository.ResourceUtils.getAnonResource;
+import static org.apache.marmotta.commons.sesame.repository.ResourceUtils.getLabel;
+import static org.apache.marmotta.commons.sesame.repository.ResourceUtils.getUriResource;
+import static org.apache.marmotta.commons.sesame.repository.ResourceUtils.listOutgoing;
+import static org.apache.marmotta.commons.sesame.repository.ExceptionUtils.handleRepositoryException;
+
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+import org.apache.marmotta.platform.core.api.content.ContentService;
+import org.apache.marmotta.platform.core.api.triplestore.SesameService;
+
+import org.apache.marmotta.commons.sesame.model.Namespaces;
+import org.apache.marmotta.kiwi.model.rdf.KiWiResource;
+import org.apache.marmotta.kiwi.model.rdf.KiWiTriple;
+import org.openrdf.model.*;
+import org.openrdf.repository.RepositoryConnection;
+import org.openrdf.repository.RepositoryException;
+import org.openrdf.repository.RepositoryResult;
+
+import javax.inject.Inject;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.*;
+
+@Path("/" + ConfigurationService.INSPECT_PATH)
+public class InspectionWebService {
+
+    private static final String  UUID_PATTERN = "/{uuid:[^#?]+}";
+    private static final String  CHARSET      = "utf-8";
+    private static final String  DEFAULT_BATCH_SIZE = "20";
+
+    @Inject
+    private ConfigurationService configurationService;
+
+    @Inject
+    private SesameService        sesameService;
+
+    @Inject
+    private ContentService       contentService;
+
+    @GET
+    @Path("/subject")
+    @Produces(Namespaces.MIME_TYPE_JSON)
+    public List<TriplePoJo> listSubjectUsage(@QueryParam("uri") String uri, @QueryParam("start") @DefaultValue("0") long offset,
+            @QueryParam("limit") @DefaultValue(DEFAULT_BATCH_SIZE) int batchSize) {
+
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                conn.begin();
+                URI r = getUriResource(conn, uri);
+                if (r != null) return
+                        buildResultList(conn, r, null, null, null, offset, batchSize);
+                else
+                    return Collections.emptyList();
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch(RepositoryException ex) {
+            handleRepositoryException(ex,InspectionWebService.class);
+            return Collections.emptyList();
+        }
+    }
+
+    @GET
+    @Path("/predicate")
+    @Produces(Namespaces.MIME_TYPE_JSON)
+    public List<TriplePoJo> listPredicatetUsage(@QueryParam("uri") String uri, @QueryParam("start") @DefaultValue("0") long offset,
+            @QueryParam("limit") @DefaultValue(DEFAULT_BATCH_SIZE) int batchSize) {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                conn.begin();
+                URI r = getUriResource(conn, uri);
+                if (r != null)
+                    return buildResultList(conn, null, r, null, null, offset, batchSize);
+                else
+                    return Collections.emptyList();
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch(RepositoryException ex) {
+            handleRepositoryException(ex,InspectionWebService.class);
+            return Collections.emptyList();
+        }
+    }
+
+
+    @GET
+    @Path("/object")
+    @Produces(Namespaces.MIME_TYPE_JSON)
+    public List<TriplePoJo> listObjectUsage(@QueryParam("uri") String uri, @QueryParam("start") @DefaultValue("0") long offset,
+            @QueryParam("limit") @DefaultValue(DEFAULT_BATCH_SIZE) int batchSize) {
+
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                conn.begin();
+                URI r = getUriResource(conn, uri);
+                if (r != null)
+                    return buildResultList(conn, null, null, r, null, offset, batchSize);
+                else
+                    return Collections.emptyList();
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch(RepositoryException ex) {
+            handleRepositoryException(ex,InspectionWebService.class);
+            return Collections.emptyList();
+        }
+    }
+
+    @GET
+    @Path("/context")
+    @Produces(Namespaces.MIME_TYPE_JSON)
+    public List<TriplePoJo> listContextUsage(@QueryParam("uri") String uri, @QueryParam("start") @DefaultValue("0") long offset,
+            @QueryParam("limit") @DefaultValue(DEFAULT_BATCH_SIZE) int batchSize) {
+
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                conn.begin();
+                URI r = getUriResource(conn, uri);
+                if (r != null)
+                    return buildResultList(conn, null, null, null, r, offset, batchSize);
+                else
+                    return Collections.emptyList();
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch(RepositoryException ex) {
+            handleRepositoryException(ex,InspectionWebService.class);
+            return Collections.emptyList();
+        }
+    }
+
+    private List<TriplePoJo> buildResultList(RepositoryConnection conn, URI s, URI p, URI o, URI c, long start, int limit) throws RepositoryException {
+        List<TriplePoJo> result = new ArrayList<InspectionWebService.TriplePoJo>();
+        RepositoryResult<Statement> triples = conn.getStatements(s,p,o,true,c);
+        // skip until start
+        for(int i = 0; i<start && triples.hasNext(); i++) {
+            triples.next();
+        }
+        // retrieve until limit
+        for(int i=0; i<limit && triples.hasNext(); i++) {
+            result.add(new TriplePoJo(triples.next()));
+        }
+        triples.close();
+        return result;
+    }
+
+    protected static class TriplePoJo {
+        private final long id;
+        private final String s, p, o, c;
+
+        public TriplePoJo(Statement t) {
+            if(t instanceof KiWiTriple) {
+                id = ((KiWiTriple)t).getId();
+            } else {
+                id = 0;
+            }
+            s = t.getSubject().toString();
+            p = t.getPredicate().toString();
+            o = t.getObject().toString();
+            c = t.getContext().toString();
+        }
+
+        public long getId() {
+            return id;
+        }
+
+        public String getS() {
+            return s;
+        }
+
+        public String getP() {
+            return p;
+        }
+
+        public String getO() {
+            return o;
+        }
+
+        public String getC() {
+            return c;
+        }
+    }
+
+    @GET
+    @Path(UUID_PATTERN)
+    @Produces("text/html")
+    public Response inspectLocalResource(@PathParam("uuid") String uuid, @QueryParam("sOffset") @DefaultValue("0") long sOffset,
+            @QueryParam("pOffset") @DefaultValue("0") long pOffset, @QueryParam("oOffset") @DefaultValue("0") long oOffset,
+            @QueryParam("cOffset") @DefaultValue("0") long cOffset, @QueryParam("limit") @DefaultValue(DEFAULT_BATCH_SIZE) int batchSize)
+                    throws UnsupportedEncodingException {
+        String uri = configurationService.getBaseUri() + ConfigurationService.RESOURCE_PATH + "/" + uuid;
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                conn.begin();
+                Resource rsc = getUriResource(conn, uri);
+                if (rsc == null) {
+                    rsc = getAnonResource(conn, uuid);
+                }
+                if (rsc == null)
+                    return Response.status(Status.NOT_FOUND).entity("Not found: " + uuid).build();
+                else
+                    return inspectResource(conn, rsc, sOffset, pOffset, oOffset, cOffset, batchSize);
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch(RepositoryException ex) {
+            handleRepositoryException(ex,InspectionWebService.class);
+            return Response.serverError().entity(ex.getMessage()).build();
+        }
+    }
+
+    @GET
+    @Path("/")
+    @Produces("text/html")
+    public Response inspectRemoteResource(@QueryParam("uri") String uri, @QueryParam("sOffset") @DefaultValue("0") long sOffset,
+            @QueryParam("pOffset") @DefaultValue("0") long pOffset, @QueryParam("oOffset") @DefaultValue("0") long oOffset,
+            @QueryParam("cOffset") @DefaultValue("0") long cOffset, @QueryParam("limit") @DefaultValue(DEFAULT_BATCH_SIZE) int batchSize)
+                    throws UnsupportedEncodingException {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                conn.begin();
+                Resource rsc = getUriResource(conn, uri);
+                if (rsc == null)
+                    return Response.status(Status.NOT_FOUND).entity("Not found: " + uri).build();
+                else
+                    return inspectResource(conn, rsc, sOffset, pOffset, oOffset, cOffset, batchSize);
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch(RepositoryException ex) {
+            handleRepositoryException(ex,InspectionWebService.class);
+            return Response.serverError().entity(ex.getMessage()).build();
+        }
+    }
+
+    private Response inspectResource(RepositoryConnection conn, Resource rsc, long subjOffset, long propOffset, long objOffset, long ctxOffset, int limit)
+            throws UnsupportedEncodingException, RepositoryException {
+        if (rsc == null)
+            return Response.status(Status.NOT_FOUND).entity("Not found").build();
+        URI uri = null;
+        if (rsc instanceof URI) {
+            uri = (URI) rsc;
+        }
+        // Well, this is rather hacky...
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(os);
+
+        ps.println("<!DOCTYPE HTML><html><head>");
+
+        ps.printf("<title>Inspect %s</title>%n", rsc.stringValue());
+        ps.println("<style type='text/css'>");
+        ps.println("table {border: 2px solid #006d8f;width: 100%;border-collapse: collapse;}"
+                + "html, body{font-family: sans-serif;}"
+                + "table th {background-color: #006d8f;color: white;}"
+                + "table tr {}"
+                + "table tr.even {background-color: #dff7ff;}"
+                + "table tr.odd {background-color: lightBlue;}"
+                + "table th, table td {padding: 2px;}"
+                + "table tr:hover {background-color: white;}"
+                + ".isDeleted { color: red; font-style: italic; }"
+                + ".deleted {width: 20px; height: 20px; display: inline-block; border-radius: 10px; float: right}"
+                + ".deleted.true {background-color: red;}"
+                + ".deleted.false {background-color: darkGreen;}"
+                + ".reasoned a { border-radius: 10px; display: inline-block; float: right; background-color: orange; width: 20px; height: 20px; color: white; text-decoration: none; margin-right: 5px; text-align: center; font-family: serif; font-weight: bolder;}"
+                + ".info-dialog { border:1px solid black; width:50%; position:absolute; top:100px; left:25%; background-color:white; z-index:2; padding-top:10px; min-height:100px; overflow:auto; display:none;}"
+                + ".info-dialog button.close-button { position:absolute; top:5px; right:5px; } "
+                + ".info-dialog iframe { border:none; width:100%; }");
+
+        ps.println("</style>");
+
+        ps.println();
+        ps.println("</head><body>");
+
+        ps.printf("<h1>Inspect %s</h1>%n<div>ShortName: %s<span class='%s'>%<s</span></div>%n", rsc.stringValue(), getLabel(conn,rsc), "");
+        if(rsc instanceof KiWiResource) {
+            ps.printf("<div>Created: <span>%tF %<tT</span> by <span>%s</span></div>%n", ((KiWiResource)rsc).getCreated(), createInspectLink(conn, null, null, ""));
+        }
+        ps.printf("<div>Last Modified: <span>%tF %<tT</span></div>%n", lastModified(conn, rsc));
+
+        // Outgoing
+        printOutgoing(conn, rsc, ps, subjOffset, limit);
+
+        // Incoming
+        printIncoming(conn, rsc, ps, objOffset, limit);
+
+        // Links
+        printLinks(conn, uri, ps, propOffset, limit);
+
+        // Context
+        printContext(conn, uri, ps, ctxOffset, limit);
+
+        ps.println();
+        ps.println("</body></html>");
+        return Response.ok().header("Content-Type", "text/html;charset=" + CHARSET).header("Last-Modified", lastModified(conn, rsc))
+                .entity(os.toString(CHARSET)).build();
+    }
+
+    protected void printLinks(RepositoryConnection conn, URI rsc, PrintStream ps, long offset, int limit) throws UnsupportedEncodingException, RepositoryException {
+        if (rsc == null) return;
+        int i = 0;
+        RepositoryResult<Statement> ts = conn.getStatements(null, rsc, null, true);
+
+        // skip until start
+        for(int j = 0; j<offset && ts.hasNext(); j++) {
+            ts.next();
+        }
+        if (ts.hasNext()) {
+            Set<Statement> triples = new HashSet<Statement>();
+            for(int j = 0; j<limit && ts.hasNext(); j++) {
+                triples.add(ts.next());
+            }
+
+            ps.printf("<h2>Connections</h2>%n");
+            ps.printf("<span class='count'>Showing %d links:</span> %s%n", triples.size(), createInspectLink(conn, rsc, "next", "&pOffset=" + (offset + limit)));
+
+            ps.printf("<table>%n  <thead><tr><th>Source</th><th>Target</th><th>Context</th><th></th><th></th></tr></thead>%n  <tbody>%n");
+            for (Statement t : triples) {
+                ps.printf("    <tr class='%s'><td>%s</td><td>%s</td><td>%s</td><td><span class='reasoned'>%s</span></td><td><span class='deleted %b'></span></td></tr>%n",
+                        i++ % 2 == 0 ? "even" : "odd", createInspectLink(conn, t.getSubject()), createInspectLink(conn,t.getObject()), createInspectLink(conn,t.getContext()),
+                                t instanceof KiWiTriple && ((KiWiTriple)t).isInferred() ? createInfo(((KiWiTriple)t).getId()) : "", t instanceof KiWiTriple && ((KiWiTriple)t).isDeleted());
+            }
+            ps.printf("  </tbody>%n</table>");
+        } else if (offset > 0) {
+            ps.printf("<h2>Connections</h2>%n");
+            ps.printf("<span class='count'>Less than %d links, reset offset</span>%n", offset);
+        }
+        ts.close();
+    }
+
+    protected void printIncoming(RepositoryConnection conn, Resource rsc, PrintStream ps, long offset, int limit) throws UnsupportedEncodingException, RepositoryException {
+        int i = 0;
+        RepositoryResult<Statement> ts = conn.getStatements(null, null, rsc, true);
+
+        // skip until start
+        for(int j = 0; j<offset && ts.hasNext(); j++) {
+            ts.next();
+        }
+        if (ts.hasNext()) {
+            Set<Statement> triples = new HashSet<Statement>();
+            for(int j = 0; j<limit && ts.hasNext(); j++) {
+                triples.add(ts.next());
+            }
+
+            ps.printf("<h2>Incoming Links</h2>%n");
+            ps.printf("<span class='count'>Showing %d incoming links:</span> %s%n", triples.size(), createInspectLink(conn,rsc, "next", "&oOffset=" + (offset + limit)));
+            ps.printf("<table>%n  <thead><tr><th>Source</th><th>Link</th><th>Context</th><th></th><th></th></tr></thead>%n  <tbody>%n");
+            for (Statement t : triples) {
+                ps.printf(
+                        "    <tr class='%s'><td>%s</td><td>%s</td><td>%s</td><td><span class='reasoned'>%s</span></td><td><span class='deleted %b'></span></td></tr>%n",
+                        i++ % 2 == 0 ? "even" : "odd", createInspectLink(conn, t.getSubject()), createInspectLink(conn,t.getObject()), createInspectLink(conn,t.getContext()),
+                                t instanceof KiWiTriple && ((KiWiTriple)t).isInferred() ? createInfo(((KiWiTriple)t).getId()) : "", t instanceof KiWiTriple && ((KiWiTriple)t).isDeleted());
+            }
+            ps.printf("  </tbody>%n</table>");
+        } else if (offset > 0) {
+            ps.printf("<h2>Incoming Links</h2>%n");
+            ps.printf("<span class='count'>No more incoming links at offset %d</span>%n", offset);
+        }
+        ts.close();
+    }
+
+    protected void printOutgoing(RepositoryConnection conn, Resource rsc, PrintStream ps, long offset, int limit) throws UnsupportedEncodingException, RepositoryException {
+        int i = 0;
+        RepositoryResult<Statement> ts = conn.getStatements(rsc, null, null, true);
+
+        // skip until start
+        for(int j = 0; j<offset && ts.hasNext(); j++) {
+            ts.next();
+        }
+        if (ts.hasNext()) {
+            Set<Statement> triples = new HashSet<Statement>();
+            for(int j = 0; j<limit && ts.hasNext(); j++) {
+                triples.add(ts.next());
+            }
+            ps.printf("<h2>Outgoing Links</h2>%n");
+            ps.printf("<span class='count'>Showing %d outgoing links:</span> %s%n", triples.size(),
+                    createInspectLink(conn, rsc, "next", "&sOffset=" + (offset + limit)));
+            ps.printf("<table>%n  <thead><tr><th>Link</th><th>Target</th><th>Context</th><th></th><th></th></tr></thead>%n  <tbody>%n");
+            for (Statement t : triples) {
+                ps.printf(
+                        "    <tr class='%s'><td>%s</td><td>%s</td><td>%s</td><td><span class='reasoned'>%s</span></td><td><span class='deleted %b'></span></td></tr>%n",
+                        i++ % 2 == 0 ? "even" : "odd", createInspectLink(conn, t.getSubject()), createInspectLink(conn,t.getObject()), createInspectLink(conn,t.getContext()),
+                                t instanceof KiWiTriple && ((KiWiTriple)t).isInferred() ? createInfo(((KiWiTriple)t).getId()) : "", t instanceof KiWiTriple && ((KiWiTriple)t).isDeleted());
+            }
+            ps.printf("  </tbody>%n</table>");
+        } else if (offset > 0) {
+            ps.printf("<h2>Outgoing Links</h2>%n");
+            ps.printf("<span class='count'>No more outgoing links at offset %d</span>%n", offset);
+        }
+        ts.close();
+    }
+
+    protected void printContext(RepositoryConnection conn, Resource rsc, PrintStream ps, long offset, int limit) throws UnsupportedEncodingException, RepositoryException {
+        if (rsc == null) return;
+        int i = 0;
+        RepositoryResult<Statement> ts = conn.getStatements(null, null, null, true, rsc);
+
+        // skip until start
+        for(int j = 0; j<offset && ts.hasNext(); j++) {
+            ts.next();
+        }
+        if (ts.hasNext()) {
+            Set<Statement> triples = new HashSet<Statement>();
+            for(int j = 0; j<limit && ts.hasNext(); j++) {
+                triples.add(ts.next());
+            }
+            ps.printf("<h2>Context</h2>%n");
+            ps.printf("<span class='count'>Showing %d triples in Context:</span> %s%n", triples.size(),
+                    createInspectLink(conn, rsc, "next", "&cOffset=" + (offset + limit)));
+            ps.printf("<table>%n  <thead><tr><th>Subject</th><th>Property</th><th>Object</th><th></th><th></th></tr></thead>%n  <tbody>%n");
+            for (Statement t : triples) {
+                ps.printf(
+                        "    <tr class='%s'><td>%s</td><td>%s</td><td>%s</td><td><span class='reasoned'>%s</span></td><td><span class='deleted %b'></span></td></tr>%n",
+                        i++ % 2 == 0 ? "even" : "odd", createInspectLink(conn, t.getSubject()), createInspectLink(conn,t.getObject()), createInspectLink(conn,t.getContext()),
+                                t instanceof KiWiTriple && ((KiWiTriple)t).isInferred() ? createInfo(((KiWiTriple)t).getId()) : "", t instanceof KiWiTriple && ((KiWiTriple)t).isDeleted());
+            }
+            ps.printf("  </tbody>%n</table>");
+        } else if (offset > 0) {
+            ps.printf("<h2>Context</h2>%n");
+            ps.printf("<span class='count'>Less than %d triples in context, reset offset.</span>%n", offset);
+        }
+
+    }
+
+    private String createInspectLink(RepositoryConnection conn, Value node) throws UnsupportedEncodingException {
+        return createInspectLink(conn, node, null, "");
+    }
+
+    private String createInspectLink(RepositoryConnection conn, Value node, String linkText, String extraQS) throws UnsupportedEncodingException {
+        if(node == null) return "undefined";
+        if (extraQS == null) {
+            extraQS = "";
+        }
+        if (node instanceof Literal) {
+            Literal lit = (Literal) node;
+            if (linkText == null) {
+                linkText = lit.getDatatype().stringValue();
+            }
+            StringBuilder sb = new StringBuilder("\"" + lit.getLabel().replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")
+                    + "\"");
+            if (lit.getLanguage() != null) {
+                sb.append("@").append(lit.getLanguage());
+            } else if (lit.getDatatype() != null) {
+                sb.append("^^").append(
+                        String.format("<a href='%s?uri=%s%s'>%s</a>", configurationService.getServerUri() + "inspect",
+                                URLEncoder.encode(lit.getDatatype().stringValue(), CHARSET), extraQS, linkText));
+            }
+            return sb.toString();
+        }
+        if (node instanceof BNode) {
+            BNode rsc = (BNode) node;
+            if (linkText == null) {
+                linkText = rsc.toString();
+            }
+            return String.format("<a href='%s/%s%s'>%s</a>", configurationService.getServerUri() + "inspect", rsc.getID(),
+                    extraQS.replaceFirst("^&", "?"), linkText);
+        }
+        if (node instanceof URI) {
+            URI rsc = (URI) node;
+            if (linkText == null) {
+                linkText = rsc.toString();
+            }
+            return String.format("<a href='%s?uri=%s%s'>%s</a>", configurationService.getServerUri() + "inspect",
+                    URLEncoder.encode(rsc.toString(), CHARSET), extraQS, linkText);
+        }
+        return "UNKNOWN";
+    }
+
+    private String buildResourceLink(RepositoryConnection conn, URI resource, String rel, String mime) {
+        final String src = configurationService.getServerUri(), base = configurationService.getBaseUri();
+
+        if (src.equals(base) && resource.toString().startsWith(base + ConfigurationService.RESOURCE_PATH + "/")) {
+            final String uuid;
+            uuid = resource.toString().substring((base + ConfigurationService.RESOURCE_PATH + "/").length());
+            return String.format("%s%s/%s/%s", base, rel, mime, uuid);
+        } else {
+            try {
+                return String.format("%s%s/%s?uri=%s", src, rel, mime, URLEncoder.encode(resource.toString(), CHARSET));
+            } catch (UnsupportedEncodingException e) {
+                return String.format("%s%s/%s?uri=%s", src, rel, mime, resource.toString());
+            }
+        }
+    }
+
+    private String createInfo(long id) {
+        StringBuilder b = new StringBuilder();
+        String closer = "<button onclick='document.getElementById(\"info" + id + "\").style.display = \"none\"' class='close-button'>X</button>";
+        String iframe = "<iframe id='iframe" + id + "' src='' style=''></iframe>";
+        b.append("<a href='#' title='justify this triple' onclick='document.getElementById(\"iframe").append(id).append("\").src=\"").append(configurationService.getServerUri()).append("core/public/html/reasoning.html#").append(id).append("\";document.getElementById(\"info").append(id).append("\").style.display = \"block\";'>i</a>");
+        b.append("<div id='info").append(id).append("' class='info-dialog'>");
+        b.append(iframe).append(closer);
+        b.append("</div>");
+        return b.toString();
+    }
+
+    /**
+     * Get the last modification of the set of triples passed as argument.
+     *
+     * @return
+     */
+    private Date lastModified(RepositoryConnection conn, Resource resource) throws RepositoryException {
+        Date last_modified = new Date(0);
+        for (Statement triple : listOutgoing(conn, resource)) {
+            if(triple instanceof KiWiTriple) {
+                KiWiTriple t = (KiWiTriple)triple;
+                if (t.getCreated().getTime() > last_modified.getTime()) {
+                    last_modified = t.getCreated();
+                }
+            }
+        }
+        return last_modified;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/MetaWebService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/MetaWebService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/MetaWebService.java
new file mode 100644
index 0000000..016e7e8
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/resource/MetaWebService.java
@@ -0,0 +1,361 @@
+/**
+ * Copyright (C) 2013 Salzburg Research.
+ *
+ * Licensed 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.marmotta.platform.core.webservices.resource;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.StreamingOutput;
+
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+import org.apache.marmotta.platform.core.api.content.ContentService;
+import org.apache.marmotta.platform.core.api.io.LMFIOService;
+import org.apache.marmotta.platform.core.api.triplestore.ContextService;
+import org.apache.marmotta.platform.core.api.triplestore.SesameService;
+import org.apache.marmotta.platform.core.services.sesame.KiWiSesameUtil;
+import org.apache.marmotta.platform.core.services.sesame.ResourceSubjectMetadata;
+
+import org.apache.marmotta.commons.collections.CollectionUtils;
+import org.apache.marmotta.commons.http.ETagGenerator;
+import org.openrdf.model.Resource;
+import org.openrdf.model.URI;
+import org.openrdf.repository.RepositoryConnection;
+import org.openrdf.repository.RepositoryException;
+import org.openrdf.repository.event.InterceptingRepositoryConnection;
+import org.openrdf.repository.event.base.InterceptingRepositoryConnectionWrapper;
+import org.openrdf.rio.RDFFormat;
+import org.openrdf.rio.RDFHandlerException;
+import org.openrdf.rio.RDFParseException;
+import org.openrdf.rio.RDFWriter;
+import org.openrdf.rio.Rio;
+
+/**
+ * Meta Web Services
+ *
+ * @author Thomas Kurz
+ * @author Sergio Fernández
+ */
+@ApplicationScoped
+@Path("/" + ConfigurationService.META_PATH)
+public class MetaWebService {
+
+    @Inject
+    private ConfigurationService configurationService;
+    
+    @Inject
+    private ContextService contextService;
+
+    @Inject
+    private ContentService contentService;
+
+    @Inject
+    private SesameService sesameService;
+
+    @Inject
+    private LMFIOService kiWiIOService;
+
+    /**
+     * Returns remote resource metadata with the given uri and an accepted
+     * return type (mimetype)
+     *
+     * @param uri
+     *            , the fully-qualified URI of the resource to create in the
+     *            triple store
+     * @param mimetype
+     *            , accepted mimetype follows the pattern .+/.+
+     * @return a remote resource's metadata (body is the resource data in
+     *         requested format)
+     * @HTTP 200 resource metadata found and returned
+     * @HTTP 400 bad request (maybe uri is not defined)
+     * @HTTP 404 resource cannot be found
+     * @HTTP 406 resource cannot be found in the given format
+     * @HTTP 500 Internal Error
+     * @ResponseHeader Content-Type (for HTTP 406) a list of available metadata
+     *                 types
+     */
+    @GET
+    @Path(ResourceWebService.MIME_PATTERN)
+    public Response getMetaRemote(@QueryParam("uri") @NotNull String uri, @PathParam("mimetype") String mimetype) throws UnsupportedEncodingException {
+        return getMeta(URLDecoder.decode(uri, "utf-8"), mimetype, null);
+    }
+
+    /**
+     * Returns local resource data with the given uuid and an accepted return
+     * type (mimetype)
+     *
+     * @param uuid
+     *            , a unique identifier (must not contain url specific
+     *            characters like /,# etc.)
+     * @param mimetype
+     *            , accepted mimetype follows the pattern .+/.+
+     * @return a local resource's metadata (body is the resource data in
+     *         requested format)
+     * @HTTP 200 resource data found and returned
+     * @HTTP 404 resource cannot be found
+     * @HTTP 406 resource cannot be found in the given format
+     * @HTTP 500 Internal Error
+     * @ResponseHeader Content-Type (for HTTP 406) a list of available metadata
+     *                 types
+     */
+    @GET
+    @Path(ResourceWebService.MIME_PATTERN + ResourceWebService.UUID_PATTERN)
+    public Response getMetaLocal(@PathParam("uuid") String uuid, @PathParam("mimetype") String mimetype) throws UnsupportedEncodingException {
+        String uri = configurationService.getBaseUri() + "resource/" + uuid;
+        return getMeta(uri, mimetype, uuid);
+    }
+
+    /**
+     * Sets metadata to a given locale resource
+     *
+     * @param uuid
+     *            , a unique identifier (must not contain url specific
+     *            characters like /,# etc.)
+     * @param mimetype
+     *            content-type of the body (metadata) follows the pattern .+/.+
+     * @return HTTP response (success or error)
+     * @HTTP 200 put was successful
+     * @HTTP 400 bad request (e.g. body is empty)
+     * @HTTP 404 resource cannot be found
+     * @HTTP 415 Content-Type is not supported
+     * @HTTP 500 Internal Error
+     * @ResponseHeader Content-Type (for HTTP 415) a list of available types for
+     *                 metadata
+     */
+    @PUT
+    @Path(ResourceWebService.MIME_PATTERN + ResourceWebService.UUID_PATTERN)
+    public Response putMetaLocal(@PathParam("uuid") String uuid, @PathParam("mimetype") String mimetype, @Context HttpServletRequest request) {
+        String uri = configurationService.getBaseUri() + "resource/" + uuid;
+        return putMeta(uri, mimetype, request);
+    }
+
+    /**
+     * Sets metadata to a given locale resource
+     *
+     * @param uri
+     *            , the fully-qualified URI of the resource to create in the
+     *            triple store
+     * @param mimetype
+     *            content-type of the body (metadata) follows the pattern .+/.+
+     * @return HTTP response (success or error)
+     * @HTTP 200 put was successful
+     * @HTTP 400 bad request (e.g. uri is null)
+     * @HTTP 404 resource cannot be found
+     * @HTTP 415 Content-Type is not supported
+     * @HTTP 500 Internal Error
+     * @ResponseHeader Content-Type (for HTTP 415) a list of available types for
+     *                 metadata
+     */
+    @PUT
+    @Path(ResourceWebService.MIME_PATTERN)
+    public Response putMetaRemote(@QueryParam("uri") @NotNull String uri, @PathParam("mimetype") String mimetype, @Context HttpServletRequest request)
+            throws UnsupportedEncodingException {
+        return putMeta(URLDecoder.decode(uri, "utf-8"), mimetype, request);
+    }
+
+    /**
+     * Delete metadata of remote resource with given uri
+     *
+     * @param uri
+     *            , the fully-qualified URI of the resource to create in the
+     *            triple store
+     * @return HTTP response (success or error)
+     * @HTTP 200 resource content deleted
+     * @HTTP 400 bad request (e.g, uri is null)
+     * @HTTP 404 resource or resource metadata not found
+     */
+    @DELETE
+    public Response deleteMetaRemote(@QueryParam("uri") @NotNull String uri) throws UnsupportedEncodingException {
+        try {
+            InterceptingRepositoryConnection connection = new InterceptingRepositoryConnectionWrapper(sesameService.getRepository(), sesameService.getConnection());
+            connection.begin();
+            final Resource subject = connection.getValueFactory().createURI(uri);
+
+            try {
+                connection.addRepositoryConnectionInterceptor(new ResourceSubjectMetadata(subject));
+
+                // delete all triples for given subject
+                connection.remove(subject,null,null);
+
+                return Response.ok().build();
+            } finally {
+                connection.commit();
+                connection.close();
+            }
+
+        } catch (RepositoryException e) {
+            return ResourceWebServiceHelper.buildErrorPage(uri, configurationService.getBaseUri(), Status.INTERNAL_SERVER_ERROR, e.getMessage());
+        }
+    }
+
+    /**
+     * Delete metadata of local resource with given uuid
+     *
+     * @param uuid
+     *            , a unique identifier (must not contain url specific
+     *            characters like /,# etc.)
+     * @return HTTP response (success or error)
+     * @HTTP 200 resource content deleted
+     * @HTTP 404 resource or resource metadata not found
+     */
+    @DELETE
+    @Path(ResourceWebService.UUID_PATTERN)
+    public Response deleteMetaLocal(@PathParam("uuid") String uuid) throws UnsupportedEncodingException {
+        String uri = configurationService.getBaseUri() + "resource/" + uuid;
+        return deleteMetaRemote(uri);
+    }
+
+    private Response getMeta(String uri, String mimetype, String uuid) throws UnsupportedEncodingException {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+
+            try {
+                conn.begin();
+                // FIXME String appendix = uuid == null ? "?uri=" + URLEncoder.encode(uri, "utf-8") :
+                // "/" + uuid;
+                URI resource = conn.getValueFactory().createURI(uri);
+                // create parser
+                final RDFFormat serializer = kiWiIOService.getSerializer(mimetype);
+                if (serializer == null) {
+                    Response response = Response.status(406).entity("mimetype can not be processed").build();
+                    ResourceWebServiceHelper.addHeader(response, "Content-Type", ResourceWebServiceHelper.appendMetaTypes(kiWiIOService.getProducedTypes()));
+                    return response;
+                }
+
+                if(resource != null) {
+
+                    final Resource subject = resource;
+
+                    StreamingOutput entity = new StreamingOutput() {
+                        @Override
+                        public void write(OutputStream output) throws IOException, WebApplicationException {
+                            // FIXME: This method is executed AFTER the @Transactional!
+                            RDFWriter writer = Rio.createWriter(serializer,output);
+                            try {
+                                RepositoryConnection connection = sesameService.getConnection();
+                                try {
+                                    connection.begin();
+                                    connection.exportStatements(subject,null,null,true,writer);
+                                } finally {
+                                    connection.commit();
+                                    connection.close();
+                                }
+                            } catch (RepositoryException e) {
+                                throw new WebApplicationException(e, Status.INTERNAL_SERVER_ERROR);
+                            } catch (RDFHandlerException e) {
+                                throw new IOException("error while writing RDF data to stream", e);
+                            }
+
+                        }
+                    };
+
+                    // build response
+                    Response response = Response.ok(entity).lastModified(KiWiSesameUtil.lastModified(resource, conn)).build();
+                    response.getMetadata().add("ETag", "W/\"" + ETagGenerator.getWeakETag(conn, resource) + "\"");
+
+                    // create the Content-Type header for the response
+                    if (mimetype.startsWith("text/") || mimetype.startsWith("application/")) {
+                        response.getMetadata().add("Content-Type", mimetype + "; charset=" + ResourceWebService.CHARSET);
+                    } else {
+                        response.getMetadata().add("Content-Type", mimetype);
+                    }
+
+                    // create the Link headers for the response
+                    List<String> links = new LinkedList<String>();
+
+                    // build the link to the human readable content of this resource (if it exists)
+                    String contentLink = ResourceWebServiceHelper.buildContentLink(resource, uuid, contentService.getContentType(resource), configurationService);
+                    if(!"".equals(contentLink)) {
+                        links.add(contentLink);
+                    }
+
+                    if (links.size() > 0) {
+                        response.getMetadata().add("Link", CollectionUtils.fold(links, ", "));
+                    }
+                    return response;
+                } else {
+                    return Response.status(Response.Status.NOT_FOUND).entity("resource with URI "+uri+" does not exist").build();
+                }
+            } finally {
+                if (conn.isOpen()) {
+                    conn.commit();
+                    conn.close();
+                }
+            }
+        } catch (RepositoryException e) {
+            return ResourceWebServiceHelper.buildErrorPage(uri, configurationService.getBaseUri(), Status.INTERNAL_SERVER_ERROR, e.getMessage());
+        }
+    }
+
+    public Response putMeta(String uri, String mimetype, HttpServletRequest request) {
+        try {
+            // create parser
+            RDFFormat parser = kiWiIOService.getParser(mimetype);
+            if (parser == null) {
+                Response response = Response.status(Status.UNSUPPORTED_MEDIA_TYPE).entity("media type " + mimetype + " not supported").build();
+                ResourceWebServiceHelper.addHeader(response, "Content-Type", ResourceWebServiceHelper.appendMetaTypes(kiWiIOService.getProducedTypes()));
+                return response;
+            }
+            if (request.getContentLength() == 0)
+                return ResourceWebServiceHelper.buildErrorPage(uri, configurationService.getBaseUri(), Status.BAD_REQUEST, "content may not be empty in resource update");
+
+            // a intercepting connection that filters out all triples that have
+            // the wrong subject
+            InterceptingRepositoryConnection connection = new InterceptingRepositoryConnectionWrapper(sesameService.getRepository(), sesameService.getConnection());
+            //RepositoryConnection connection = sesameService.getConnection();
+            try {
+                connection.begin();
+                final Resource subject = connection.getValueFactory().createURI(uri);
+
+                connection.addRepositoryConnectionInterceptor(new ResourceSubjectMetadata(subject));
+
+                // delete all triples for given subject
+                connection.remove(subject, null, null, (Resource)null);
+
+                // add RDF data from input to the suject
+                connection.add(request.getReader(), configurationService.getBaseUri(), parser, contextService.getDefaultContext());
+            } finally {
+                connection.commit();
+                connection.close();
+            }
+            return Response.ok().build();
+        } catch (RepositoryException e) {
+            return ResourceWebServiceHelper.buildErrorPage(uri, configurationService.getBaseUri(), Status.INTERNAL_SERVER_ERROR, e.getMessage());
+        } catch (IOException e) {
+            return ResourceWebServiceHelper.buildErrorPage(uri, configurationService.getBaseUri(), Status.INTERNAL_SERVER_ERROR, "could not read request body");
+        } catch (RDFParseException e) {
+            return ResourceWebServiceHelper.buildErrorPage(uri, configurationService.getBaseUri(), Status.UNSUPPORTED_MEDIA_TYPE, "could not parse request body");
+        }
+    }
+
+}