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

[8/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/services/http/HttpClientServiceImpl.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/HttpClientServiceImpl.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/HttpClientServiceImpl.java
new file mode 100644
index 0000000..19a33cc
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/HttpClientServiceImpl.java
@@ -0,0 +1,808 @@
+/**
+ * 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.services.http;
+
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+import org.apache.marmotta.platform.core.api.http.HttpClientService;
+import org.apache.marmotta.platform.core.api.statistics.StatisticsModule;
+import org.apache.marmotta.platform.core.api.statistics.StatisticsService;
+import org.apache.marmotta.platform.core.api.task.Task;
+import org.apache.marmotta.platform.core.api.task.TaskManagerService;
+import org.apache.marmotta.platform.core.events.ConfigurationChangedEvent;
+import org.apache.marmotta.platform.core.qualifiers.cache.LMFCache;
+import org.apache.marmotta.platform.core.services.http.response.LastModifiedResponseHandler;
+import org.apache.marmotta.platform.core.services.http.response.StatusCodeResponseHandler;
+import org.apache.marmotta.platform.core.services.http.response.StringBodyResponseHandler;
+import net.sf.ehcache.Ehcache;
+import org.apache.http.*;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.cache.CacheResponseStatus;
+import org.apache.http.client.cache.HttpCacheStorage;
+import org.apache.http.client.methods.*;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.client.DefaultRedirectStrategy;
+import org.apache.http.impl.client.cache.CacheConfig;
+import org.apache.http.impl.client.cache.CachingHttpClient;
+import org.apache.http.impl.client.cache.ehcache.EhcacheHttpCacheStorage;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
+import org.apache.http.params.*;
+import org.apache.http.pool.PoolStats;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+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.nio.charset.Charset;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ **/
+@ApplicationScoped
+public class HttpClientServiceImpl implements HttpClientService {
+
+    private static final String          TASK_GROUP_CLIENT       = "HttpClient";
+
+    private static final String[]        KEYS                    = {
+        "requests executed", "payload sent", "payload received", "payload from cache",
+        "ConnectionManager", "max connections", "available connections",
+        "active connections", "requests waiting"
+    };
+
+    private static final Charset         DEFAULT_CHARSET         = Charset.defaultCharset();
+
+    @Inject
+    private Logger                       log;
+
+    @Inject
+    private TaskManagerService           taskManagerService;
+
+    @Inject
+    private ConfigurationService         configurationService;
+
+    @Inject
+    private StatisticsService            statisticsService;
+
+    @Inject
+    @LMFCache("http-client-cache")
+    private Instance<Ehcache>            ehcache;
+
+    private HttpClient                   httpClient;
+    private IdleConnectionMonitorThread  idleConnectionMonitorThread;
+    private BasicHttpParams              httpParams;
+
+    private final AtomicLong             bytesSent               = new AtomicLong();
+    private final AtomicLong             bytesReceived           = new AtomicLong();
+    private final AtomicLong             bytesFromCache          = new AtomicLong();
+    private final AtomicLong             requestsExecuted        = new AtomicLong();
+
+    private final ReentrantReadWriteLock lock                    = new ReentrantReadWriteLock();
+
+    /**
+     * Execute a request and pass the response to the provided handler.
+     * 
+     * @param request
+     * @param handler
+     * @return
+     * @throws ClientProtocolException
+     * @throws IOException
+     */
+    @Override
+    public <T> T execute(HttpRequestBase request, ResponseHandler<? extends T> handler) throws ClientProtocolException, IOException {
+        if (handler == null)
+            throw new IllegalArgumentException("Response handler must not be null.");
+        final long start = System.nanoTime();
+        String logMessageF = "Request '{}' failed after {}";
+        try {
+            final T result = httpClient.execute(request, handler);
+            logMessageF = "Request '{}' took {}";
+            return result;
+        } finally {
+            log.debug(logMessageF, request.getRequestLine(), formatNanoDuration(System.nanoTime() - start));
+        }
+    }
+
+    /**
+     * Get a ready-to-use {@link HttpClient}.
+     */
+    @Override
+    public HttpClient getHttpClient() {
+        return new ReadLockHttpClient();
+    }
+
+    /**
+     * Execute a request.
+     * 
+     * <b>ATTENTION</b> Make sure to close the {@link InputStream} in the {@link HttpEntity}, or
+     * just use {@link #cleanupResponse(HttpResponse)}!
+     * 
+     * @param request
+     * @return
+     * @throws ClientProtocolException
+     * @throws IOException
+     * @see #cleanupResponse(HttpResponse)
+     * @see EntityUtils#consume(HttpEntity)
+     */
+    @Override
+    public HttpResponse execute(HttpRequestBase request) throws ClientProtocolException, IOException {
+        final long start = System.nanoTime();
+        String logMessageF = "Request '{}' failed after {}";
+        try {
+            final HttpResponse resp = httpClient.execute(request);
+            logMessageF = "Request '{}' took {}";
+            return resp;
+        } finally {
+            log.debug(logMessageF, request.getRequestLine(), formatNanoDuration(System.nanoTime() - start));
+        }
+    }
+
+    @Override
+    public void cleanupResponse(HttpResponse response) {
+        if (response == null) throw new IllegalArgumentException("Response must not be null");
+        EntityUtils.consumeQuietly(response.getEntity());
+    }
+
+    private final String formatNanoDuration(long nano) {
+        // convert to microseconds (1/1000s)
+        final long micro = nano / 1000;
+        if (micro > 1000 * 1000l) {
+            // more than a second
+            long millis = micro / 1000l;
+            if (millis > 60000l)
+                // more than a minute
+                return String.format("%d min %.1f sec", millis / (1000 * 60), 0.001d * millis % 60);
+            else
+                // more than a second
+                return String.format("%.1f sec", 0.001d * millis);
+        } else
+            // some millis
+            return String.format("%f ms", 0.001d * micro);
+    }
+
+    @Override
+    public String doGet(String requestUrl) throws IOException {
+        return doGet(requestUrl, new StringBodyResponseHandler());
+    }
+
+    @Override
+    public <T> T doGet(String requestUrl, ResponseHandler<? extends T> responseHandler) throws IOException {
+        final HttpGet get = new HttpGet(requestUrl);
+
+        return execute(get, responseHandler);
+    }
+
+    @Override
+    public <T> T doPost(String requestUrl, HttpEntity body, ResponseHandler<? extends T> responseHandler) throws IOException {
+        HttpPost post = new HttpPost(requestUrl);
+        post.setEntity(body);
+
+        return execute(post, responseHandler);
+    }
+
+    @Override
+    public String doPost(String url, String body) throws IOException {
+        return doPost(url, new StringEntity(body, ContentType.create("text/plain", DEFAULT_CHARSET)), new StringBodyResponseHandler());
+    }
+
+    @Override
+    public <T> T doPut(String url, HttpEntity body, ResponseHandler<? extends T> responseHandler) throws IOException {
+        HttpPut put = new HttpPut(url);
+        put.setEntity(body);
+
+        return execute(put, responseHandler);
+    }
+
+    @Override
+    public String doPut(String url, String body) throws IOException {
+        return doPut(url, new StringEntity(body, ContentType.create("text/plain", DEFAULT_CHARSET)), new StringBodyResponseHandler());
+    }
+
+    @Override
+    public <T> T doDelete(String url, ResponseHandler<? extends T> responseHandler) throws IOException {
+        HttpDelete delete = new HttpDelete(url);
+
+        return execute(delete, responseHandler);
+    }
+
+    @Override
+    public int doDelete(String url) throws IOException {
+        return doDelete(url, new StatusCodeResponseHandler());
+    }
+
+    @Override
+    public <T> T doHead(String url, ResponseHandler<? extends T> responseHandler) throws IOException {
+        HttpHead head = new HttpHead(url);
+
+        return execute(head, responseHandler);
+    }
+
+    @Override
+    public Date doHead(String url) throws IOException {
+        return doHead(url, new LastModifiedResponseHandler());
+    }
+
+    protected void onConfigurationChange(@Observes ConfigurationChangedEvent event) {
+        if (event.containsChangedKeyWithPrefix("core.http.")) {
+            try {
+                lock.writeLock().lock();
+                shutdown();
+                initialize();
+            } finally {
+                lock.writeLock().unlock();
+            }
+        }
+    }
+
+    @PostConstruct
+    protected void initialize() {
+        try {
+            lock.writeLock().lock();
+
+            httpParams = new BasicHttpParams();
+            String userAgentString =
+                    "LMF/" + configurationService.getStringConfiguration("kiwi.version") +
+                    " (running at " + configurationService.getServerUri() + ")" +
+                    " lmf-core/" + configurationService.getStringConfiguration("kiwi.version");
+            userAgentString = configurationService.getStringConfiguration("core.http.user_agent", userAgentString);
+
+            httpParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, configurationService.getIntConfiguration("core.http.so_timeout", 60000));
+            httpParams.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT,
+                    configurationService.getIntConfiguration("core.http.connection_timeout", 10000));
+
+            httpParams.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, true);
+            httpParams.setIntParameter(ClientPNames.MAX_REDIRECTS, 3);
+
+            SchemeRegistry schemeRegistry = new SchemeRegistry();
+            schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
+            schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
+
+            PoolingClientConnectionManager cm = new PoolingClientConnectionManager(schemeRegistry);
+            cm.setMaxTotal(configurationService.getIntConfiguration("core.http.max_connections", 20));
+            cm.setDefaultMaxPerRoute(configurationService.getIntConfiguration("core.http.max_connections_per_route", 10));
+
+            final DefaultHttpClient hc = new DefaultHttpClient(cm, httpParams);
+            hc.setRedirectStrategy(new LMFRedirectStrategy());
+            hc.setHttpRequestRetryHandler(new LMFHttpRequestRetryHandler());
+            hc.removeRequestInterceptorByClass(org.apache.http.protocol.RequestUserAgent.class);
+            hc.addRequestInterceptor(new LMFRequestUserAgent(userAgentString));
+
+            if (configurationService.getBooleanConfiguration("core.http.client_cache_enable", true)) {
+                CacheConfig cacheConfig = new CacheConfig();
+                // FIXME: Hardcoded constants - is this useful?
+                cacheConfig.setMaxCacheEntries(1000);
+                cacheConfig.setMaxObjectSize(81920);
+
+                final HttpCacheStorage cacheStore = new EhcacheHttpCacheStorage(ehcache.get(), cacheConfig);
+
+                this.httpClient = new MonitoredHttpClient(new CachingHttpClient(hc, cacheStore, cacheConfig));
+            } else {
+                this.httpClient = new MonitoredHttpClient(hc);
+            }
+            bytesSent.set(0);
+            bytesReceived.set(0);
+            requestsExecuted.set(0);
+
+            idleConnectionMonitorThread = new IdleConnectionMonitorThread(httpClient.getConnectionManager());
+            idleConnectionMonitorThread.start();
+
+            StatisticsProvider stats = new StatisticsProvider(cm);
+            statisticsService.registerModule(HttpClientService.class.getSimpleName(), stats);
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    @PreDestroy
+    protected void shutdown() {
+        try {
+            lock.writeLock().lock();
+            statisticsService.unregisterModule(HttpClientService.class.getSimpleName());
+            idleConnectionMonitorThread.shutdown();
+            httpClient.getConnectionManager().shutdown();
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    private static class LMFRequestUserAgent implements HttpRequestInterceptor {
+
+        private final String baseUserAgent;
+
+        public LMFRequestUserAgent(String baseUserAgent) {
+            this.baseUserAgent = baseUserAgent;
+        }
+
+        private final String buildUserAgentString(String localPart) {
+            if (localPart == null || localPart.length() == 0)
+                return baseUserAgent;
+            else if (localPart.endsWith(baseUserAgent)) return localPart;
+            return localPart + " " + baseUserAgent;
+        }
+
+        @Override
+        public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
+            if (request == null) throw new IllegalArgumentException("HTTP request must not be null");
+            if (!request.containsHeader(HTTP.USER_AGENT)) {
+                String useragent = HttpProtocolParams.getUserAgent(request.getParams());
+                request.addHeader(HTTP.USER_AGENT, buildUserAgentString(useragent));
+            } else {
+                String useragent = request.getFirstHeader(HTTP.USER_AGENT).getValue();
+                request.setHeader(HTTP.USER_AGENT, buildUserAgentString(useragent));
+            }
+        }
+
+    }
+
+    private static class LMFRedirectStrategy extends DefaultRedirectStrategy {
+        @Override
+        public boolean isRedirected(HttpRequest request, HttpResponse response, HttpContext context) throws ProtocolException {
+            if (request == null)
+                throw new IllegalArgumentException("HTTP request must not be null");
+            if (response == null)
+                throw new IllegalArgumentException("HTTP response must not be null");
+
+            int statusCode = response.getStatusLine().getStatusCode();
+            String method = request.getRequestLine().getMethod();
+            Header locationHeader = response.getFirstHeader("location");
+            switch (statusCode) {
+            case HttpStatus.SC_MOVED_TEMPORARILY:
+                return (method.equalsIgnoreCase(HttpGet.METHOD_NAME) || method.equalsIgnoreCase(HttpHead.METHOD_NAME))
+                        && locationHeader != null;
+            case HttpStatus.SC_MOVED_PERMANENTLY:
+            case HttpStatus.SC_TEMPORARY_REDIRECT:
+                return method.equalsIgnoreCase(HttpGet.METHOD_NAME)
+                        || method.equalsIgnoreCase(HttpHead.METHOD_NAME);
+            case HttpStatus.SC_SEE_OTHER:
+                return true;
+            case HttpStatus.SC_MULTIPLE_CHOICES:
+                return true;
+            default:
+                return false;
+            } // end of switch
+        }
+    }
+
+    private static class LMFHttpRequestRetryHandler implements HttpRequestRetryHandler {
+        /**
+         * Determines if a method should be retried after an IOException occurs
+         * during execution.
+         * 
+         * @param exception
+         *            the exception that occurred
+         * @param executionCount
+         *            the number of times this method has been unsuccessfully
+         *            executed
+         * @param context
+         *            the context for the request execution
+         * @return <code>true</code> if the method should be retried, <code>false</code> otherwise
+         */
+        @Override
+        public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
+            return false;
+        }
+    }
+
+    private static class IdleConnectionMonitorThread extends Thread {
+
+        private final ClientConnectionManager connMgr;
+        private volatile boolean              shutdown;
+
+        public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
+            super("HttpClientService Idle Connection Manager");
+            this.connMgr = connMgr;
+            setDaemon(true);
+        }
+
+        @Override
+        public void run() {
+            try {
+                while (!shutdown) {
+                    synchronized (this) {
+                        wait(5000);
+                        // Close expired connections
+                        connMgr.closeExpiredConnections();
+                        // Optionally, close connections
+                        // that have been idle longer than 30 sec
+                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
+                    }
+                }
+            } catch (InterruptedException ex) {
+                // terminate
+            }
+        }
+
+        public void shutdown() {
+            synchronized (this) {
+                shutdown = true;
+                notifyAll();
+            }
+        }
+
+    }
+
+    private class StatisticsProvider implements StatisticsModule {
+
+        private boolean                        enabled;
+        private PoolingClientConnectionManager connectionManager;
+
+        public StatisticsProvider(PoolingClientConnectionManager cm) {
+            this.connectionManager = cm;
+            enabled = true;
+        }
+
+        @Override
+        public void enable() {
+            enabled = true;
+        }
+
+        @Override
+        public void disable() {
+            enabled = false;
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        @Override
+        public List<String> getPropertyNames() {
+            return Collections.unmodifiableList(Arrays.asList(KEYS));
+        }
+
+        @Override
+        public Map<String, String> getStatistics() {
+            int i = 0;
+            final Map<String, String> data = new LinkedHashMap<String, String>();
+            data.put(KEYS[i++], String.valueOf(requestsExecuted.get()));
+            data.put(KEYS[i++], humanReadableBytes(bytesSent.get(), false));
+            data.put(KEYS[i++], humanReadableBytes(bytesReceived.get(), false));
+            data.put(KEYS[i++], humanReadableBytes(bytesFromCache.get(), false));
+
+            final PoolStats cmStats = connectionManager.getTotalStats();
+            data.put(KEYS[i++], connectionManager.getClass().getSimpleName());
+            data.put(KEYS[i++], String.valueOf(cmStats.getMax()));
+            data.put(KEYS[i++], String.valueOf(cmStats.getAvailable()));
+            data.put(KEYS[i++], String.valueOf(cmStats.getLeased()));
+            data.put(KEYS[i++], String.valueOf(cmStats.getPending()));
+            return data;
+        }
+
+        private String humanReadableBytes(long bytes, boolean si) {
+            final int unit = si ? 1000 : 1024;
+            if (bytes < unit) return bytes + " B";
+            int exp = (int) (Math.log(bytes) / Math.log(unit));
+            String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
+            return String.format("%.2f %sB", bytes / Math.pow(unit, exp), pre);
+        }
+
+        @Override
+        public String getName() {
+            return HttpClientService.class.getSimpleName();
+        }
+
+    }
+
+    private class ReadLockHttpClient implements HttpClient {
+
+        private HttpParams params;
+
+        public ReadLockHttpClient() {
+            this.params = new DefaultedHttpParams(new BasicHttpParams(), httpParams);
+        }
+
+        @Override
+        public HttpParams getParams() {
+            return params;
+        }
+
+        @Override
+        public ClientConnectionManager getConnectionManager() {
+            return httpClient.getConnectionManager();
+        }
+
+        @Override
+        public HttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException {
+            lock.readLock().lock();
+            try {
+                return httpClient.execute(request);
+            } finally {
+                lock.readLock().unlock();
+            }
+        }
+
+        @Override
+        public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException, ClientProtocolException {
+            lock.readLock().lock();
+            try {
+                return httpClient.execute(request, context);
+            } finally {
+                lock.readLock().unlock();
+            }
+        }
+
+        @Override
+        public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException, ClientProtocolException {
+            lock.readLock().lock();
+            try {
+                return httpClient.execute(target, request);
+            } finally {
+                lock.readLock().unlock();
+            }
+        }
+
+        @Override
+        public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) throws IOException, ClientProtocolException {
+            lock.readLock().lock();
+            try {
+                return httpClient.execute(target, request, context);
+            } finally {
+                lock.readLock().unlock();
+            }
+        }
+
+        @Override
+        public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException, ClientProtocolException {
+            lock.readLock().lock();
+            try {
+                return httpClient.execute(request, responseHandler);
+            } finally {
+                lock.readLock().unlock();
+            }
+        }
+
+        @Override
+        public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException,
+        ClientProtocolException {
+            lock.readLock().lock();
+            try {
+                return httpClient.execute(request, responseHandler, context);
+            } finally {
+                lock.readLock().unlock();
+            }
+        }
+
+        @Override
+        public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler) throws IOException,
+        ClientProtocolException {
+            lock.readLock().lock();
+            try {
+                return httpClient.execute(target, request, responseHandler);
+            } finally {
+                lock.readLock().unlock();
+            }
+        }
+
+        @Override
+        public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
+                throws IOException, ClientProtocolException {
+            lock.readLock().lock();
+            try {
+                return httpClient.execute(target, request, responseHandler, context);
+            } finally {
+                lock.readLock().unlock();
+            }
+        }
+
+    }
+
+    protected class MonitoredHttpClient implements HttpClient {
+
+        private final HttpClient delegate;
+
+        public MonitoredHttpClient(HttpClient delegate) {
+            this.delegate = delegate;
+        }
+
+        @Override
+        public HttpParams getParams() {
+            return delegate.getParams();
+        }
+
+        @Override
+        public ClientConnectionManager getConnectionManager() {
+            return delegate.getConnectionManager();
+        }
+
+        @Override
+        public HttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException {
+            final Task task = preProcess(request);
+
+            final HttpResponse response;
+            lock.readLock().lock();
+            try {
+                response = delegate.execute(request);
+            } catch (ClientProtocolException cpe) {
+                task.endTask();
+                throw cpe;
+            } catch (IOException io) {
+                task.endTask();
+                throw io;
+            } finally {
+                lock.readLock().unlock();
+            }
+
+            return postProcess(response, null, task);
+        }
+
+        @Override
+        public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException, ClientProtocolException {
+            final Task task = preProcess(request);
+
+            final HttpResponse response;
+            lock.readLock().lock();
+            try {
+                response = delegate.execute(request, context);
+            } catch (ClientProtocolException cpe) {
+                task.endTask();
+                throw cpe;
+            } catch (IOException io) {
+                task.endTask();
+                throw io;
+            } finally {
+                lock.readLock().unlock();
+            }
+
+            return postProcess(response, context, task);
+        }
+
+        private Task preProcess(HttpRequest request) {
+            final RequestLine rl = request.getRequestLine();
+            final String taskName = String.format("%S %s %S", rl.getMethod(), rl.getUri(), request.getProtocolVersion());
+
+            final Task task = taskManagerService.createSubTask(taskName, TASK_GROUP_CLIENT);
+            task.updateMessage("preparing request");
+            task.updateDetailMessage("method", rl.getMethod());
+            task.updateDetailMessage("url", rl.getUri());
+            // TODO: some more detail messages?
+
+            if (request instanceof HttpEntityEnclosingRequest) {
+                // To report upload progress, the entity is wrapped in a MonitoredHttpEntity.
+                final HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
+                entityRequest.setEntity(new MonitoredHttpEntity(entityRequest.getEntity(), task, bytesSent));
+            }
+
+            task.updateMessage("sending request");
+            return task;
+        }
+
+        private HttpResponse postProcess(final HttpResponse response, HttpContext context, final Task task) {
+            requestsExecuted.incrementAndGet();
+            task.resetProgress();
+            task.updateMessage("retrieving response");
+            if (response.getEntity() != null) {
+                boolean cachedResponse;
+                if (context != null) {
+                    CacheResponseStatus cacheRespStatus = (CacheResponseStatus) context.getAttribute(CachingHttpClient.CACHE_RESPONSE_STATUS);
+                    // To report download progress, the entity is wrapped in a MonitoredHttpEntity.
+                    cachedResponse = cacheRespStatus != null && cacheRespStatus != CacheResponseStatus.CACHE_MISS;
+                } else {
+                    cachedResponse = false;
+                }
+                response.setEntity(new MonitoredHttpEntity(response.getEntity(), task, cachedResponse ? bytesFromCache : bytesReceived));
+            } else {
+                task.endTask();
+            }
+            return response;
+        }
+
+        @Override
+        public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException, ClientProtocolException {
+            final Task task = preProcess(request);
+
+            final HttpResponse response;
+            lock.readLock().lock();
+            try {
+                response = delegate.execute(target, request);
+            } catch (ClientProtocolException cpe) {
+                task.endTask();
+                throw cpe;
+            } catch (IOException io) {
+                task.endTask();
+                throw io;
+            } finally {
+                lock.readLock().unlock();
+            }
+
+            return postProcess(response, null, task);
+        }
+
+        @Override
+        public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) throws IOException, ClientProtocolException {
+            final Task task = preProcess(request);
+
+            final HttpResponse response;
+            lock.readLock().lock();
+            try {
+                response = delegate.execute(target, request, context);
+            } catch (ClientProtocolException cpe) {
+                task.endTask();
+                throw cpe;
+            } catch (IOException io) {
+                task.endTask();
+                throw io;
+            } finally {
+                lock.readLock().unlock();
+            }
+
+            return postProcess(response, context, task);
+        }
+
+        @Override
+        public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException, ClientProtocolException {
+            final HttpResponse response = execute(request);
+
+            return processResponse(responseHandler, response);
+        }
+
+        private <T> T processResponse(ResponseHandler<? extends T> responseHandler, final HttpResponse response) throws ClientProtocolException,
+        IOException {
+            try {
+                return responseHandler.handleResponse(response);
+            } finally {
+                // Make sure everything is cleaned up properly
+                EntityUtils.consume(response.getEntity());
+            }
+        }
+
+        @Override
+        public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException,
+        ClientProtocolException {
+            final HttpResponse response = execute(request, context);
+
+            return processResponse(responseHandler, response);
+        }
+
+        @Override
+        public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler) throws IOException,
+        ClientProtocolException {
+            final HttpResponse response = execute(target, request);
+
+            return processResponse(responseHandler, response);
+        }
+
+        @Override
+        public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
+                throws IOException, ClientProtocolException {
+            final HttpResponse response = execute(target, request, context);
+
+            return processResponse(responseHandler, response);
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/MonitoredHttpEntity.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/MonitoredHttpEntity.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/MonitoredHttpEntity.java
new file mode 100644
index 0000000..71b854d
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/MonitoredHttpEntity.java
@@ -0,0 +1,213 @@
+/**
+ * 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.services.http;
+
+import org.apache.marmotta.platform.core.api.task.Task;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.annotation.NotThreadSafe;
+import org.apache.http.entity.HttpEntityWrapper;
+
+import java.io.*;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * An implementation of {@link HttpEntity} that reports write/read operations to a {@link Task}.
+ * 
+ */
+@NotThreadSafe
+class MonitoredHttpEntity extends HttpEntityWrapper {
+
+    private MonitoredInputStream foo = null;
+    private final Task           monitor;
+    private final AtomicLong     bytesReceived;
+
+    /**
+     * Create a {@link MonitoredHttpEntity} based on the provided {@link HttpEntity} reporting
+     * progress to the {@link Task}.
+     * 
+     * @param delegate the {@link HttpEntity} to wrap.
+     * @param monitor the {@link Task} to report the progress to.
+     * @param bytesReceived {@link AtomicLong} to add transfered bytes to.
+     */
+    public MonitoredHttpEntity(HttpEntity delegate, Task monitor, AtomicLong bytesReceived) {
+        super(delegate);
+        this.monitor = monitor;
+        this.bytesReceived = bytesReceived;
+
+        monitorHeader(wrappedEntity.getContentType());
+        monitorHeader(wrappedEntity.getContentEncoding());
+    }
+
+    /**
+     * Create a {@link MonitoredHttpEntity} based on the provided {@link HttpEntity} reporting
+     * progress to the {@link Task}.
+     * 
+     * @param delegate the {@link HttpEntity} to wrap.
+     * @param monitor the {@link Task} to report the progress to.
+     */
+    public MonitoredHttpEntity(HttpEntity delegate, Task monitor) {
+        this(delegate, monitor, null);
+    }
+
+    @Override
+    public InputStream getContent() throws IOException {
+        if (foo == null) {
+            foo = new MonitoredInputStream(super.getContent());
+            if (monitor != null && getContentLength() > 0) {
+                monitor.updateTotalSteps(getContentLength());
+            }
+        }
+        return foo;
+    }
+
+    @Override
+    public void writeTo(OutputStream outstream) throws IOException {
+        if (monitor != null && getContentLength() > 0) {
+            monitor.updateTotalSteps(getContentLength());
+        }
+        super.writeTo(new MonitoredOutputStream(outstream));
+        if (monitor != null) {
+            monitor.updateMessage("waiting for response");
+        }
+    }
+
+    private void monitorHeader(final Header ct) {
+        if (monitor != null && ct != null) {
+            monitor.updateDetailMessage(ct.getName(), ct.getValue());
+        }
+    }
+
+    private void updateMonitor(long cPos, long delta) {
+        if (monitor != null) {
+            monitor.updateProgress(cPos);
+        }
+        if (bytesReceived != null) {
+            bytesReceived.addAndGet(delta);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        monitor.endTask();
+        super.finalize();
+    }
+
+    /**
+     * OutputStream that reports the progress on every write operation.
+     * 
+     */
+    protected class MonitoredOutputStream extends FilterOutputStream {
+
+        private long cPos;
+
+        public MonitoredOutputStream(OutputStream out) {
+            super(out);
+            cPos = 0;
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            super.write(b);
+            updateMonitor(++cPos, 1);
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            /* copied from FilterOutputStream.write(byte[], int, int) */
+            if ((off | len | b.length - (len + off) | off + len) < 0)
+                throw new IndexOutOfBoundsException();
+
+            for (int i = 0; i < len; i++) {
+                super.write(b[off + i]);
+            }
+            updateMonitor(cPos += len, len);
+        }
+
+        @Override
+        public void close() throws IOException {
+            super.close();
+        }
+
+    }
+
+    /**
+     * InputStream that reports the progress on every read operation.
+     */
+    protected class MonitoredInputStream extends FilterInputStream {
+
+        private long cPos, markPos;
+
+        protected MonitoredInputStream(InputStream in) {
+            super(in);
+            cPos = 0;
+        }
+
+        @Override
+        public int read() throws IOException {
+            final int i = super.read();
+            if (i >= 0) {
+                updateMonitor(++cPos, 1);
+            }
+            return i;
+        }
+
+        @Override
+        public int read(byte[] b) throws IOException {
+            final int i = super.read(b);
+            if (i > 0) {
+                updateMonitor(cPos += i, i);
+            }
+            return i;
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+            final int i = super.read(b, off, len);
+            if (i > 0) {
+                updateMonitor(cPos += i, i);
+            }
+            return i;
+        }
+
+        @Override
+        public long skip(long n) throws IOException {
+            final long i = super.skip(n);
+            if (i > 0) {
+                updateMonitor(cPos += i, i);
+            }
+            return i;
+        }
+
+        @Override
+        public synchronized void mark(int readlimit) {
+            super.mark(readlimit);
+            markPos = cPos;
+        }
+
+        @Override
+        public synchronized void reset() throws IOException {
+            super.reset();
+            updateMonitor(cPos = markPos, 0);
+        }
+
+        @Override
+        public void close() throws IOException {
+            monitor.endTask();
+            super.close();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/LastModifiedResponseHandler.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/LastModifiedResponseHandler.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/LastModifiedResponseHandler.java
new file mode 100644
index 0000000..fd964ad
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/LastModifiedResponseHandler.java
@@ -0,0 +1,63 @@
+/**
+ * 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.services.http.response;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class LastModifiedResponseHandler implements ResponseHandler<Date> {
+    // Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
+    private final SimpleDateFormat RFC_1123 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z");
+    // Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+    private final SimpleDateFormat RFC_1036 = new SimpleDateFormat("EEEE, dd-MMM-yy HH:mm:ss z");
+    // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
+    private final SimpleDateFormat ANSI_C   = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy");
+
+    @Override
+    public Date handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+        try {
+            Header lastModH = response.getFirstHeader("Last-Modified");
+            return lastModH != null ? parseDate(lastModH.getValue()) : null;
+        } finally {
+            EntityUtils.consume(response.getEntity());
+        }
+    }
+
+    private Date parseDate(String value) {
+        try {
+            return RFC_1123.parse(value);
+        } catch (ParseException e) {
+            try {
+                return RFC_1036.parse(value);
+            } catch (ParseException e1) {
+                try {
+                    return ANSI_C.parse(value);
+                } catch (ParseException e2) {
+                    return null;
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/StatusCodeResponseHandler.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/StatusCodeResponseHandler.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/StatusCodeResponseHandler.java
new file mode 100644
index 0000000..b3532a1
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/StatusCodeResponseHandler.java
@@ -0,0 +1,36 @@
+/**
+ * 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.services.http.response;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+
+public class StatusCodeResponseHandler implements ResponseHandler<Integer> {
+
+    @Override
+    public Integer handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+        try {
+            return response.getStatusLine().getStatusCode();
+        } finally {
+            EntityUtils.consume(response.getEntity());
+        }
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/StringBodyResponseHandler.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/StringBodyResponseHandler.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/StringBodyResponseHandler.java
new file mode 100644
index 0000000..8140621
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/http/response/StringBodyResponseHandler.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.services.http.response;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+
+public class StringBodyResponseHandler implements ResponseHandler<String> {
+
+    @Override
+    public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
+        if (response.getStatusLine().getStatusCode() == 200) {
+            final HttpEntity entity = response.getEntity();
+
+            return entity != null ? EntityUtils.toString(entity) : null;
+        }
+        return null;
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/importer/ImportServiceImpl.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/importer/ImportServiceImpl.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/importer/ImportServiceImpl.java
new file mode 100644
index 0000000..9add952
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/importer/ImportServiceImpl.java
@@ -0,0 +1,89 @@
+/**
+ * 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.services.importer;
+
+import org.apache.marmotta.platform.core.api.importer.ImportService;
+import org.apache.marmotta.platform.core.api.importer.Importer;
+import org.apache.marmotta.platform.core.exception.io.LMFImportException;
+import org.openrdf.model.Resource;
+import org.openrdf.model.URI;
+import org.slf4j.Logger;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.Instance;
+import javax.inject.Inject;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * User: Thomas Kurz
+ * Date: 02.02.11
+ * Time: 11:38
+ */
+@ApplicationScoped
+public class ImportServiceImpl implements ImportService{
+
+	@Inject
+    private Logger log;
+
+    private Map<String,Importer> importerMap;
+
+    public ImportServiceImpl() {
+    }
+
+    @Inject
+	public void initImporters(@Any Instance<Importer> importers) {
+        if(importerMap == null) {
+            importerMap = new HashMap<String,Importer>();
+            for( Importer i : importers ) {
+                for( String s : i.getAcceptTypes() ) {
+                    importerMap.put(s,i);
+                    log.info("registered importer for type {}: {}",s,i.getName());
+                }
+            }
+        }
+	}
+
+	@Override
+	public Set<String> getAcceptTypes() {
+		return importerMap.keySet();
+	}
+
+	@Override
+	public int importData(URL url, String format, Resource user, URI context) throws LMFImportException {
+		return getImporterInstance(format).importData(url,format,user,context);
+	}
+
+	@Override
+	public int importData(InputStream is, String format, Resource user, URI context) throws LMFImportException {
+		return getImporterInstance(format).importData(is,format,user,context);
+	}
+
+	@Override
+	public int importData(Reader reader, String format, Resource user, URI context) throws LMFImportException {
+		return getImporterInstance(format).importData(reader,format,user,context);
+	}
+
+	private Importer getImporterInstance(String type) throws LMFImportException {
+		if(!importerMap.containsKey(type)) throw new LMFImportException("no importer defined for type "+type);
+		return importerMap.get(type);
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/importer/rdf/RDFImporterImpl.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/importer/rdf/RDFImporterImpl.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/importer/rdf/RDFImporterImpl.java
new file mode 100644
index 0000000..2b6c2f5
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/importer/rdf/RDFImporterImpl.java
@@ -0,0 +1,297 @@
+/**
+ * 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.services.importer.rdf;
+
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+import org.apache.marmotta.platform.core.api.importer.Importer;
+import org.apache.marmotta.platform.core.api.task.Task;
+import org.apache.marmotta.platform.core.api.task.TaskManagerService;
+import org.apache.marmotta.platform.core.api.triplestore.SesameService;
+import org.apache.marmotta.platform.core.exception.io.LMFImportException;
+import org.openrdf.model.Resource;
+import org.openrdf.model.URI;
+import org.openrdf.repository.RepositoryConnection;
+import org.openrdf.repository.RepositoryException;
+import org.openrdf.rio.RDFFormat;
+import org.openrdf.rio.RDFParseException;
+import org.openrdf.rio.RDFParserRegistry;
+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 java.io.Reader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * An io for importing RDF sources in RDF/XML or other RDF formats. Currently uses
+ * the Sesame parser for parsing the RDF content.
+ * <p>
+ * For each triple found in the imported data, an appropriate KiWi triple is added. For each
+ * resource in the imported data, the io creates a ContentItem using rdfs:label or
+ * dc:title as title and rdfs:comment or dc:description as content.
+ *
+ * @author Sebastian Schaffert
+ *
+ */
+@ApplicationScoped
+public class RDFImporterImpl implements Importer {
+
+    @Inject
+    private Logger log;
+
+
+    @Inject
+    private ConfigurationService configurationService;
+
+    @Inject
+    private TaskManagerService   taskManagerService;
+
+    @Inject
+    private SesameService sesameService;
+
+    private static long taskCounter = 0;
+
+    private List<String> acceptTypes;
+
+
+
+
+    /**
+     * Get a collection of all mime types accepted by this io. Used for automatically
+     * selecting the appropriate io in ImportService.
+     *
+     * @return a set of strings representing the mime types accepted by this io
+     */
+    @Override
+    public Set<String> getAcceptTypes() {
+        return new HashSet<String>(acceptTypes);
+    }
+
+    /**
+     * Get a description of this io for presentation to the user.
+     *
+     * @return a string describing this io for the user
+     */
+    @Override
+    public String getDescription() {
+        return "Importer for various RDF formats (RDF/XML, N3, TURTLE); also supports OWL and RDFS files";
+    }
+
+    /**
+     * Get the name of this io. Used for presentation to the user and for internal
+     * identification.
+     *
+     * @return a string uniquely identifying this io
+     */
+    @Override
+    public String getName() {
+        return "RDF";
+    }
+
+    /**
+     * Import data from the input stream provided as argument into the KiWi database.
+     *
+     * @param url the url from which to read the data
+     * @param user the user to use as author of all imported data
+     */
+    @Override
+    //@RaiseEvent("ontologyChanged")
+    public int importData(URL url, String format, Resource user, URI context) throws LMFImportException {
+        try {
+            return importData(url.openStream(), format, user,context, url.toString());
+        } catch (IOException ex) {
+            log.error("I/O error while importing data from URL {}: {}", url, ex.getMessage());
+            return 0;
+        }
+    }
+
+
+    /**
+     * Import data from the input stream provided as argument into the KiWi database.
+     * <p>
+     * Import function for formats supported by Sesame; imports the data first into a separate memory
+     * repository, and then iterates over all statements, adding them to the current knowledge space.
+     * This method also checks for resources that have a rdfs:label, dc:title, or skos:prefLabel and uses
+     * it as the title for newly created ContentItems.
+     *
+     * @param is the input stream from which to read the data
+     * @param user the user to use as author of all imported data
+     */
+    @Override
+    //@RaiseEvent("ontologyChanged")
+    public int importData(InputStream is, String format, Resource user, URI context) throws LMFImportException {
+        String baseUri = configurationService.getBaseUri() + "resource/";
+        return importData(is,format,user,context,baseUri);
+    }
+
+    /**
+     * Import data from the input stream provided as argument into the KiWi database.
+     * <p>
+     * Import function for formats supported by Sesame; imports the data first into a separate memory
+     * repository, and then iterates over all statements, adding them to the current knowledge space.
+     * This method also checks for resources that have a rdfs:label, dc:title, or skos:prefLabel and uses
+     * it as the title for newly created ContentItems.
+     *
+     * @param is the input stream from which to read the data
+     * @param user the user to use as author of all imported data
+     */
+    //@RaiseEvent("ontologyChanged")
+    private int importData(InputStream is, String format, Resource user, URI context, String baseUri) throws LMFImportException {
+
+        // TODO: need to figure out format automatically!
+        RDFFormat f = getFormat(format);
+
+        final String taskName = String.format("RDF Importer Task %d (%s)", ++taskCounter, f.getName());
+        Task task = taskManagerService.createSubTask(taskName,"Importer");
+        task.updateMessage("importing data into LMF repository");
+        task.updateDetailMessage("format", f.getDefaultMIMEType());
+        task.updateDetailMessage("baseUri", baseUri);
+
+        int count = 0;
+
+        try {
+            if (is != null) {
+
+                long timer = System.currentTimeMillis();
+
+                RepositoryConnection c_import = sesameService.getConnection();
+                try {
+                    c_import.add(is, baseUri, f, context );
+                } catch (RepositoryException ex) {
+                    log.error("error while importing Sesame data:", ex);
+                    c_import.rollback();
+                    throw ex;
+                } catch (RDFParseException ex) {
+                    log.error("parse error while importing Sesame data:", ex);
+                    c_import.rollback();
+                    throw ex;
+                } catch (IOException ex) {
+                    log.error("I/O error while importing Sesame data:", ex);
+                    c_import.rollback();
+                    throw ex;
+                } finally {
+                    c_import.commit();
+                    c_import.close();
+                }
+                log.debug("imported data into LMF repository ({} ms)", System.currentTimeMillis() - timer);
+
+            } else {
+                log.error("could not load ontology; InputStream was null");
+            }
+        } catch (Exception ex) {
+            log.error("error while importing Sesame data:", ex);
+            throw new LMFImportException(ex);
+        } finally {
+            taskManagerService.endTask(task);
+        }
+        return count;
+
+    }
+
+    /**
+     * Import data from the reader provided as argument into the KiWi database.
+     * <p>
+     * Import function for formats supported by Sesame; imports the data first into a separate memory
+     * repository, and then iterates over all statements, adding them to the current knowledge space.
+     * This method also checks for resources that have a rdfs:label, dc:title, or skos:prefLabel and uses
+     * it as the title for newly created ContentItems.
+     *
+     * @param reader the reader from which to read the data
+     * @param user the user to use as author of all imported data
+     */
+    @Override
+    public int importData(Reader reader, String format, Resource user, URI context) throws LMFImportException {
+
+        // TODO: need to figure out format automatically!
+        RDFFormat f = getFormat(format);
+
+        String baseUri = configurationService.getBaseUri() + "resource/";
+        final String taskName = String.format("RDF Importer Task %d (%s)", ++taskCounter, f.getName());
+        Task task = taskManagerService.createSubTask(taskName, "Importer");
+        task.updateMessage("importing data into LMF repository");
+        task.updateDetailMessage("format", f.getDefaultMIMEType());
+        task.updateDetailMessage("baseURI", baseUri);
+
+        int count = 0;
+
+        try {
+            if (reader != null) {
+
+                long timer = System.currentTimeMillis();
+
+                RepositoryConnection c_import = sesameService.getConnection();
+                try {
+                    c_import.add(reader, baseUri, f, context);
+                } catch (RepositoryException ex) {
+                    log.error("error while importing Sesame data:", ex);
+                    c_import.rollback();
+                    throw ex;
+                } catch (RDFParseException ex) {
+                    log.error("parse error while importing Sesame data:", ex);
+                    c_import.rollback();
+                    throw ex;
+                } catch (IOException ex) {
+                    log.error("I/O error while importing Sesame data:", ex);
+                    c_import.rollback();
+                    throw ex;
+                } finally {
+                    c_import.commit();
+                    c_import.close();
+                }
+
+                log.info("imported data into LMF repository ({} ms)", System.currentTimeMillis() - timer);
+
+            } else {
+                log.error("could not load ontology; InputStream was null");
+            }
+        } catch (Exception ex) {
+            log.error("error while importing Sesame data:", ex);
+            throw new LMFImportException(ex);
+        } finally {
+            taskManagerService.endTask(task);
+        }
+        return count;
+    }
+
+
+    @PostConstruct
+    public void initialise() {
+        log.info("registering RDF importer ...");
+
+
+        RDFParserRegistry parserRegistry = RDFParserRegistry.getInstance();
+
+        acceptTypes = new ArrayList<String>();
+        for(RDFFormat format : parserRegistry.getKeys()) {
+            acceptTypes.addAll(format.getMIMETypes());
+        }
+        log.info(" - available parsers: {}", Arrays.toString(acceptTypes.toArray()));
+
+    }
+
+
+    private static RDFFormat getFormat(String format) {
+        return RDFParserRegistry.getInstance().getFileFormatForMIMEType(format);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/io/LMFIOServiceImpl.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/io/LMFIOServiceImpl.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/io/LMFIOServiceImpl.java
new file mode 100644
index 0000000..6a74ffd
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/io/LMFIOServiceImpl.java
@@ -0,0 +1,109 @@
+/**
+ * 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.services.io;
+
+import org.apache.marmotta.platform.core.api.io.LMFIOService;
+import org.openrdf.rio.RDFFormat;
+import org.openrdf.rio.RDFParserRegistry;
+import org.openrdf.rio.RDFWriterRegistry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * User: Thomas Kurz
+ * Date: 18.02.11
+ * Time: 10:41
+ */
+@ApplicationScoped
+public class LMFIOServiceImpl implements LMFIOService {
+
+    private Logger log = LoggerFactory.getLogger(this.getClass());
+
+    private RDFParserRegistry parserRegistry;
+    private RDFWriterRegistry writerRegistry;
+
+    private List<String> acceptTypes;
+    private List<String> producedTypes;
+
+    @PostConstruct
+    public void initialise() {
+        log.info("initialising LMF I/O service ...");
+
+        parserRegistry = RDFParserRegistry.getInstance();
+
+        acceptTypes = new ArrayList<String>();
+        for(RDFFormat format : parserRegistry.getKeys()) {
+            acceptTypes.addAll(format.getMIMETypes());
+        }
+        log.info(" - available parsers: {}", Arrays.toString(acceptTypes.toArray()));
+
+        writerRegistry = RDFWriterRegistry.getInstance();
+
+        producedTypes = new ArrayList<String>();
+        for(RDFFormat format : writerRegistry.getKeys()) {
+            producedTypes.addAll(format.getMIMETypes());
+        }
+        log.info(" - available writers: {}", Arrays.toString(producedTypes.toArray()));
+
+
+
+    }
+
+
+	/**
+	 * returns a list of all mimetypes which can be parsed by implemented parsers
+	 * @return
+	 */
+	@Override
+	public List<String> getAcceptTypes() {
+		return acceptTypes;
+	}
+
+	/**
+	 * returns a list of all mimetypes which can be produced by implemented serializers
+	 * @return
+	 */
+	@Override
+	public List<String> getProducedTypes() {
+		return producedTypes;
+	}
+
+	/**
+	 * returns a serializer for a given mimetype; null if no serializer defined
+	 * @param mimetype
+	 * @return
+	 */
+	@Override
+	public RDFFormat getSerializer(String mimetype) {
+		return writerRegistry.getFileFormatForMIMEType(mimetype);
+	}
+
+	/**
+	 * returns a parser for a given mimetype; null if no parser defined
+	 * @param mimetype
+	 * @return
+	 */
+	@Override
+	public RDFFormat getParser(String mimetype) {
+		return parserRegistry.getFileFormatForMIMEType(mimetype);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/logging/LMFLogbackPropertyDefiner.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/logging/LMFLogbackPropertyDefiner.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/logging/LMFLogbackPropertyDefiner.java
new file mode 100644
index 0000000..8d5764f
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/logging/LMFLogbackPropertyDefiner.java
@@ -0,0 +1,109 @@
+/**
+ * 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.services.logging;
+
+import ch.qos.logback.core.Context;
+import ch.qos.logback.core.spi.PropertyDefiner;
+import ch.qos.logback.core.status.Status;
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+import org.apache.marmotta.platform.core.util.KiWiContext;
+
+/**
+ * An abstract class with access to the LMF Configuration Service to get access to LMF runtime configuration.
+ * <p/>
+ * Author: Sebastian Schaffert
+ */
+public class LMFLogbackPropertyDefiner implements PropertyDefiner {
+
+
+    protected ConfigurationService configurationService;
+
+    protected Context context;
+    
+    protected String key;
+
+    public LMFLogbackPropertyDefiner() {
+        configurationService = KiWiContext.getInstance(ConfigurationService.class);
+    }
+
+    /**
+     * Get the property value, defined by this property definer
+     *
+     * @return defined property value
+     */
+    @Override
+    public String getPropertyValue() {
+        if(key != null) {
+            return configurationService.getStringConfiguration(key);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void setContext(Context context) {
+        this.context = context;
+    }
+
+    @Override
+    public Context getContext() {
+        return context;
+    }
+
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    @Override
+    public void addStatus(Status status) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void addInfo(String msg) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void addInfo(String msg, Throwable ex) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void addWarn(String msg) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void addWarn(String msg, Throwable ex) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void addError(String msg) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+
+    @Override
+    public void addError(String msg, Throwable ex) {
+        //To change body of implemented methods use File | Settings | File Templates.
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/logging/LoggingServiceImpl.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/logging/LoggingServiceImpl.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/logging/LoggingServiceImpl.java
new file mode 100644
index 0000000..a6c80e1
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/logging/LoggingServiceImpl.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.services.logging;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.util.StatusPrinter;
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+import org.apache.marmotta.platform.core.api.logging.LoggingService;
+import org.apache.marmotta.platform.core.events.ConfigurationChangedEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.InjectionPoint;
+import javax.inject.Inject;
+import java.io.File;
+
+/**
+ * LoggingServiceImpl
+ *
+ * @author Sebastian Schaffert
+ *
+ */
+@ApplicationScoped
+public class LoggingServiceImpl implements LoggingService {
+    private static Logger log = LoggerFactory.getLogger(LoggingService.class);
+
+    @Inject
+    private ConfigurationService configurationService;
+
+    @PostConstruct
+    public void initialize() {
+        log.info("KiWi Logging Service starting up ...");
+
+        for(String key : configurationService.listConfigurationKeys("logging.")) {
+            String loggerName   = key.substring("logging.".length());
+            setConfiguredLevel(loggerName);
+        }
+    }
+
+    public void configurationChangedEvent(@Observes ConfigurationChangedEvent event) {
+        for (String key : event.getKeys())
+            if (key.startsWith("logging.")) {
+                String loggerName   = key.substring("logging.".length());
+                setConfiguredLevel(loggerName);
+            } else if(key.equalsIgnoreCase("debug.enabled")) {
+                // set root logger level
+                reloadLoggingConfiguration();
+            }
+    }
+
+
+    private void reloadLoggingConfiguration() {
+        log.info("reloading logging configuration");
+        File log_configuration = new File(configurationService.getWorkDir() + File.separator + "logback.xml");
+        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+        try {
+            JoranConfigurator configurator = new JoranConfigurator();
+            configurator.setContext(context);
+            context.reset();
+            configurator.doConfigure(log_configuration);
+        } catch (JoranException e) {
+            StatusPrinter.printInCaseOfErrorsOrWarnings(context);
+        }
+
+        // set root logger level
+        ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
+        if(configurationService.getBooleanConfiguration("debug.enabled",false)) {
+            rootLogger.setLevel(Level.DEBUG);
+        } else {
+            rootLogger.setLevel(Level.INFO);
+        }
+    }
+
+
+
+    private void setConfiguredLevel(String loggerName) {
+        String logLevelName = configurationService.getStringConfiguration("logging."+loggerName,"INFO");
+        Level logLevel = null;
+
+        if("DEBUG".equals(logLevelName.toUpperCase())) {
+            logLevel = Level.DEBUG;
+        } else if("INFO".equals(logLevelName.toUpperCase())) {
+            logLevel = Level.INFO;
+        } else if("WARN".equals(logLevelName.toUpperCase())) {
+            logLevel = Level.WARN;
+        } else if("ERROR".equals(logLevelName.toUpperCase())) {
+            logLevel = Level.ERROR;
+        } else {
+            log.error("unsupported log level for pattern {}: {}",loggerName,logLevelName);
+        }
+
+        if(logLevel != null) {
+            ch.qos.logback.classic.Logger logger =
+                    (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(loggerName);
+
+            logger.setLevel(logLevel);
+            log.info("configured logger {} to level {}",loggerName,logLevelName.toUpperCase());
+        }
+    }
+
+    /**
+     * Provide a logger to the injection point given as argument.
+     * 
+     * @see org.apache.marmotta.platform.core.api.logging.LoggingService#createLogger(javax.enterprise.inject.spi.InjectionPoint)
+     */
+    @Override
+    @Produces
+    public Logger createLogger(InjectionPoint injectionPoint) {
+        return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-marmotta/blob/4d3eebdd/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/modules/LMFResourceServiceImpl.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/modules/LMFResourceServiceImpl.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/modules/LMFResourceServiceImpl.java
new file mode 100644
index 0000000..f3e00a1
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/modules/LMFResourceServiceImpl.java
@@ -0,0 +1,252 @@
+/**
+ * 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.services.modules;
+
+import com.google.common.io.ByteStreams;
+import eu.medsea.mimeutil.MimeType;
+import eu.medsea.mimeutil.MimeUtil2;
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+import org.apache.marmotta.platform.core.api.modules.LMFResourceService;
+import org.apache.marmotta.platform.core.api.modules.ModuleService;
+import org.apache.marmotta.platform.core.api.modules.ResourceEntry;
+import org.apache.marmotta.platform.core.events.SystemStartupEvent;
+import org.apache.marmotta.platform.core.model.module.ModuleConfiguration;
+import org.apache.marmotta.platform.core.qualifiers.cache.LMFCache;
+import net.sf.ehcache.Ehcache;
+import net.sf.ehcache.Element;
+import org.slf4j.Logger;
+
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.inject.Inject;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A service for resolving and accessing resources contained in the LMF modules. The resource service takes care
+ * of retrieving, caching and refreshing resources from the appropriate locations.
+ * <p/>
+ * Note that the resource service is not to be confused with the RDF resources maintained by the server. It is
+ * purely meant to retrieve static non-Java resources contained in the modules and web application.
+ *
+ * User: sschaffe
+ */
+@ApplicationScoped
+public class LMFResourceServiceImpl implements LMFResourceService {
+
+    @Inject
+    private Logger log;
+
+    @Inject
+    private ConfigurationService configurationService;
+
+
+    @Inject
+    private ModuleService moduleService;
+
+
+    @Inject @LMFCache("resource-cache")
+    private Ehcache resourceCache;
+
+    /**
+     * Used for detecting the mime type of resources contained in KiWi modules
+     */
+    private MimeUtil2 mimeUtil;
+
+
+    private Map<String,String> resourceMap;
+
+
+    @PostConstruct
+    public void initialise() {
+        // find all kiwi-module.properties and check whether they contain a baseurl property to map module web
+        // resources to a certain path prefix; if yes, store the prefix and jar URL in the map for lookup and
+        // resource resolving by the filter
+        this.resourceMap = new HashMap<String, String>();
+
+
+        for(String module : moduleService.listModules()) {
+            ModuleConfiguration config = moduleService.getModuleConfiguration(module);
+
+            if(config.getConfiguration().containsKey("baseurl")) {
+                String path = config.getConfiguration().getString("baseurl");
+                resourceMap.put(path.startsWith("/")?path:"/"+path,moduleService.getModuleJar(module).toString());
+            }
+        }
+
+
+
+        // detect the mime type of resources
+        this.mimeUtil = new MimeUtil2();
+        this.mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.ExtensionMimeDetector");
+    }
+
+
+    /**
+     * Makes sure the service is initialised on system startup
+     *
+     * @param event
+     */
+    public void systemStartup(@Observes SystemStartupEvent event) {
+
+    }
+
+
+    /**
+     * Return the resource identified by the relative URL passed as argument. The passed argument is relative
+     * to the web application root of this web application.
+     *
+     * @param relativeURL a URL relative to the web application root of this web application
+     * @return the resource identified by the relative URL, or null if it does not exist
+     */
+    @Override
+    public ResourceEntry getResource(String relativeURL) {
+        ResourceEntry data = null;
+
+        // check the request path prefix whether it matches with one of the prefixes in the prefix mapping; if yes,
+        // lookup the resource as follows:
+        // 1. look in the cache using the path of the request url as cache key
+        // 2. if not found: look in the jar file by mapping the resource to the correct jar file and retrieving it
+        //    from the "web" folder contained therein
+        // 3. if not found: proceed with the chain by calling chain.doFilter()
+        // TODO: (FIXME) data_cache.get(path) might return null some times even though path is in cache
+        if(isCached(relativeURL)) {
+            data = getFromCache(relativeURL);
+        } else {
+
+            try {
+                URL jarUrl = resolveResource(relativeURL);
+
+                if(jarUrl != null) {
+                    try {
+                        byte[] bytes = ByteStreams.toByteArray(jarUrl.openStream());
+
+                        data = new ResourceEntry(jarUrl,bytes,bytes.length,getMimeType(jarUrl));
+
+                        log.debug("retrieved resource {} (mime type {}, length {} bytes)", jarUrl.toString(), data.getContentType(),data.getLength());
+                    } catch (NullPointerException e) {
+                        // This happens if a directory is accessed in the jar-file.
+                        data = null;
+                    }
+                    putInCache(relativeURL,data);
+                } else {
+                    putInCache(relativeURL,null);
+
+                    log.debug("resource {} not found in any module",relativeURL);
+                }
+
+            } catch(IOException ex) {
+                log.debug("error while trying to retrieve resource {}: {}",relativeURL,ex.getMessage());
+            }
+        }
+        return data;
+
+    }
+
+    /**
+     * Return the file system URL of the resource identified by the relative HTTP URL passed as argument.
+     * he passed argument is relative to the web application root of this web application.
+     *
+     * @param relativeURL a URL relative to the web application root of this web application
+     * @return the file system URL of the resource, regardless whether it actually exists or not
+     */
+    @Override
+    public URL resolveResource(String relativeURL) {
+
+        // we take the first match from the resource map ...
+        for(String key : resourceMap.keySet()) {
+            if(relativeURL.startsWith(key)) {
+
+                // the name of the resource inside the jar file
+                String entryPath    = relativeURL.substring(key.length());
+                // map "/" to /index.html
+                if(entryPath.endsWith("/") || entryPath.equals("")) {
+                    entryPath += "index.html";
+                }
+
+                // the base URL of the jar file in the file system
+                String jarUrlBase   = resourceMap.get(key);
+
+                // the JAR URL of the resource inside the jar file
+                String jarUrlEntry;
+
+                if(jarUrlBase.endsWith(".jar")) {
+                    jarUrlEntry = "jar:" + jarUrlBase + "!/web" + ( entryPath.startsWith("/") ? entryPath : "/" + entryPath);
+                } else {
+                    jarUrlEntry = jarUrlBase + (jarUrlBase.endsWith("/")?"":"/") + "web" + ( entryPath.startsWith("/") ? entryPath : "/" + entryPath);
+                }
+
+
+                try {
+                    return new URL(jarUrlEntry);
+
+                } catch(IOException ex) {
+                    log.debug("error while trying to retrieve resource {}: {}",jarUrlEntry,ex.getMessage());
+
+                }
+            }
+        }
+        return null;
+    }
+
+
+
+
+
+    private boolean isCacheEnabled() {
+        return configurationService.getBooleanConfiguration("resources.servercache.enabled", false);
+    }
+
+
+    private boolean isCached(String key) {
+        return isCacheEnabled() && resourceCache.isKeyInCache(key) && resourceCache.get(key) != null;
+    }
+
+    private ResourceEntry getFromCache(String key) {
+        if (isCacheEnabled())
+            return (ResourceEntry) resourceCache.get(key).getObjectValue();
+        else
+            return null;
+    }
+
+    // Store in the cache
+    private void putInCache(String key, ResourceEntry data) {
+        if(isCacheEnabled()) {
+            resourceCache.put(new Element(key,data));
+        }
+    }
+
+
+    private String getMimeType(URL resource) {
+
+        @SuppressWarnings("unchecked")
+        Collection<MimeType> types = mimeUtil.getMimeTypes(resource);
+
+        if(types.size() > 0) {
+            MimeType t = types.iterator().next();
+            return t.toString();
+        } else
+            return null;
+
+    }
+
+
+}