You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@marmotta.apache.org by wi...@apache.org on 2013/02/19 13:52:01 UTC

[12/52] [partial] code contribution, initial import of relevant modules of LMF-3.0.0-SNAPSHOT based on revision 4bf944319368 of the default branch at https://code.google.com/p/lmf/

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/lmf-core/src/main/java/kiwi/core/services/content/ContentServiceImpl.java
----------------------------------------------------------------------
diff --git a/lmf-core/src/main/java/kiwi/core/services/content/ContentServiceImpl.java b/lmf-core/src/main/java/kiwi/core/services/content/ContentServiceImpl.java
new file mode 100644
index 0000000..4c4b4da
--- /dev/null
+++ b/lmf-core/src/main/java/kiwi/core/services/content/ContentServiceImpl.java
@@ -0,0 +1,327 @@
+/**
+ * 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 kiwi.core.services.content;
+
+import kiwi.core.api.config.ConfigurationService;
+import kiwi.core.api.content.ContentReader;
+import kiwi.core.api.content.ContentService;
+import kiwi.core.api.content.ContentWriter;
+import kiwi.core.events.ConfigurationChangedEvent;
+import kiwi.core.exception.LMFException;
+import kiwi.core.exception.WritingNotSupportedException;
+import org.openrdf.model.Resource;
+import org.slf4j.Logger;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Instance;
+import javax.inject.Inject;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Service that provides access to the content associated with a resource. It makes use of the ContentReader and
+ * ContentWriter implementations registered in the system.
+ *
+ * User: Thomas Kurz, Sebastian Schaffert
+ * Date: 07.02.11
+ * Time: 12:37
+ */
+@ApplicationScoped
+public class ContentServiceImpl implements ContentService {
+
+    @Inject
+    private Logger log;
+
+    @Inject
+    private ConfigurationService configurationService;
+
+    @Inject
+    private Instance<ContentReader> readers;
+
+    @Inject
+    private Instance<ContentWriter> writers;
+
+    private Map<Pattern,ContentReader> readerMap;
+    private Map<Pattern,ContentWriter> writerMap;
+
+    @Override
+    @PostConstruct
+    public void initialise() {
+        log.info("Content Service starting up ...");
+
+        initialiseReadersWriters();
+    }
+
+    private void initialiseReadersWriters() {
+        readerMap = new HashMap<Pattern, ContentReader>();
+        writerMap = new HashMap<Pattern, ContentWriter>();
+
+        // first read from the config file all content.* keys and store the name of the reader/writer in this set
+        // will e.g. store "triplestore" and "filesystem" if it finds keys of the form "content.triplestore.reader"
+        // and "content.filesystem.writer"
+        Set<String> configNames = new HashSet<String>();
+        for(String key : configurationService.listConfigurationKeys("content")) {
+            String[] components = key.split("\\.");
+            if(components.length > 1) {
+                configNames.add(components[1]);
+            }
+        }
+
+        // then read the configuration for each reader/writer specification in the config file and look whether
+        // the appropriate reader and/or writer is available
+        for(String configName : configNames) {
+            String readerClass = configurationService.getStringConfiguration("content."+configName+".reader");
+            String writerClass = configurationService.getStringConfiguration("content."+configName+".writer");
+            String patternStr  = configurationService.getStringConfiguration("content."+configName+".pattern");
+            String enabledStr  = configurationService.getStringConfiguration("content."+configName+".enabled");
+
+            if(Boolean.parseBoolean(enabledStr)) {
+                ContentReader reader = null;
+                ContentWriter writer = null;
+                Pattern pattern = null;
+                if(readerClass != null) {
+                    for(ContentReader r : readers) {
+                        if(r.getClass().getCanonicalName().startsWith(readerClass)) {
+                            reader = r;
+                            break;
+                        }
+                    }
+                }
+
+                if(writerClass != null) {
+                    for(ContentWriter w : writers) {
+                        if(w.getClass().getCanonicalName().startsWith(writerClass)) {
+                            writer = w;
+                            break;
+                        }
+                    }
+                }
+
+                try {
+                    pattern = Pattern.compile(patternStr);
+                } catch(Exception ex) {
+                    log.warn("pattern {} is not a valid regular expression; disabling reader/writer {} (message was {})", patternStr,configName,ex.getMessage());
+                    continue;
+                }
+
+                if(pattern != null && reader != null) {
+                    readerMap.put(pattern,reader);
+                    log.info("enabled content reader '{}' for pattern {}",reader.getName(),pattern);
+                }
+                if(pattern != null && writer != null) {
+                    writerMap.put(pattern,writer);
+                    log.info("enabled content writer '{}' for pattern {}", writer.getName(), pattern);
+                }
+
+
+            } else {
+                log.info("content reader/writer {} disabled",configName);
+            }
+        }
+    }
+
+
+    public void configurationChangedEvent(@Observes ConfigurationChangedEvent event) {
+        for (String key : event.getKeys())
+            if (key.startsWith("content.")) {
+                log.info("Content Service reinitialising ...");
+                initialiseReadersWriters();
+                break;
+            }
+    }
+
+
+
+
+    @Override
+    public void setContentData(Resource resource, byte[] data, String mimetype) throws WritingNotSupportedException {
+        // iterate over all possible writers; if the pattern matches, try to store the content and return
+        for(Pattern p : writerMap.keySet()) {
+            if(p.matcher(resource.toString()).matches()) {
+                ContentWriter writer = writerMap.get(p);
+                log.debug("setting content for resource {} using writer {}",resource,writer.getName());
+                try {
+                    writer.setContentData(resource, data, mimetype);
+                    return;
+                } catch(IOException ex) {
+                    log.error("could not write content, writer threw an IO Exception",ex);
+                    throw new WritingNotSupportedException(ex.getMessage(),ex);
+                }
+            }
+        }
+        throw new WritingNotSupportedException("no writer found for resource "+resource);
+    }
+
+
+    /**
+     * Store the content of the specified mime type for the specified resource. Accepts an input stream containing
+     * the byte data of the content that is read and written to the destination configured for this writer.
+     * <p/>
+     * This method is preferrable for resources with large amounts of data.
+     *
+     * @param resource the resource for which to return the content
+     * @param mimeType the mime type to retrieve of the content
+     * @param in       a InputStream containing the content of the resource
+     */
+    @Override
+    public void setContentStream(Resource resource, InputStream in, String mimeType) throws WritingNotSupportedException {
+        // iterate over all possible writers; if the pattern matches, try to store the content and return
+        for(Pattern p : writerMap.keySet()) {
+            if(p.matcher(resource.toString()).matches()) {
+                ContentWriter writer = writerMap.get(p);
+                log.debug("setting content for resource {} using writer {}",resource,writer.getName());
+                try {
+                    writer.setContentStream(resource,in,mimeType);
+                    return;
+                } catch(IOException ex) {
+                    log.error("could not write content, writer threw an IO Exception",ex);
+                    throw new WritingNotSupportedException(ex.getMessage(),ex);
+                }
+            }
+        }
+        throw new WritingNotSupportedException("no writer found for resource "+resource);
+    }
+
+    /**
+     * Retrieve the content of the specified mime type for the specified resource. Returns a byte array containing
+     * the byte data of the content, or null, indicating that a content of the specified mime type does not exist
+     * for the resource.
+     * <p/>
+     * Specialised content readers could even transform the resource content from its original form to the new
+     * mimetype, e.g. converting an image from JPEG to PNG.
+     *
+     * @param resource  the resource for which to return the content
+     * @param mimetype  the mime type to retrieve of the content
+     * @return a byte array containing the content of the resource, or null if no content exists
+     */
+    @Override
+    public byte[] getContentData(Resource resource, String mimetype) {
+        // iterate over all possible writers; if the pattern matches, try to store the content and return
+        for(Pattern p : readerMap.keySet()) {
+            if(p.matcher(resource.toString()).matches()) {
+                ContentReader reader = readerMap.get(p);
+                log.debug("reading content for resource {} using reader {}",resource,reader.getName());
+                try {
+                    return reader.getContentData(resource, mimetype);
+                } catch(IOException ex) {
+                    log.error("could not read content, reader threw an IO Exception",ex);
+                    return null;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the content of the specified mime type for the specified resource. Returns a input stream containing
+     * the byte data of the content, or null, indicating that a content of the specified mime type does not exist
+     * for the resource.
+     * <p/>
+     * Specialised content readers could even transform the resource content from its original form to the new
+     * mimetype, e.g. converting an image from JPEG to PNG.
+     * <p/>
+     * This method is preferrable for resources with large amounts of data.
+     *
+     * @param resource the resource for which to return the content
+     * @param mimetype the mime type to retrieve of the content
+     * @return a InputStream containing the content of the resource, or null if no content exists
+     */
+    @Override
+    public InputStream getContentStream(Resource resource, String mimetype) throws IOException {
+        // iterate over all possible writers; if the pattern matches, try to store the content and return
+        for(Pattern p : readerMap.keySet()) {
+            if(p.matcher(resource.toString()).matches()) {
+                ContentReader reader = readerMap.get(p);
+                log.debug("reading content for resource {} using reader {}",resource,reader.getName());
+                try {
+                    return reader.getContentStream(resource, mimetype);
+                } catch(IOException ex) {
+                    log.error("could not read content for resource {}, reader threw an IO Exception (message: {})",resource,ex.getMessage());
+                    return null;
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean hasContent(Resource resource, String mimetype) {
+        // iterate over all possible writers; if the pattern matches, try to store the content and return
+        for(Pattern p : readerMap.keySet()) {
+            if(p.matcher(resource.toString()).matches()) {
+                ContentReader reader = readerMap.get(p);
+                return reader.hasContent(resource, mimetype);
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String getContentType(Resource resource) {
+        // iterate over all possible writers; if the pattern matches, try to store the content and return
+        for(Pattern p : readerMap.keySet()) {
+            if(p.matcher(resource.stringValue()).matches()) {
+                ContentReader reader = readerMap.get(p);
+                return reader.getContentType(resource);
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Return the number of bytes the content of this resource contains.
+     *
+     * @param resource resource for which to return the content length
+     * @return byte count for the resource content
+     */
+    @Override
+    public long getContentLength(Resource resource, String mimetype) {
+        // iterate over all possible writers; if the pattern matches, try to store the content and return
+        for(Pattern p : readerMap.keySet()) {
+            if(p.matcher(resource.toString()).matches()) {
+                ContentReader reader = readerMap.get(p);
+                return reader.getContentLength(resource,mimetype);
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public boolean deleteContent(Resource resource) throws LMFException {
+        // iterate over all possible writers; if the pattern matches, try to store the content and return
+        for(Pattern p : writerMap.keySet()) {
+            if(p.matcher(resource.toString()).matches()) {
+                ContentWriter writer = writerMap.get(p);
+                try {
+                    writer.deleteContent(resource,"");
+                    return true;
+                } catch(IOException ex) {
+                    log.error("could not write content, writer threw an IO Exception",ex);
+                    throw new WritingNotSupportedException(ex.getMessage(),ex);
+                }
+            }
+        }
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/lmf-core/src/main/java/kiwi/core/services/content/FileSystemContentReader.java
----------------------------------------------------------------------
diff --git a/lmf-core/src/main/java/kiwi/core/services/content/FileSystemContentReader.java b/lmf-core/src/main/java/kiwi/core/services/content/FileSystemContentReader.java
new file mode 100644
index 0000000..f414a20
--- /dev/null
+++ b/lmf-core/src/main/java/kiwi/core/services/content/FileSystemContentReader.java
@@ -0,0 +1,309 @@
+/**
+ * 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 kiwi.core.services.content;
+
+import at.newmedialab.sesame.facading.FacadingFactory;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import kiwi.core.api.config.ConfigurationService;
+import kiwi.core.api.content.ContentReader;
+import kiwi.core.api.triplestore.SesameService;
+import kiwi.core.model.content.MediaContentItem;
+import org.apache.marmotta.kiwi.model.rdf.KiWiUriResource;
+import org.apache.tika.detect.DefaultDetector;
+import org.apache.tika.metadata.Metadata;
+import org.openrdf.model.Resource;
+import org.openrdf.repository.RepositoryConnection;
+import org.openrdf.repository.RepositoryException;
+import org.slf4j.Logger;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+
+import static at.newmedialab.sesame.commons.repository.ExceptionUtils.handleRepositoryException;
+
+/**
+ * A content reader that reads the content of a resource from the file system.
+ * It uses the kiwi:hasContentPath property to determine the path of the content to access.
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+@ApplicationScoped
+public class FileSystemContentReader implements ContentReader {
+
+    @Inject
+    private Logger log;
+
+
+    @Inject
+    private ConfigurationService configurationService;
+
+    @Inject
+    private SesameService sesameService;
+
+    /** TIKA mime type detector */
+    private DefaultDetector detector;
+
+
+    private String defaultDir;
+
+    public FileSystemContentReader() {
+    }
+
+    @PostConstruct
+    public void initialise() {
+        detector = new DefaultDetector();
+        defaultDir = configurationService.getWorkDir()+File.separator+"resources";
+
+        log.debug("FileSystem Content Reader started (default directory: {})",defaultDir);
+
+    }
+
+    /**
+     * Retrieve the content of the specified mime type for the specified resource. Returns a byte array containing
+     * the byte data of the content, or null, indicating that a content of the specified mime type does not exist
+     * for the resource.
+     * <p/>
+     * Specialised content readers could even transform the resource content from its original form to the new
+     * mimetype, e.g. converting an image from JPEG to PNG.
+     *
+     * @param resource the resource for which to return the content
+     * @param mimetype the mime type to retrieve of the content
+     * @return a byte array containing the content of the resource, or null if no content exists
+     */
+    @Override
+    public byte[] getContentData(Resource resource, String mimetype) throws IOException {
+        InputStream in = getContentStream(resource,mimetype);
+        try {
+            return ByteStreams.toByteArray(in);
+        } finally {
+            in.close();
+        }
+    }
+
+    /**
+     * Return the name of the content reader. Used to identify and display the content reader to admin users.
+     *
+     * @return
+     */
+    @Override
+    public String getName() {
+        return "FileSystem Content Reader";
+    }
+
+    /**
+     * Retrieve the content of the specified mime type for the specified resource. Returns a input stream containing
+     * the byte data of the content, or null, indicating that a content of the specified mime type does not exist
+     * for the resource.
+     * <p/>
+     * Specialised content readers could even transform the resource content from its original form to the new
+     * mimetype, e.g. converting an image from JPEG to PNG.
+     * <p/>
+     * This method is preferrable for resources with large amounts of data.
+     *
+     * @param resource the resource for which to return the content
+     * @param mimetype the mime type to retrieve of the content
+     * @return a InputStream containing the content of the resource, or null if no content exists
+     */
+    @Override
+    public InputStream getContentStream(Resource resource, String mimetype) throws IOException {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String path = mci.getContentPath();
+                if(path == null && resource instanceof KiWiUriResource && ((KiWiUriResource)resource).stringValue().startsWith("file:")) {
+                    try {
+                        URI uri = new URI(((KiWiUriResource)resource).stringValue());
+                        path = uri.getPath();
+                    } catch(Exception ex) {}
+                }
+
+                if(path != null) {
+                    if(!path.startsWith(defaultDir)) {
+                        if(!configurationService.getBooleanConfiguration("content.filesystem.secure")) {
+                            log.warn("accessing file {}, which is outside the default directory; this is a potential security risk; " +
+                                    "enable the option content.filesystem.secure in the configuration",path);
+                        } else {
+                            throw new FileNotFoundException("the file "+path+" is outside the LMF default directory location; access denied");
+                        }
+                    }
+
+                    File file = new File(path);
+                    if(file.exists() && file.canRead()) {
+                        log.debug("reading file content from file {} for resource {} ...", file, resource);
+                        return Files.newInputStreamSupplier(file).getInput();
+                    } else {
+                        throw new FileNotFoundException("the file "+path+" does not exist or is not readable");
+                    }
+                } else {
+                    return null;
+                }
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+            return null;
+        }
+    }
+
+    /**
+     * Check whether the specified resource has content of the specified mimetype for this reader. Returns true
+     * in this case, false otherwise.
+     *
+     * @param resource the resource to check
+     * @param mimetype the mimetype to look for
+     * @return true if content of this mimetype is associated with the resource, false otherwise
+     */
+    @Override
+    public boolean hasContent(Resource resource, String mimetype) {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String path = mci.getContentPath();
+                if(path == null && resource instanceof KiWiUriResource && ((KiWiUriResource)resource).stringValue().startsWith("file:")) {
+                    try {
+                        URI uri = new URI(((KiWiUriResource)resource).stringValue());
+                        path = uri.getPath();
+                    } catch(Exception ex) {}
+                }
+
+                if(path != null) {
+                    File file = new File(path);
+                    if(file.exists() && file.canRead()) {
+                        log.debug("found file content from file {} for resource {} ...", file, resource);
+                        return true;
+                    } else {
+                        return false;
+                    }
+                } else {
+                    return false;
+                }
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+            return false;
+        }
+    }
+
+    /**
+     * Return the MIME content type of the resource passed as argument.
+     *
+     * @param resource resource for which to return the content type
+     * @return the MIME content type of the resource
+     */
+    @Override
+    public String getContentType(Resource resource) {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String path = mci.getContentPath();
+                if(path == null && resource instanceof KiWiUriResource && ((KiWiUriResource)resource).stringValue().startsWith("file:")) {
+                    try {
+                        URI uri = new URI(((KiWiUriResource)resource).stringValue());
+                        path = uri.getPath();
+                    } catch(Exception ex) {}
+                }
+
+                if(path != null) {
+                    File file = new File(path);
+                    if(file.exists() && file.canRead()) {
+
+                        String mimeType = null;
+
+                        Metadata metadata = new Metadata();
+                        metadata.set(Metadata.RESOURCE_NAME_KEY, file.getAbsolutePath());
+                        try {
+                            InputStream in = new BufferedInputStream(Files.newInputStreamSupplier(file).getInput());
+                            mimeType = detector.detect(in,metadata).toString();
+                            in.close();
+                        } catch (IOException e) {
+                            log.error("I/O error while detecting file type for file {}",file,e);
+                        }
+                        log.debug("detected mime type {} of file {} for resource {} ...", mimeType, file, resource);
+
+                        return mimeType;
+                    } else {
+                        return null;
+                    }
+                } else {
+                    return null;
+                }
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+            return null;
+        }
+    }
+
+
+    /**
+     * Return the number of bytes the content of this resource contains.
+     *
+     * @param resource resource for which to return the content length
+     * @return byte count for the resource content
+     */
+    @Override
+    public long getContentLength(Resource resource, String mimetype) {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String path = mci.getContentPath();
+                if(path == null && resource instanceof KiWiUriResource && ((KiWiUriResource)resource).stringValue().startsWith("file:")) {
+                    try {
+                        URI uri = new URI(((KiWiUriResource)resource).stringValue());
+                        path = uri.getPath();
+                    } catch(Exception ex) {}
+                }
+
+                if(path != null) {
+                    File file = new File(path);
+                    if(file.exists() && file.canRead()) {
+                        return file.length();
+                    }
+                }
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+        }
+        return 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/lmf-core/src/main/java/kiwi/core/services/content/FileSystemContentWriter.java
----------------------------------------------------------------------
diff --git a/lmf-core/src/main/java/kiwi/core/services/content/FileSystemContentWriter.java b/lmf-core/src/main/java/kiwi/core/services/content/FileSystemContentWriter.java
new file mode 100644
index 0000000..91f0711
--- /dev/null
+++ b/lmf-core/src/main/java/kiwi/core/services/content/FileSystemContentWriter.java
@@ -0,0 +1,232 @@
+/**
+ * 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 kiwi.core.services.content;
+
+import at.newmedialab.sesame.facading.FacadingFactory;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import kiwi.core.api.config.ConfigurationService;
+import kiwi.core.api.content.ContentWriter;
+import kiwi.core.api.triplestore.SesameService;
+import kiwi.core.model.content.MediaContentItem;
+import org.apache.marmotta.kiwi.model.rdf.KiWiUriResource;
+import org.openrdf.model.Resource;
+import org.openrdf.repository.RepositoryConnection;
+import org.openrdf.repository.RepositoryException;
+import org.slf4j.Logger;
+import sun.net.www.MimeEntry;
+import sun.net.www.MimeTable;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.UUID;
+
+import static at.newmedialab.sesame.commons.repository.ExceptionUtils.handleRepositoryException;
+
+/**
+ * A content writer that writes the content of a resource to the file system.
+ * It uses the kiwi:hasContentPath property to determine the destination path of the content to write to.
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+@ApplicationScoped
+public class FileSystemContentWriter implements ContentWriter {
+
+    @Inject
+    private Logger log;
+
+
+    @Inject
+    private ConfigurationService configurationService;
+
+    @Inject
+    private SesameService sesameService;
+
+    private String defaultDir;
+
+    public FileSystemContentWriter() {
+    }
+
+    @PostConstruct
+    public void initialise() {
+        defaultDir = configurationService.getWorkDir()+File.separator+"resources";
+
+        log.debug("FileSystem Content Writer started (default file location: {})",defaultDir);
+
+        File dir = new File(defaultDir);
+        if(!dir.exists() && !dir.mkdirs()) {
+            log.warn("could not create default directory for file system storage of content (directory: {})",defaultDir);
+        }
+    }
+
+    /**
+     * Delete the content of the speficied mime type for the specified resource.
+     *
+     * @param resource the resource for which to delete the content
+     * @param mimetype the mime type of the content to delete (optional)
+     */
+    @Override
+    public void deleteContent(Resource resource, String mimetype) throws IOException {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String path = mci.getContentPath();
+                if(path != null) {
+                    if(!path.startsWith(defaultDir)) {
+                        if(!configurationService.getBooleanConfiguration("content.filesystem.secure")) {
+                            log.warn("accessing file {}, which is outside the default directory; this is a potential security risk; " +
+                                    "enable the option content.filesystem.secure in the configuration",path);
+                        } else {
+                            throw new FileNotFoundException("the file "+path+" is outside the LMF default directory location; access denied");
+                        }
+                    }
+
+                    File file = new File(path);
+                    if(file.exists() && file.canWrite()) {
+                        log.info("deleting file {} for resource {} ...", file.getPath(), resource);
+                        file.delete();
+                    } else {
+                        throw new FileNotFoundException("could not delete file "+path+"; it does not exist or is not writable");
+                    }
+                } else {
+                    throw new FileNotFoundException("could not delete file content for resource "+resource+"; no content path has been specified for the resource");
+                }
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+        }
+    }
+
+    /**
+     * Return the name of the content reader. Used to identify and display the content reader to admin users.
+     *
+     * @return
+     */
+    @Override
+    public String getName() {
+        return "FileSystem Content Writer";
+    }
+
+    /**
+     * Store the content of the specified mime type for the specified resource. Accepts a byte array containing
+     * the byte data of the content that is then written to the destination configured for this writer.
+     * <p/>
+     *
+     * @param resource the resource for which to store the content
+     * @param mimetype the mime type of the content
+     * @param data     a byte array containing the content of the resource
+     */
+    @Override
+    public void setContentData(Resource resource, byte[] data, String mimetype) throws IOException {
+        setContentStream(resource, new ByteArrayInputStream(data),mimetype);
+    }
+
+    /**
+     * Store the content of the specified mime type for the specified resource. Accepts an input stream containing
+     * the byte data of the content that is read and written to the destination configured for this writer.
+     * <p/>
+     * This method is preferrable for resources with large amounts of data.
+     *
+     * @param resource the resource for which to return the content
+     * @param mimetype the mime type to retrieve of the content
+     * @param in       a InputStream containing the content of the resource
+     */
+    @Override
+    public void setContentStream(Resource resource, InputStream in, String mimetype) throws IOException {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String path = mci.getContentPath();
+
+                if(path == null) {
+                    if(resource instanceof KiWiUriResource && ((KiWiUriResource)resource).stringValue().startsWith("file:")) {
+                        try {
+                            URI uri = new URI(((KiWiUriResource)resource).stringValue());
+                            path = uri.getPath();
+                        } catch(Exception ex) {}
+                    } else {
+                        // we store all other resources in the default directory; create a random file name and store it in the hasContentLocation
+                        // property
+                        String extension = null;
+                        MimeEntry entry = MimeTable.getDefaultTable().find(mimetype);
+                        if(entry != null && entry.getExtensions().length > 0) {
+                            extension = entry.getExtensions()[0];
+                        }
+
+                        String fileName = UUID.randomUUID().toString();
+                        path = defaultDir + File.separator +
+                                fileName.substring(0,2) + File.separator +
+                                fileName.substring(2,4) + File.separator +
+                                fileName.substring(4,6) + File.separator +
+                                fileName + (extension != null ? extension : "");
+                        mci.setContentPath(path);
+                    }
+                }
+
+                if(path != null) {
+                    if(!path.startsWith(defaultDir)) {
+                        if(!configurationService.getBooleanConfiguration("content.filesystem.secure")) {
+                            log.warn("accessing file {}, which is outside the default directory; this is a potential security risk; " +
+                                    "enable the option content.filesystem.secure in the configuration",path);
+                        } else {
+                            throw new FileNotFoundException("the file "+path+" is outside the LMF default directory location; access denied");
+                        }
+                    }
+
+                    File file = new File(path);
+                    if(!file.exists()) {
+                        try {
+                            file.getParentFile().mkdirs();
+                            file.createNewFile();
+                        } catch(IOException ex) {
+                            throw new FileNotFoundException("could not create file "+path+"; it is not writable");
+                        }
+                    }
+                    if(file.exists() && file.canWrite()) {
+                        log.debug("writing file content to file {} for resource {} ...", file, resource);
+                        ByteStreams.copy(in, Files.newOutputStreamSupplier(file));
+                    } else {
+                        throw new FileNotFoundException("could not write to file "+path+"; it does not exist or is not writable");
+                    }
+
+
+
+                } else {
+                    throw new FileNotFoundException("could not write file content for resource "+resource+"; no content path has been specified for the resource");
+                }
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/lmf-core/src/main/java/kiwi/core/services/content/HTTPContentReader.java
----------------------------------------------------------------------
diff --git a/lmf-core/src/main/java/kiwi/core/services/content/HTTPContentReader.java b/lmf-core/src/main/java/kiwi/core/services/content/HTTPContentReader.java
new file mode 100644
index 0000000..3a6b545
--- /dev/null
+++ b/lmf-core/src/main/java/kiwi/core/services/content/HTTPContentReader.java
@@ -0,0 +1,302 @@
+/**
+ * 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 kiwi.core.services.content;
+
+import at.newmedialab.sesame.facading.FacadingFactory;
+import com.google.common.io.ByteStreams;
+import kiwi.core.api.config.ConfigurationService;
+import kiwi.core.api.content.ContentReader;
+import kiwi.core.api.http.HttpClientService;
+import kiwi.core.api.triplestore.SesameService;
+import kiwi.core.model.content.MediaContentItem;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.marmotta.kiwi.model.rdf.KiWiUriResource;
+import org.openrdf.model.Resource;
+import org.openrdf.repository.RepositoryConnection;
+import org.openrdf.repository.RepositoryException;
+import org.slf4j.Logger;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static at.newmedialab.sesame.commons.repository.ExceptionUtils.handleRepositoryException;
+
+/**
+ * Add file description here!
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+@ApplicationScoped
+public class HTTPContentReader implements ContentReader {
+
+    @Inject
+    private Logger log;
+
+
+    @Inject
+    private SesameService sesameService;
+
+    @Inject
+    private ConfigurationService configurationService;
+
+    @Inject
+    private HttpClientService    httpClientService;
+
+    public HTTPContentReader() {
+    }
+
+    @PostConstruct
+    public void initialise() {
+        log.debug("HTTP Content Reader started");
+
+    }
+
+
+    /**
+     * Retrieve the content of the specified mime type for the specified resource. Returns a byte array containing
+     * the byte data of the content, or null, indicating that a content of the specified mime type does not exist
+     * for the resource.
+     * <p/>
+     * Uses the property kiwi:hasContentLocation to access the URL for the resource content. If this property is not
+     * given and the resource is itself a HTTP URI resource, it will try to access the URI itself.
+     *
+     * @param resource the resource for which to return the content
+     * @param mimetype the mime type to retrieve of the content
+     * @return a byte array containing the content of the resource, or null if no content exists
+     */
+    @Override
+    public byte[] getContentData(Resource resource, String mimetype) throws IOException {
+        return ByteStreams.toByteArray(getContentStream(resource, mimetype));
+    }
+
+    /**
+     * Return the name of the content reader. Used to identify and display the content reader to admin users.
+     *
+     * @return
+     */
+    @Override
+    public String getName() {
+        return "HTTP Content Reader";
+    }
+
+    /**
+     * Retrieve the content of the specified mime type for the specified resource. Returns a input stream containing
+     * the byte data of the content, or null, indicating that a content of the specified mime type does not exist
+     * for the resource.
+     * <p/>
+     * Uses the property kiwi:hasContentLocation to access the URL for the resource content. If this property is not
+     * given and the resource is itself a HTTP URI resource, it will try to access the URI itself.
+     * <p/>
+     * This method is preferrable for resources with large amounts of data.
+     *
+     * @param resource the resource for which to return the content
+     * @param mimetype the mime type to retrieve of the content
+     * @return a InputStream containing the content of the resource, or null if no content exists
+     */
+    @Override
+    public InputStream getContentStream(final Resource resource, String mimetype) throws IOException {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String location = mci.getContentLocation();
+
+                // if no location is explicitly specified, use the resource URI itself
+                if(location == null && resource instanceof KiWiUriResource && ((KiWiUriResource)resource).stringValue().startsWith("http://")) {
+                    location = ((KiWiUriResource)resource).stringValue();
+                }
+
+                if(location != null) {
+                    log.info("reading remote resource {}",location);
+                    HttpGet get = new HttpGet(location);
+                    get.setHeader("Accept",mimetype);
+
+                    HttpResponse response = httpClientService.execute(get);
+                    if(response.getStatusLine().getStatusCode() == 200)
+                        return response.getEntity().getContent();
+                    else {
+                        log.info("invalid status code while retrieving HTTP remote content for resource {}: {}",resource,response.getStatusLine());
+                        return null;
+                    }
+                } else
+                    return null;
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+            return null;
+        }
+    }
+
+    /**
+     * Check whether the specified resource has content of the specified mimetype for this reader. Returns true
+     * in this case, false otherwise.
+     *
+     * @param resource the resource to check
+     * @param mimetype the mimetype to look for
+     * @return true if content of this mimetype is associated with the resource, false otherwise
+     */
+    @Override
+    public boolean hasContent(Resource resource, String mimetype) {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String location = mci.getContentLocation();
+
+                // if no location is explicitly specified, use the resource URI itself
+                if(location == null && resource instanceof KiWiUriResource && ((KiWiUriResource)resource).stringValue().startsWith("http://")) {
+                    location = ((KiWiUriResource)resource).stringValue();
+                }
+
+                try {
+                    if(location != null) {
+                        log.info("reading remote resource {}",location);
+                        HttpHead head = new HttpHead(location);
+                        head.setHeader("Accept",mimetype);
+
+                        return httpClientService.execute(head, new ResponseHandler<Boolean>() {
+                            @Override
+                            public Boolean handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+                                return response.getStatusLine().getStatusCode() == 200;
+                            }
+                        });
+
+                    } else
+                        return false;
+                } catch(IOException ex) {
+                    return false;
+                }
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+            return false;
+        }
+    }
+
+    /**
+     * Return the MIME content type of the resource passed as argument.
+     *
+     * @param resource resource for which to return the content type
+     * @return the MIME content type of the resource
+     */
+    @Override
+    public String getContentType(Resource resource) {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String location = mci.getContentLocation();
+
+                // if no location is explicitly specified, use the resource URI itself
+                if(location == null && resource instanceof KiWiUriResource && ((KiWiUriResource)resource).stringValue().startsWith("http://")) {
+                    location = ((KiWiUriResource)resource).stringValue();
+                }
+
+                try {
+                    if(location != null) {
+                        log.info("reading remote resource {}",location);
+                        HttpHead head = new HttpHead(location);
+
+                        return httpClientService.execute(head, new ResponseHandler<String>() {
+                            @Override
+                            public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+                                if (response.getStatusLine().getStatusCode() == 200)
+                                    return response.getFirstHeader("Content-Type").getValue().split(";")[0];
+                                else
+                                    return null;
+                            }
+                        });
+                    } else
+                        return null;
+                } catch(IOException ex) {
+                    return null;
+                }
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+            return null;
+        }
+    }
+
+    /**
+     * Return the number of bytes the content of this resource contains.
+     *
+     * @param resource resource for which to return the content length
+     * @return byte count for the resource content
+     */
+    @Override
+    public long getContentLength(Resource resource, String mimetype) {
+        try {
+            RepositoryConnection conn = sesameService.getConnection();
+            try {
+                MediaContentItem mci = FacadingFactory.createFacading(conn).createFacade(resource, MediaContentItem.class);
+
+                String location = mci.getContentLocation();
+
+                // if no location is explicitly specified, use the resource URI itself
+                if(location == null && resource instanceof KiWiUriResource && ((KiWiUriResource)resource).stringValue().startsWith("http://")) {
+                    location = ((KiWiUriResource)resource).stringValue();
+                }
+
+                try {
+                    if(location != null) {
+                        log.info("reading remote resource {}",location);
+                        HttpHead head = new HttpHead(location);
+
+                        return httpClientService.execute(head, new ResponseHandler<Long>() {
+
+                            @Override
+                            public Long handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+                                if (response.getStatusLine().getStatusCode() == 200)
+                                    return Long.parseLong(response.getFirstHeader("Content-Length").getValue());
+                                else
+                                    return 0l;
+                            }
+                        });
+                    } else
+                        return 0;
+                } catch(Exception ex) {
+                    return 0;
+                }
+            } finally {
+                conn.commit();
+                conn.close();
+            }
+        } catch (RepositoryException ex) {
+            handleRepositoryException(ex,FileSystemContentReader.class);
+            return 0;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/c32963d5/lmf-core/src/main/java/kiwi/core/services/exporter/ExporterServiceImpl.java
----------------------------------------------------------------------
diff --git a/lmf-core/src/main/java/kiwi/core/services/exporter/ExporterServiceImpl.java b/lmf-core/src/main/java/kiwi/core/services/exporter/ExporterServiceImpl.java
new file mode 100644
index 0000000..2d4efe5
--- /dev/null
+++ b/lmf-core/src/main/java/kiwi/core/services/exporter/ExporterServiceImpl.java
@@ -0,0 +1,331 @@
+/**
+ * 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 kiwi.core.services.exporter;
+
+import com.google.common.collect.ImmutableSet;
+import kiwi.core.api.exporter.ExportService;
+import kiwi.core.api.io.LMFIOService;
+import kiwi.core.api.triplestore.SesameService;
+import kiwi.core.exception.io.UnsupportedExporterException;
+import org.openrdf.model.URI;
+import org.openrdf.repository.RepositoryConnection;
+import org.openrdf.repository.RepositoryException;
+import org.openrdf.rio.RDFFormat;
+import org.openrdf.rio.RDFHandlerException;
+import org.openrdf.rio.RDFWriter;
+import org.openrdf.rio.Rio;
+import org.slf4j.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Set;
+
+/**
+ * Methods for writing triple data into different targets using different exporters.
+ *
+ * @author Sebastian Schaffert
+ *
+ */
+@ApplicationScoped
+public class ExporterServiceImpl implements ExportService {
+
+    @Inject
+    private Logger log;
+
+    @Inject
+    private LMFIOService ioService;
+
+
+    @Inject
+    private SesameService sesameService;
+
+
+
+    /**
+     * Get a collection of all mime types accepted by this exporter. Used for automatically
+     * selecting the appropriate exporter in ExportService.
+     *
+     * @return a set of strings representing the mime types accepted by this exporter
+     */
+    @Override
+    public Set<String> getProducedTypes() {
+        return ImmutableSet.copyOf(ioService.getProducedTypes());
+    }
+
+    /**
+     * Export the triple data contained in the named graph passed as argument "context" and return
+     * it as a Java string using the serialisation format specified by "mimeType".
+     * <p/>
+     * The context parameter limits the exported triples to the named graph represented by this
+     * resource. If it is set to null, all named graphs will be exported.
+     * <p/>
+     * The mime type must be supported by at least one of the registered exporters, otherwise an
+     * UnsupportedExporterException. Available mime types can be retrieved using the getProducedTypes()
+     * method.
+     *
+     *
+     * @param context  the named graph to export; if null, all named graphs will be exported
+     * @param mimeType a mime type registered by an exporter
+     * @throws kiwi.core.exception.io.UnsupportedExporterException
+     *                             in case there is no matching exporter for the given mime type
+     */
+    @Override
+    public String exportData(URI context, String mimeType) throws UnsupportedExporterException {
+        RDFFormat serializer = ioService.getSerializer(mimeType);
+        if(serializer == null) {
+            log.warn("could not find serializer for MIME type {}",mimeType);
+            throw new UnsupportedExporterException("No serializer for mime type "+mimeType);
+        }
+        try {
+            StringWriter writer = new StringWriter();
+            exportData(writer,context,mimeType);
+            return writer.toString();
+        } catch (IOException e) {
+            log.error("impossible I/O error while writing to string buffer",e);
+            return null;
+        }
+    }
+
+    /**
+     * Export the triple data contained in the named graph passed as argument "context" and write it
+     * to the writer given as first argument using the serialisation format specified by "mimeType".
+     * <p/>
+     * The context parameter limits the exported triples to the named graph represented by this
+     * resource. If it is set to null, all named graphs will be exported.
+     * <p/>
+     * The mime type must be supported by at least one of the registered exporters, otherwise an
+     * UnsupportedExporterException. Available mime types can be retrieved using the getProducedTypes()
+     * method.
+     *
+     *
+     * @param writer   the writer to write the triples to; will be closed when the triples are written
+     * @param context  the named graph to export; if null, all named graphs will be exported
+     * @param mimeType a mime type registered by an exporter
+     * @throws kiwi.core.exception.io.UnsupportedExporterException
+     *                             in case there is no matching exporter for the given mime type
+     * @throws java.io.IOException in case there is an error writing to the output
+     */
+    @Override
+    public void exportData(Writer writer, URI context, String mimeType) throws UnsupportedExporterException, IOException {
+        RDFFormat serializer = ioService.getSerializer(mimeType);
+        if(serializer == null) {
+            log.warn("could not find serializer for MIME type {}",mimeType);
+            throw new UnsupportedExporterException("No serializer for mime type "+mimeType);
+        }
+
+        // HINT: This method might be executed outside a transaction!
+        RDFWriter handler = Rio.createWriter(serializer,writer);
+        try {
+            RepositoryConnection connection = sesameService.getConnection();
+            connection.begin();
+            try {
+                if(context == null) {
+                    connection.exportStatements(null,null,null,true,handler);
+                } else {
+                    connection.exportStatements(null,null,null,true,handler,context);
+                }
+            } finally {
+                connection.commit();
+                connection.close();
+            }
+        } catch (RepositoryException e) {
+            throw new IOException("error while getting repository connection");
+        } catch (RDFHandlerException e) {
+            throw new IOException("error while writing RDF data to stream");
+        }
+    }
+
+    /**
+     * Export the triple data contained in the named graph passed as argument "context" and write it
+     * to the output stream given as first argument using the serialisation format specified by "mimeType".
+     * <p/>
+     * The context parameter limits the exported triples to the named graph represented by this
+     * resource. If it is set to null, all named graphs will be exported.
+     * <p/>
+     * The mime type must be supported by at least one of the registered exporters, otherwise an
+     * UnsupportedExporterException. Available mime types can be retrieved using the getProducedTypes()
+     * method.
+     *
+     *
+     * @param outputStream the OutputStream to write the triples to; data will be written using UTF-8 encoding;
+     *                     will be closed when the triples are written
+     * @param context      the named graph to export; if null, all named graphs will be exported
+     * @param mimeType     a mime type registered by an exporter
+     * @throws kiwi.core.exception.io.UnsupportedExporterException
+     *                             in case there is no matching exporter for the given mime type
+     * @throws java.io.IOException in case there is an error writing to the output
+     */
+    @Override
+    public void exportData(OutputStream outputStream, URI context, String mimeType) throws UnsupportedExporterException, IOException {
+        RDFFormat serializer = ioService.getSerializer(mimeType);
+        if(serializer == null) {
+            log.warn("could not find serializer for MIME type {}",mimeType);
+            throw new UnsupportedExporterException("No serializer for mime type "+mimeType);
+        }
+        // HINT: This method might be executed outside a transaction!
+        RDFWriter handler = Rio.createWriter(serializer,outputStream);
+        try {
+            RepositoryConnection connection = sesameService.getConnection();
+            try {
+                if(context == null) {
+                    connection.exportStatements(null,null,null,true,handler);
+                } else {
+                    connection.exportStatements(null,null,null,true,handler,context);
+                }
+            } finally {
+                connection.commit();
+                connection.close();
+            }
+        } catch (RepositoryException e) {
+            throw new IOException("error while getting repository connection");
+        } catch (RDFHandlerException e) {
+            throw new IOException("error while writing RDF data to stream");
+        }
+    }
+
+
+    /**
+     * Export the triple data for the given resource contained in the named graph passed as argument "context" and return
+     * it as a Java string using the serialisation format specified by "mimeType".
+     * <p/>
+     * The context parameter limits the exported triples to the named graph represented by this
+     * resource. If it is set to null, all named graphs will be exported.
+     * <p/>
+     * The mime type must be supported by at least one of the registered exporters, otherwise an
+     * UnsupportedExporterException. Available mime types can be retrieved using the getProducedTypes()
+     * method.
+     *
+     * @param context  the named graph to export; if null, all named graphs will be exported
+     * @param mimeType a mime type registered by an exporter
+     * @throws kiwi.core.exception.io.UnsupportedExporterException
+     *          in case there is no matching exporter for the given mime type
+     */
+    @Override
+    public String exportData(URI resource, URI context, String mimeType) throws UnsupportedExporterException {
+        RDFFormat serializer = ioService.getSerializer(mimeType);
+        if(serializer == null) {
+            log.warn("could not find serializer for MIME type {}",mimeType);
+            throw new UnsupportedExporterException("No serializer for mime type "+mimeType);
+        }
+        try {
+            StringWriter writer = new StringWriter();
+            exportData(writer,resource,context,mimeType);
+            return writer.toString();
+        } catch (IOException e) {
+            log.error("impossible I/O error while writing to string buffer",e);
+            return null;
+        }
+    }
+
+    /**
+     * Export the triple data for the given resource contained in the named graph passed as argument "context" and write it
+     * to the writer given as first argument using the serialisation format specified by "mimeType".
+     * <p/>
+     * The context parameter limits the exported triples to the named graph represented by this
+     * resource. If it is set to null, all named graphs will be exported.
+     * <p/>
+     * The mime type must be supported by at least one of the registered exporters, otherwise an
+     * UnsupportedExporterException. Available mime types can be retrieved using the getProducedTypes()
+     * method.
+     *
+     *
+     * @param writer   the writer to write the triples to; will be closed when the triples are written
+     * @param context  the named graph to export; if null, all named graphs will be exported
+     * @param mimeType a mime type registered by an exporter
+     * @throws kiwi.core.exception.io.UnsupportedExporterException
+     *                             in case there is no matching exporter for the given mime type
+     * @throws java.io.IOException in case there is an error writing to the output
+     */
+    @Override
+    public void exportData(Writer writer, URI resource, URI context, String mimeType) throws UnsupportedExporterException, IOException {
+        RDFFormat serializer = ioService.getSerializer(mimeType);
+        if(serializer == null) {
+            log.warn("could not find serializer for MIME type {}",mimeType);
+            throw new UnsupportedExporterException("No serializer for mime type "+mimeType);
+        }
+
+        // HINT: This method might be executed outside a transaction!
+        RDFWriter handler = Rio.createWriter(serializer,writer);
+        try {
+            RepositoryConnection connection = sesameService.getConnection();
+            try {
+                if(context == null) {
+                    connection.exportStatements(resource,null,null,true,handler);
+                } else {
+                    connection.exportStatements(resource,null,null,true,handler,context);
+                }
+            } finally {
+                connection.commit();
+                connection.close();
+            }
+        } catch (RepositoryException e) {
+            throw new IOException("error while getting repository connection");
+        } catch (RDFHandlerException e) {
+            throw new IOException("error while writing RDF data to stream");
+        }
+    }
+
+    /**
+     * Export the triple data for the given resource contained in the named graph passed as argument "context" and write it
+     * to the output stream given as first argument using the serialisation format specified by "mimeType".
+     * <p/>
+     * The context parameter limits the exported triples to the named graph represented by this
+     * resource. If it is set to null, all named graphs will be exported.
+     * <p/>
+     * The mime type must be supported by at least one of the registered exporters, otherwise an
+     * UnsupportedExporterException. Available mime types can be retrieved using the getProducedTypes()
+     * method.
+     *
+     * @param outputStream the OutputStream to write the triples to; data will be written using UTF-8 encoding;
+     *                     will be closed when the triples are written
+     * @param context      the named graph to export; if null, all named graphs will be exported
+     * @param mimeType     a mime type registered by an exporter
+     * @throws kiwi.core.exception.io.UnsupportedExporterException
+     *                             in case there is no matching exporter for the given mime type
+     * @throws java.io.IOException in case there is an error writing to the output
+     */
+    @Override
+    public void exportData(OutputStream outputStream, URI resource, URI context, String mimeType) throws UnsupportedExporterException, IOException {
+        RDFFormat serializer = ioService.getSerializer(mimeType);
+        if(serializer == null) {
+            log.warn("could not find serializer for MIME type {}",mimeType);
+            throw new UnsupportedExporterException("No serializer for mime type "+mimeType);
+        }
+        // HINT: This method might be executed outside a transaction!
+        RDFWriter handler = Rio.createWriter(serializer,outputStream);
+        try {
+            RepositoryConnection connection = sesameService.getConnection();
+            try {
+                if(context == null) {
+                    connection.exportStatements(resource,null,null,true,handler);
+                } else {
+                    connection.exportStatements(resource,null,null,true,handler,context);
+                }
+            } finally {
+                connection.commit();
+                connection.close();
+            }
+        } catch (RepositoryException e) {
+            throw new IOException("error while getting repository connection");
+        } catch (RDFHandlerException e) {
+            throw new IOException("error while writing RDF data to stream");
+        }
+    }
+}