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/11/28 20:36:51 UTC
[3/6] git commit: configuration webservice for MARMOTTA-390
(configurable logging) functional
configuration webservice for MARMOTTA-390 (configurable logging) functional
Project: http://git-wip-us.apache.org/repos/asf/marmotta/repo
Commit: http://git-wip-us.apache.org/repos/asf/marmotta/commit/e5dd9cf6
Tree: http://git-wip-us.apache.org/repos/asf/marmotta/tree/e5dd9cf6
Diff: http://git-wip-us.apache.org/repos/asf/marmotta/diff/e5dd9cf6
Branch: refs/heads/develop
Commit: e5dd9cf63a7f3fe4526aa7a09677ab1125f65ecc
Parents: 7b9cb5a
Author: Sebastian Schaffert <ss...@apache.org>
Authored: Thu Nov 28 16:24:25 2013 +0100
Committer: Sebastian Schaffert <ss...@apache.org>
Committed: Thu Nov 28 16:24:25 2013 +0100
----------------------------------------------------------------------
.../core/api/config/ConfigurationService.java | 2 +
.../config/ConfigurationServiceImpl.java | 98 +++++-
.../services/logging/LoggingServiceImpl.java | 9 +
.../webservices/logging/LoggingWebService.java | 332 +++++++++++++++++++
.../src/main/resources/kiwi-module.properties | 3 +-
5 files changed, 426 insertions(+), 18 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/marmotta/blob/e5dd9cf6/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/api/config/ConfigurationService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/api/config/ConfigurationService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/api/config/ConfigurationService.java
index 4f8c5f1..19909c4 100644
--- a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/api/config/ConfigurationService.java
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/api/config/ConfigurationService.java
@@ -69,6 +69,8 @@ public interface ConfigurationService {
static final String DIR_IMPORT = "import";
+ static final String LOGGING_PATH = "logging";
+
/**
* Get the base URI of the system.
* The base URI is used by the LMF to create local resource URIs. In this way, all Apache Marmotta resources
http://git-wip-us.apache.org/repos/asf/marmotta/blob/e5dd9cf6/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/config/ConfigurationServiceImpl.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/config/ConfigurationServiceImpl.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/config/ConfigurationServiceImpl.java
index 676f763..752fe74 100644
--- a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/config/ConfigurationServiceImpl.java
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/services/config/ConfigurationServiceImpl.java
@@ -26,6 +26,7 @@ import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.MapConfiguration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.marmotta.platform.core.api.config.ConfigurationService;
import org.apache.marmotta.platform.core.events.ConfigurationChangedEvent;
import org.apache.marmotta.platform.core.events.ConfigurationServiceInitEvent;
@@ -48,15 +49,9 @@ import java.io.IOException;
import java.lang.reflect.Array;
import java.net.URL;
import java.net.UnknownHostException;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
+import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -128,10 +123,28 @@ public class ConfigurationServiceImpl implements ConfigurationService {
*/
private ReadWriteLock lock;
+ /**
+ * Backlog for delayed event collection; only fires a configuration changed event if there has not been a further
+ * update in a specified amount of time (default 250ms);
+ */
+ private Set<String> eventBacklog;
+
+ private long EVENT_DELAY = 250L;
+
+
+ /*
+ * Timer and task for delayed execution of configuration changed events
+ */
+ private Timer eventTimer;
+ private ReentrantLock eventLock;
+
public ConfigurationServiceImpl() {
runtimeFlags = new HashMap<String, Boolean>();
lock = new ReentrantReadWriteLock();
+
+ eventTimer = new Timer("Configuration Event Timer", true);
+ eventLock = new ReentrantLock();
}
/**
@@ -578,7 +591,7 @@ public class ConfigurationServiceImpl implements ConfigurationService {
}
if (!initialising) {
- configurationEvent.fire(new ConfigurationChangedEvent(key));
+ raiseDelayedConfigurationEvent(Collections.singleton(key));
}
}
}
@@ -713,7 +726,7 @@ public class ConfigurationServiceImpl implements ConfigurationService {
}
if (!initialising) {
- configurationEvent.fire(new ConfigurationChangedEvent(key));
+ raiseDelayedConfigurationEvent(Collections.singleton(key));
}
}
}
@@ -764,7 +777,7 @@ public class ConfigurationServiceImpl implements ConfigurationService {
}
if (!initialising) {
- configurationEvent.fire(new ConfigurationChangedEvent(key));
+ raiseDelayedConfigurationEvent(Collections.singleton(key));
}
}
}
@@ -831,7 +844,7 @@ public class ConfigurationServiceImpl implements ConfigurationService {
}
if (!initialising) {
- configurationEvent.fire(new ConfigurationChangedEvent(key));
+ raiseDelayedConfigurationEvent(Collections.singleton(key));
}
}
}
@@ -886,7 +899,7 @@ public class ConfigurationServiceImpl implements ConfigurationService {
if (!initialising) {
- configurationEvent.fire(new ConfigurationChangedEvent(key));
+ raiseDelayedConfigurationEvent(Collections.singleton(key));
}
}
}
@@ -982,7 +995,7 @@ public class ConfigurationServiceImpl implements ConfigurationService {
if (!initialising) {
log.debug("firing configuration changed event for key {}", key);
- configurationEvent.fire(new ConfigurationChangedEvent(key));
+ raiseDelayedConfigurationEvent(Collections.singleton(key));
}
}
}
@@ -1030,7 +1043,7 @@ public class ConfigurationServiceImpl implements ConfigurationService {
if (!initialising) {
log.debug("firing configuration changed event for key {}", key);
- configurationEvent.fire(new ConfigurationChangedEvent(key));
+ raiseDelayedConfigurationEvent(Collections.singleton(key));
}
}
}
@@ -1087,7 +1100,7 @@ public class ConfigurationServiceImpl implements ConfigurationService {
if (!initialising) {
- configurationEvent.fire(new ConfigurationChangedEvent(values.keySet()));
+ raiseDelayedConfigurationEvent((values.keySet()));
}
}
@@ -1107,7 +1120,7 @@ public class ConfigurationServiceImpl implements ConfigurationService {
if (!initialising) {
- configurationEvent.fire(new ConfigurationChangedEvent(key));
+ raiseDelayedConfigurationEvent(Collections.singleton(key));
}
}
}
@@ -1470,4 +1483,55 @@ public class ConfigurationServiceImpl implements ConfigurationService {
}
return value;
}
+
+
+ /**
+ * Start a delayed execution of raising an event
+ * @param keys
+ */
+ private void raiseDelayedConfigurationEvent(Set<String> keys) {
+ eventLock.lock();
+ try {
+ if(eventBacklog == null) {
+ eventBacklog = new HashSet<>();
+ }
+ eventBacklog.addAll(keys);
+
+ if(eventTimer != null) {
+ eventTimer.cancel();
+ }
+ eventTimer = new Timer("Configuration Event Timer", true);
+ eventTimer.schedule(new EventTimerTask(), EVENT_DELAY);
+ } finally {
+ eventLock.unlock();
+ }
+
+ if(log.isDebugEnabled()) {
+ log.debug("updated configuration keys [{}]", StringUtils.join(keys,", "));
+ }
+
+ }
+
+ /**
+ * Delayed event firing task
+ */
+ private class EventTimerTask extends TimerTask {
+
+ @Override
+ public void run() {
+ eventLock.lock();
+ try {
+ Set<String> keys = eventBacklog;
+ eventBacklog = null;
+
+ if(log.isDebugEnabled()) {
+ log.debug("firing delayed ({}ms) configuration changed event with keys [{}]",EVENT_DELAY, StringUtils.join(keys,", "));
+ }
+
+ configurationEvent.fire(new ConfigurationChangedEvent(keys));
+ } finally {
+ eventLock.unlock();
+ }
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/marmotta/blob/e5dd9cf6/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
index d77897b..0265660 100644
--- 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
@@ -38,6 +38,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.marmotta.platform.core.api.config.ConfigurationService;
import org.apache.marmotta.platform.core.api.logging.LoggingModule;
import org.apache.marmotta.platform.core.api.logging.LoggingService;
+import org.apache.marmotta.platform.core.events.ConfigurationChangedEvent;
import org.apache.marmotta.platform.core.events.LoggingStartEvent;
import org.apache.marmotta.platform.core.exception.MarmottaConfigurationException;
import org.apache.marmotta.platform.core.model.logging.ConsoleOutput;
@@ -150,6 +151,14 @@ public class LoggingServiceImpl implements LoggingService {
configureLoggers();
}
+ public void configurationEventHandler(@Observes ConfigurationChangedEvent event) {
+ if(event.containsChangedKeyWithPrefix("logging.")) {
+ log.warn("LOGGING: Reloading logging configuration");
+
+ configureLoggers();
+ }
+ }
+
/**
* Configure all loggers according to their configuration and set some reasonable fallback for the root level
http://git-wip-us.apache.org/repos/asf/marmotta/blob/e5dd9cf6/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/logging/LoggingWebService.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/logging/LoggingWebService.java b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/logging/LoggingWebService.java
new file mode 100644
index 0000000..c44ab09
--- /dev/null
+++ b/platform/marmotta-core/src/main/java/org/apache/marmotta/platform/core/webservices/logging/LoggingWebService.java
@@ -0,0 +1,332 @@
+package org.apache.marmotta.platform.core.webservices.logging;
+
+import ch.qos.logback.classic.Level;
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.marmotta.platform.core.api.config.ConfigurationService;
+import org.apache.marmotta.platform.core.api.logging.LoggingModule;
+import org.apache.marmotta.platform.core.api.logging.LoggingService;
+import org.apache.marmotta.platform.core.model.logging.ConsoleOutput;
+import org.apache.marmotta.platform.core.model.logging.LogFileOutput;
+import org.apache.marmotta.platform.core.model.logging.LoggingOutput;
+import org.apache.marmotta.platform.core.model.logging.SyslogOutput;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Listing and modifying logging configuration for appenders and logging modules. JSON format for each appender is:
+ * <pre>
+ * {
+ * "type": ("console" | "logfile" | "syslog" ),
+ * "id": unique identifier
+ * "name": human-readable name
+ * "pattern": pattern for logging output
+ * "level": ("ERROR" | "WARN" | "INFO" | "DEBUG" | "TRACE" | "OFF" )
+ * }
+ * </pre>
+ *
+ * Depending on the type of appender, the following additional fields need to be provided:
+ * <ul>
+ * <li><strong>logfile</strong>
+ * <pre>
+ * "file": name of the logfile
+ * "keep": how many days to keep old logfiles
+ * </pre>
+ * </li>
+ * <li><strong>syslog</strong>
+ * <pre>
+ * "host": name of server where the syslog daemon is running
+ * "facility": facility to use for logging
+ * </pre>
+ * </li>
+ * </ul>
+ *
+ * JSON format for logging modules is:
+ * <pre>
+ * {
+ * "id": unique identifier
+ * "name": human-readable name
+ * "level": ("ERROR" | "WARN" | "INFO" | "DEBUG" | "TRACE" | "OFF" )
+ * "appenders": list of appender ids to send logging to
+ * "packages": list of package ids managed by module
+ * }
+ * </pre>
+ *
+ * @author Sebastian Schaffert (sschaffert@apache.org)
+ */
+@ApplicationScoped
+@Path("/" + ConfigurationService.LOGGING_PATH)
+public class LoggingWebService {
+
+ @Inject
+ private ConfigurationService configurationService;
+
+ @Inject
+ private LoggingService loggingService;
+
+ /**
+ * Get a JSON list of all log appenders currently configured in the system using the JSON format described in the
+ * header of the class
+ *
+ * @return JSON list
+ */
+ @GET
+ @Path("/appenders")
+ @Produces("application/json")
+ public Response listAppenders() {
+ return Response.ok(Lists.transform(loggingService.listOutputConfigurations(), new Function<LoggingOutput, Object>() {
+ @Override
+ public Object apply(LoggingOutput input) {
+ return appenderToJSON(input);
+ }
+ })).build();
+ }
+
+ /**
+ * Get the configuration of the log appender with the given ID using the JSON format described in the header of
+ * the class
+ *
+ * @HTTP 200 appender configuration returned successfully
+ * @HTTP 404 appender not found
+ *
+ * @param id unique identifier of appender
+ * @return JSON formatted representation of configuration
+ */
+ @GET
+ @Path("/appenders/{id}")
+ @Produces("application/json")
+ public Response getAppender(@PathParam("id") String id) {
+ LoggingOutput appender = loggingService.getOutputConfiguration(id);
+ if(appender != null) {
+ return Response.ok(appenderToJSON(appender)).build();
+ } else {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ }
+
+
+ /**
+ * Create or update the appender with the given id, using the JSON description sent in the body of the request
+ *
+ * @HTTP 200 appender updated successfully
+ * @HTTP 400 appender configuration invalid (e.g. not proper JSON)
+ * @HTTP 404 appender not found
+ *
+ * @param id unique identifier of appender
+ * @return HTTP status 200 in case of success
+ */
+ @POST
+ @Path("/appenders/{id}")
+ @Consumes("application/json")
+ public Response updateAppender(@PathParam("id") String id, @Context HttpServletRequest request) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ //log.info(getContentData(request.getReader()));
+ Map<String,Object> values = mapper.readValue(request.getInputStream(), new TypeReference<HashMap<String,Object>>(){});
+
+ String type = (String) values.get("type");
+ String name = (String) values.get("name");
+ String level = (String) values.get("level");
+ String pattern = (String) values.get("pattern");
+
+ LoggingOutput appender = loggingService.getOutputConfiguration(id);
+ if(appender == null) {
+ // type information required
+ Preconditions.checkArgument(type != null, "appender type was not given");
+ Preconditions.checkArgument(name != null, "appender name was not given");
+
+ if("logfile".equals(type)) {
+ String file = (String) values.get("file");
+
+ Preconditions.checkArgument(file != null, "logfile name was not given");
+
+ appender = loggingService.createLogFileOutput(id,name,file);
+ } else if("syslog".equals(type)) {
+ String host = (String) values.get("host");
+
+ Preconditions.checkArgument(host != null, "syslog host was not given");
+
+ appender = loggingService.createSyslogOutput(id,name);
+ } else {
+ return Response.status(Response.Status.NOT_IMPLEMENTED).entity("new appenders of type "+type+" not supported").build();
+ }
+ }
+
+ appender.setName(name);
+
+ if(level != null) {
+ appender.setMaxLevel(Level.toLevel(level));
+ }
+ if(pattern != null) {
+ appender.setPattern(pattern);
+ }
+ if(values.get("file") != null && appender instanceof LogFileOutput) {
+ ((LogFileOutput) appender).setFileName((String) values.get("file"));
+ }
+ if(values.get("keep") != null && appender instanceof LogFileOutput) {
+ ((LogFileOutput) appender).setKeepDays(Integer.parseInt(values.get("keep").toString()));
+ }
+ if(values.get("host") != null && appender instanceof SyslogOutput) {
+ ((SyslogOutput) appender).setHostName((String) values.get("host"));
+ }
+ if(values.get("facility") != null && appender instanceof SyslogOutput) {
+ ((SyslogOutput) appender).setFacility((String) values.get("facility"));
+ }
+
+ return Response.ok().build();
+ } catch (IllegalArgumentException ex) {
+ // thrown by Preconditions.checkArgument
+ return Response.status(Response.Status.BAD_REQUEST).entity(ex.getMessage()).build();
+ } catch (JsonMappingException | JsonParseException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity("invalid JSON format: "+e.getMessage()).build();
+ } catch (IOException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity("could not read stream: "+e.getMessage()).build();
+ }
+ }
+
+
+ /**
+ * List all modules currently available in the system as a JSON list using the JSON format described in the
+ * header of this class
+ *
+ * @HTTP 200 in case the modules are listed properly
+ *
+ * @return JSON list of module descriptions
+ */
+ @GET
+ @Path("/modules")
+ @Produces("application/json")
+ public Response listModules() {
+ return Response.ok(Lists.transform(loggingService.listModules(), new Function<LoggingModule, Object>() {
+ @Override
+ public Object apply(LoggingModule input) {
+ return moduleToJSON(input);
+ }
+ })).build();
+ }
+
+ /**
+ * Get the configuration of the logging module with the given id, using the JSON format described in the
+ * header of this class.
+ *
+ * @HTTP 200 module found
+ * @HTTP 404 module not found
+ *
+ * @param id unique logging module identifier
+ * @return HTTP status 200 in case of success
+ */
+ @GET
+ @Path("/modules/{id}")
+ @Produces("application/json")
+ public Response getModule(@PathParam("id") String id) {
+ for(LoggingModule module : loggingService.listModules()) {
+ if(StringUtils.equals(module.getId(), id)) {
+ return Response.ok(moduleToJSON(module)).build();
+ }
+ }
+
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+
+
+
+ /**
+ * Update the module with the given id, using the JSON description sent in the body of the request. Only the fields
+ * "level" and "appenders" can be updated for modules.
+ *
+ * @HTTP 200 module updated successfully
+ * @HTTP 400 module configuration invalid (e.g. not proper JSON)
+ * @HTTP 404 module not found
+ *
+ * @param id unique logging module identifier
+ * @return HTTP status 200 in case of success
+ */
+ @POST
+ @Path("/modules/{id}")
+ @Consumes("application/json")
+ public Response updateModule(@PathParam("id") String id, @Context HttpServletRequest request) {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ //log.info(getContentData(request.getReader()));
+ Map<String,Object> values = mapper.readValue(request.getInputStream(), new TypeReference<HashMap<String,Object>>(){});
+
+ String level = (String) values.get("level");
+ List appenders = (List) values.get("appenders");
+ for(LoggingModule module : loggingService.listModules()) {
+ if(StringUtils.equals(module.getId(), id)) {
+ if(level != null) {
+ module.setCurrentLevel(Level.toLevel(level));
+ }
+ if(appenders != null) {
+ module.setLoggingOutputIds(appenders);
+ }
+
+ return Response.ok("module updated").build();
+ }
+ }
+
+ return Response.status(Response.Status.NOT_FOUND).build();
+ } catch (JsonMappingException | JsonParseException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity("invalid JSON format: "+e.getMessage()).build();
+ } catch (IOException e) {
+ return Response.status(Response.Status.BAD_REQUEST).entity("could not read stream: "+e.getMessage()).build();
+ }
+ }
+
+
+ private static Map<String,Object> appenderToJSON(LoggingOutput out) {
+ Map<String,Object> result = new HashMap<>();
+
+ if(out instanceof SyslogOutput) {
+ result.put("type", "syslog");
+ result.put("host", ((SyslogOutput) out).getHostName());
+ result.put("facility", ((SyslogOutput) out).getFacility());
+ } else if(out instanceof ConsoleOutput) {
+ result.put("type", "console");
+ } else if(out instanceof LogFileOutput) {
+ result.put("type", "logfile");
+ result.put("file", ((LogFileOutput) out).getFileName());
+ result.put("keep", ((LogFileOutput) out).getKeepDays());
+ }
+
+ result.put("level", out.getMaxLevel().toString());
+ result.put("pattern", out.getPattern());
+ result.put("name", out.getName());
+ result.put("id", out.getId());
+
+ return result;
+ }
+
+
+ private static Map<String,Object> moduleToJSON(LoggingModule module) {
+ Map<String,Object> result = new HashMap<>();
+
+ result.put("id", module.getId());
+ result.put("name", module.getName());
+ result.put("level", module.getCurrentLevel().toString());
+ result.put("appenders", module.getLoggingOutputIds());
+ result.put("packages", module.getPackages());
+
+ return result;
+ }
+}
http://git-wip-us.apache.org/repos/asf/marmotta/blob/e5dd9cf6/platform/marmotta-core/src/main/resources/kiwi-module.properties
----------------------------------------------------------------------
diff --git a/platform/marmotta-core/src/main/resources/kiwi-module.properties b/platform/marmotta-core/src/main/resources/kiwi-module.properties
index aeecd4d..a71bb0b 100644
--- a/platform/marmotta-core/src/main/resources/kiwi-module.properties
+++ b/platform/marmotta-core/src/main/resources/kiwi-module.properties
@@ -70,5 +70,6 @@ webservices=org.apache.marmotta.platform.core.webservices.config.ConfigurationWe
org.apache.marmotta.platform.core.webservices.resource.InspectionWebService,\
org.apache.marmotta.platform.core.webservices.triplestore.LdpWebService,\
org.apache.marmotta.platform.core.webservices.triplestore.ContextWebService,\
- org.apache.marmotta.platform.core.webservices.prefix.PrefixWebService
+ org.apache.marmotta.platform.core.webservices.prefix.PrefixWebService,\
+ org.apache.marmotta.platform.core.webservices.logging.LoggingWebService
\ No newline at end of file