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;
+
+ }
+
+
+}