Generic HTTP REST Client/Server AuditLogging framework 23/1323/6
authorjsimomaa <jani.simomaa@gmail.com>
Fri, 15 Dec 2017 12:21:43 +0000 (14:21 +0200)
committerJani Simomaa <jani.simomaa@semantum.fi>
Wed, 27 Dec 2017 05:52:06 +0000 (07:52 +0200)
refs #7684

Change-Id: Ia861758f0e23a4bc55edeffd946030fff4e52121

16 files changed:
bundles/org.simantics.auditlogging/.classpath [new file with mode: 0644]
bundles/org.simantics.auditlogging/.project [new file with mode: 0644]
bundles/org.simantics.auditlogging/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
bundles/org.simantics.auditlogging/META-INF/MANIFEST.MF [new file with mode: 0644]
bundles/org.simantics.auditlogging/build.properties [new file with mode: 0644]
bundles/org.simantics.auditlogging/scl/Simantics/AuditLogging/Client.scl [new file with mode: 0644]
bundles/org.simantics.auditlogging/scl/Simantics/AuditLogging/Server.scl [new file with mode: 0644]
bundles/org.simantics.auditlogging/src/org/simantics/audit/Activator.java [new file with mode: 0644]
bundles/org.simantics.auditlogging/src/org/simantics/audit/AuditLogging.java [new file with mode: 0644]
bundles/org.simantics.auditlogging/src/org/simantics/audit/AuditLoggingException.java [new file with mode: 0644]
bundles/org.simantics.auditlogging/src/org/simantics/audit/client/AuditLoggingAPIClient.java [new file with mode: 0644]
bundles/org.simantics.auditlogging/src/org/simantics/audit/client/AuditLoggingClient.java [new file with mode: 0644]
bundles/org.simantics.auditlogging/src/org/simantics/audit/server/AuditLoggingAPI.java [new file with mode: 0644]
bundles/org.simantics.auditlogging/src/org/simantics/audit/server/AuditLoggingServer.java [new file with mode: 0644]
bundles/pom.xml
features/org.simantics.sdk.feature/feature.xml

diff --git a/bundles/org.simantics.auditlogging/.classpath b/bundles/org.simantics.auditlogging/.classpath
new file mode 100644 (file)
index 0000000..eca7bdb
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.simantics.auditlogging/.project b/bundles/org.simantics.auditlogging/.project
new file mode 100644 (file)
index 0000000..058132d
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>org.simantics.auditlogging</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.ManifestBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.pde.SchemaBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.eclipse.pde.PluginNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/bundles/org.simantics.auditlogging/.settings/org.eclipse.jdt.core.prefs b/bundles/org.simantics.auditlogging/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..0c68a61
--- /dev/null
@@ -0,0 +1,7 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/bundles/org.simantics.auditlogging/META-INF/MANIFEST.MF b/bundles/org.simantics.auditlogging/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..61adfd1
--- /dev/null
@@ -0,0 +1,22 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Simantics Audit Logging
+Bundle-SymbolicName: org.simantics.auditlogging
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.simantics.audit.Activator
+Require-Bundle: org.eclipse.core.runtime,
+ com.fasterxml.jackson.core.jackson-databind,
+ com.fasterxml.jackson.core.jackson-core,
+ org.slf4j.api,
+ javax.servlet-api,
+ javax.ws.rs-api,
+ org.eclipse.jetty.server,
+ org.eclipse.jetty.servlet,
+ org.eclipse.jetty.util,
+ org.glassfish.jersey.core.jersey-server,
+ org.glassfish.jersey.media.jersey-media-json-jackson,
+ org.glassfish.jersey.containers.jersey-container-servlet-core,
+ org.glassfish.jersey.core.jersey-client,
+ org.glassfish.jersey.core.jersey-common
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-ActivationPolicy: lazy
diff --git a/bundles/org.simantics.auditlogging/build.properties b/bundles/org.simantics.auditlogging/build.properties
new file mode 100644 (file)
index 0000000..a4fd10d
--- /dev/null
@@ -0,0 +1,5 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               scl/
diff --git a/bundles/org.simantics.auditlogging/scl/Simantics/AuditLogging/Client.scl b/bundles/org.simantics.auditlogging/scl/Simantics/AuditLogging/Client.scl
new file mode 100644 (file)
index 0000000..f60474a
--- /dev/null
@@ -0,0 +1,15 @@
+import "Map" as Map
+
+importJava "org.simantics.audit.client.AuditLoggingClient" where
+    @JavaName sendLog
+    sendLogM :: Map.T String a -> <Proc> ()
+    sendLog :: [a] -> <Proc> ()
+
+    @JavaName sendError
+    sendErrorM :: Map.T String a -> <Proc> ()
+    sendError :: [a] -> <Proc> ()
+    
+    @JavaName sendTrace
+    sendTraceM :: Map.T String a -> <Proc> ()
+    sendTrace :: [a] -> <Proc> ()
+    
\ No newline at end of file
diff --git a/bundles/org.simantics.auditlogging/scl/Simantics/AuditLogging/Server.scl b/bundles/org.simantics.auditlogging/scl/Simantics/AuditLogging/Server.scl
new file mode 100644 (file)
index 0000000..396f5f5
--- /dev/null
@@ -0,0 +1,11 @@
+import "Map" as Map
+
+importJava "org.simantics.audit.AuditLogging" where
+    register :: String -> <Proc> String
+    log :: String -> Map.T String a -> <Proc, Exception> ()
+    error :: String -> Map.T String a -> <Proc, Exception> ()
+    trace :: String -> Map.T String a -> <Proc, Exception> ()
+    getLogEvents :: String -> String -> String -> String -> <Proc, Exception> [String]
+
+importJava "org.simantics.audit.server.AuditLoggingServer" where
+    start :: String -> Integer -> <Proc> ()
\ No newline at end of file
diff --git a/bundles/org.simantics.auditlogging/src/org/simantics/audit/Activator.java b/bundles/org.simantics.auditlogging/src/org/simantics/audit/Activator.java
new file mode 100644 (file)
index 0000000..7451ffd
--- /dev/null
@@ -0,0 +1,42 @@
+package org.simantics.audit;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Platform;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator {
+
+       private static BundleContext context;
+       private static Path logLocation;
+
+       static BundleContext getContext() {
+               return context;
+       }
+
+       /*
+        * (non-Javadoc)
+        * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
+        */
+       public void start(BundleContext bundleContext) throws Exception {
+               Activator.context = bundleContext;
+               IPath ipath = Platform.getStateLocation(getContext().getBundle());
+               logLocation = Paths.get(ipath.toOSString());
+       }
+
+       /*
+        * (non-Javadoc)
+        * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
+        */
+       public void stop(BundleContext bundleContext) throws Exception {
+               Activator.context = null;
+       }
+       
+    public static Path getLogLocation() {
+        return logLocation;
+    }
+
+}
diff --git a/bundles/org.simantics.auditlogging/src/org/simantics/audit/AuditLogging.java b/bundles/org.simantics.auditlogging/src/org/simantics/audit/AuditLogging.java
new file mode 100644 (file)
index 0000000..cbbf142
--- /dev/null
@@ -0,0 +1,131 @@
+package org.simantics.audit;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileSystemException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class AuditLogging {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AuditLogging.class);
+
+    private static ObjectMapper mapper = new ObjectMapper();
+
+    public enum Level {
+        INFO,
+        ERROR,
+        TRACE
+    }
+
+    public static String register(String id) throws AuditLoggingException {
+        try {
+            String entryRoot = id + "_" + UUID.randomUUID().toString();
+            Files.createDirectories(getEntryRoot(entryRoot));
+            return entryRoot;
+        } catch (Exception e) {
+            throw new AuditLoggingException("Could not register service with id " + id, e);
+        }
+    }
+    
+    public static List<String> getLogEvents(String uuid, String level, String startDate, String endDate) throws AuditLoggingException {
+        Path entryRoot = getEntryRoot(uuid);
+        try {
+            LocalDate localStartDate = LocalDate.parse(startDate);
+            LocalDate localEndDate = LocalDate.parse(endDate).plusDays(1);
+            List<String> allLines = new ArrayList<>();
+            while (localStartDate.isBefore(localEndDate)) {
+                String fileName = resolveLogFileName(uuid, Level.valueOf(level.toUpperCase()), localStartDate);
+                try {
+                    List<String> lines = Files.readAllLines(entryRoot.resolve(fileName));
+                    allLines.addAll(lines);
+                } catch (FileSystemException e) {
+                    // presumably file not found but lets not throw this cause forward, log here
+                    LOGGER.error("Could not read file {}", fileName, e);
+                } finally {
+                    localStartDate = localStartDate.plusDays(1);
+                }
+            }
+            return allLines;
+        } catch (Exception e) {
+            throw new AuditLoggingException(e);
+        }
+    }
+    
+
+    public static Path getEntryRoot(String uuid) {
+        return Activator.getLogLocation().resolve(uuid);
+    }
+
+    public static Path getLogFile(String uuid, Level level) {
+        Path root = getEntryRoot(uuid);
+        String fileName = resolveLogFileName(uuid, level, LocalDate.now());
+        return root.resolve(fileName);
+    }
+    
+    private static String resolveLogFileName(String uuid, Level level, LocalDate date) {
+        return date.toString() + "_" + uuid + "." + level.toString().toLowerCase();
+    }
+
+    public static void log(String uuid, Map<String, Object> json) throws AuditLoggingException {
+        write(uuid, Level.INFO, json);
+    }
+
+    public static void error(String uuid, Map<String, Object> json) throws AuditLoggingException {
+        write(uuid, Level.ERROR, json);
+    }
+
+    public static void trace(String uuid, Map<String, Object> json) throws AuditLoggingException {
+        write(uuid, Level.TRACE, json);
+    }
+
+    private static void write(String uuid, Level level, Map<String, Object> json) throws AuditLoggingException {
+        Map<String, Object> wrappedAuditEvent = wrapAndAddAuditMetadata(uuid, level, json);
+        try {
+            String jsonLine = mapper.writeValueAsString(wrappedAuditEvent);
+            Path logFile = getLogFile(uuid, level);
+            if (!Files.exists(logFile))
+                Files.createFile(logFile);
+            String lineWithNewline = jsonLine + "\n";
+            Files.write(logFile, lineWithNewline.getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, StandardOpenOption.APPEND);
+        } catch (JsonProcessingException e) {
+            throw new AuditLoggingException("Could not serialize input", e);
+        } catch (IOException e) {
+            throw new AuditLoggingException("Could not write line to log", e);
+        }
+    }
+
+    private static final String timestamp = "timestamp";
+    
+    private static Map<String, Object> wrapAndAddAuditMetadata(String uuid, Level level, Map<String, Object> original) {
+        Map<String, Object> wrapped = new HashMap<>();
+        long newValue = System.currentTimeMillis();
+        Object possibleExisting = wrapped.put(timestamp, newValue);
+        if (possibleExisting != null) {
+            LOGGER.warn("Replacing existing value {} for key {} - new value is {}", possibleExisting, timestamp, newValue);
+        }
+        possibleExisting = wrapped.put("uuid", uuid);
+        if (possibleExisting != null) {
+            LOGGER.warn("Replacing existing value {} for key {} - new value is {}", possibleExisting, "uuid", uuid);
+        }
+        possibleExisting = wrapped.put("level", level.toString());
+        if (possibleExisting != null) {
+            LOGGER.warn("Replacing existing value {} for key {} - new value is {}", possibleExisting, "level", level.toString());
+        }
+        wrapped.put("original", original);
+        return wrapped;
+    }
+}
diff --git a/bundles/org.simantics.auditlogging/src/org/simantics/audit/AuditLoggingException.java b/bundles/org.simantics.auditlogging/src/org/simantics/audit/AuditLoggingException.java
new file mode 100644 (file)
index 0000000..a9ab1be
--- /dev/null
@@ -0,0 +1,23 @@
+package org.simantics.audit;
+
+public class AuditLoggingException extends Exception {
+
+    private static final long serialVersionUID = -5798538535211386651L;
+
+    public AuditLoggingException() {
+        super();
+    }
+
+    public AuditLoggingException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public AuditLoggingException(String message) {
+        super(message);
+    }
+
+    public AuditLoggingException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/bundles/org.simantics.auditlogging/src/org/simantics/audit/client/AuditLoggingAPIClient.java b/bundles/org.simantics.auditlogging/src/org/simantics/audit/client/AuditLoggingAPIClient.java
new file mode 100644 (file)
index 0000000..a7fc602
--- /dev/null
@@ -0,0 +1,116 @@
+package org.simantics.audit.client;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+import java.util.Map;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.simantics.audit.Activator;
+import org.simantics.audit.AuditLoggingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AuditLoggingAPIClient {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AuditLoggingAPIClient.class);
+    
+    private Client httpClient;
+    private WebTarget base;
+    private String uuid;
+
+    public AuditLoggingAPIClient(String id, String serverAddress) throws AuditLoggingException {
+        ClientConfig configuration = new ClientConfig();
+        configuration.register(JacksonFeature.class);
+        httpClient = ClientBuilder.newClient(configuration);
+        if (!serverAddress.startsWith("http://")) {
+            serverAddress = "http://" + serverAddress;
+        }
+        base = httpClient.target(serverAddress);
+        
+        // see if registered already
+        uuid = possibleUUIDFromFile();
+        if (uuid == null) {
+            // register
+            register(id);
+        }
+    }
+
+    private void register(String id) throws AuditLoggingException {
+        try {
+            Response response = base.path("register").request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json(Collections.singletonMap("id", id)));
+            Map<String, String> payload = response.readEntity(Map.class);
+            String possibleUUID = payload.get("uuid");
+            if (possibleUUID != null && !possibleUUID.isEmpty()) {
+                persistUUID(possibleUUID);
+            } else {
+                LOGGER.warn("Invalid response received from {} for register with response payload {}", base.getUri(), payload);
+            }
+        } catch (Exception e) {
+            throw new AuditLoggingException(e);
+        }
+    }
+    
+    private static Path auditLoggingFile() {
+        return Activator.getLogLocation().resolve(".auditlogging");
+    }
+    
+    private static String possibleUUIDFromFile() {
+        Path auditLoggingFile = auditLoggingFile();
+        if (Files.exists(auditLoggingFile)) {
+            try {
+                String possibleUUID = new String(Files.readAllBytes(auditLoggingFile));
+                if (possibleUUID != null && !possibleUUID.isEmpty()) {
+                    return possibleUUID;
+                } else {
+                    LOGGER.warn(".auditlogging file exists but is somehow corrupted");
+                }
+            } catch (IOException e) {
+                LOGGER.error("Could not read .auditlogging file and related information", e);
+            }
+        }
+        return null;
+    }
+    
+    private void persistUUID(String possibleUUID) throws IOException {
+        Path auditLoggingFile = auditLoggingFile();
+        Files.write(auditLoggingFile, possibleUUID.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+        uuid = possibleUUID;
+    }
+
+    public void log(Map<String, Object> message) throws AuditLoggingException {
+        try {
+            Response response = base.path(uuid).path("log").request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json(message));
+        } catch (Exception e) {
+            throw new AuditLoggingException(e);
+        }
+    }
+    
+    public void error(Map<String, Object> message) throws AuditLoggingException {
+        try {
+            Response response = base.path(uuid).path("error").request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json(message));
+        } catch (Exception e) {
+            throw new AuditLoggingException(e);
+        }
+    }
+
+    public void trace(Map<String, Object> message) throws AuditLoggingException {
+        try {
+            Response response = base.path(uuid).path("trace").request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json(message));
+        } catch (Exception e) {
+            throw new AuditLoggingException(e);
+        }
+    }
+
+}
diff --git a/bundles/org.simantics.auditlogging/src/org/simantics/audit/client/AuditLoggingClient.java b/bundles/org.simantics.auditlogging/src/org/simantics/audit/client/AuditLoggingClient.java
new file mode 100644 (file)
index 0000000..e31a6ab
--- /dev/null
@@ -0,0 +1,115 @@
+package org.simantics.audit.client;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.simantics.audit.AuditLogging;
+import org.simantics.audit.AuditLogging.Level;
+import org.simantics.audit.AuditLoggingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AuditLoggingClient {
+
+    private static final String AUDIT_SERVER_ADDRESS = "org.simantics.audit.serverAddress";
+    
+    private static final Logger LOGGER = LoggerFactory.getLogger(AuditLoggingClient.class);
+    
+    private static AuditLoggingClient INSTANCE;
+
+    private AuditLoggingAPIClient apiClient;
+
+    private AuditLoggingClient() throws AuditLoggingException {
+        // Read config from sysargs
+        System.out.println("asd");
+        String serverAddress = System.getProperty(AUDIT_SERVER_ADDRESS);
+        if (serverAddress != null && !serverAddress.isEmpty()) {
+            apiClient = new AuditLoggingAPIClient("testlog", serverAddress);
+             
+        } else {
+            LOGGER.warn("No {} system property defined so client not configured", AUDIT_SERVER_ADDRESS);
+        }
+    }
+
+    private static AuditLoggingClient instance() throws AuditLoggingException {
+        if (INSTANCE == null) {
+            synchronized (AuditLoggingClient.class) {
+                if (INSTANCE == null) {
+                    INSTANCE = new AuditLoggingClient();
+                }
+            }
+        }
+        return INSTANCE;
+    }
+
+    public static void sendLog(List<Object> keyValues) throws AuditLoggingException {
+        commit(Level.INFO, toMap(keyValues.toArray()));
+    }
+
+    private static Map<String, Object> toMap(Object... keyValues) {
+        if ((keyValues.length % 2) != 0)
+            throw new IllegalArgumentException("Invalid amount of arguments! " + Arrays.toString(keyValues));
+        Map<String, Object> results = new HashMap<>(keyValues.length / 2);
+        for (int i = 0; i < keyValues.length; i += 2) {
+            Object key = keyValues[i];
+            Object value = keyValues[i + 1];
+            if (!(key instanceof String))
+                throw new IllegalArgumentException("Key with index " + i + " is not String");
+            results.put((String) key, value);
+        }
+        return results;
+    }
+    
+    public static void sendLog(Map<String, Object> event) throws AuditLoggingException {
+        commit(Level.INFO, event);
+    }
+
+    public static void sendError(Map<String, Object> event) throws AuditLoggingException {
+        commit(Level.ERROR, event);
+    }
+
+    public static void sendError(List<Object> keyValues) throws AuditLoggingException {
+        commit(Level.ERROR, toMap(keyValues.toArray()));
+    }
+
+    public static void sendTrace(Map<String, Object> event) throws AuditLoggingException {
+        commit(Level.TRACE, event);
+    }
+
+    public static void sendTrace(List<Object> keyValues) throws AuditLoggingException {
+        commit(Level.TRACE, toMap(keyValues.toArray()));
+    }
+
+    private static void commit(Level level, Map<String, Object> message) throws AuditLoggingException {
+        try {
+            AuditLoggingAPIClient client = instance().apiClient;
+            if (client == null) {
+                // No can do - at least log to file
+                LOGGER.warn("Audit logging server not configured - printing event to log");
+                LOGGER.info(message.toString());
+            } else {
+                switch (level) {
+                case INFO:
+                    client.log(message);
+                    break;
+                case ERROR:
+                    client.error(message);
+                    break;
+                case TRACE:
+                    client.trace(message);
+                    break;
+                default:
+                    break;
+                }
+            }
+        } catch (AuditLoggingException e) {
+            // Just for debugging purposes
+            LOGGER.error("Could not send audit event {} with level {}", message, level, e);
+            // log this locally to a file just in case
+            AuditLogging.log("local", message);
+            throw e;
+        }
+    }
+}
diff --git a/bundles/org.simantics.auditlogging/src/org/simantics/audit/server/AuditLoggingAPI.java b/bundles/org.simantics.auditlogging/src/org/simantics/audit/server/AuditLoggingAPI.java
new file mode 100644 (file)
index 0000000..980ea4c
--- /dev/null
@@ -0,0 +1,62 @@
+package org.simantics.audit.server;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.core.Response;
+
+import org.simantics.audit.AuditLogging;
+import org.simantics.audit.AuditLoggingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Path("audit")
+public class AuditLoggingAPI {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AuditLoggingAPI.class);
+    
+    private static Map<String, Object> buildJSONResponse(Object... keyValues) {
+        if ((keyValues.length % 2) != 0)
+            throw new IllegalArgumentException("Invalid amount of arguments! " + Arrays.toString(keyValues));
+        Map<String, Object> results = new HashMap<>(keyValues.length / 2);
+        for (int i = 0; i < keyValues.length; i += 2) {
+            Object key = keyValues[i];
+            Object value = keyValues[i + 1];
+            if (!(key instanceof String))
+                throw new IllegalArgumentException("Key with index " + i + " is not String");
+            results.put((String) key, value);
+        }
+        return results;
+    }
+
+    @Path("register")
+    @POST
+    public Response register(Map<String, String> payload) {
+        String id = payload.get("id");
+        
+        try {
+            String uuid = AuditLogging.register(id);
+            return Response.ok(buildJSONResponse("uuid", uuid)).build();
+        } catch (AuditLoggingException e) {
+            LOGGER.error("Could not register audit with id {}", id, e);
+            return Response.serverError().entity(buildJSONResponse("message", e.getMessage())).build();
+        }
+    }
+
+    @Path("{uuid}/log")
+    @POST
+    public Response log(@PathParam("uuid") String uuid, Map<String, Object> payload) {
+        
+        try {
+            AuditLogging.log(uuid, payload);
+            return Response.ok().build();
+        } catch (AuditLoggingException e) {
+            LOGGER.error("Could not log audit with id {}", uuid, e);
+            return Response.serverError().entity(buildJSONResponse("message", e.getMessage())).build();
+        }
+    }
+}
diff --git a/bundles/org.simantics.auditlogging/src/org/simantics/audit/server/AuditLoggingServer.java b/bundles/org.simantics.auditlogging/src/org/simantics/audit/server/AuditLoggingServer.java
new file mode 100644 (file)
index 0000000..5bb305d
--- /dev/null
@@ -0,0 +1,81 @@
+package org.simantics.audit.server;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.glassfish.jersey.jackson.JacksonFeature;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.servlet.ServletContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AuditLoggingServer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(AuditLoggingServer.class);
+    
+    private static AuditLoggingServer INSTANCE = null;
+    private static Server server;
+    private static ServiceServerThread serverThread;
+
+    private AuditLoggingServer(String token, int preferablePort) {
+        ResourceConfig config = new ResourceConfig();
+        // JSON serialization/deserialization
+        config.register(JacksonFeature.class);
+        // Actual API
+        config.register(AuditLoggingAPI.class);
+        // Authorization
+//        config.register(new AuthorizationFilter(token));
+        
+        ServletHolder holder = new ServletHolder(new ServletContainer(config));
+        
+        server = new Server();
+        ServerConnector connector = new ServerConnector(server);
+        connector.setPort(preferablePort);
+        
+        server.setConnectors(new Connector[] { connector });
+        
+        ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
+        context.addServlet(holder, "/*");
+    }
+    
+    private static class ServiceServerThread extends Thread {
+
+        @Override
+        public void run() {
+            try {
+                server.start();
+                server.join();
+            } catch (Exception e) {
+                LOGGER.error("Could not start server ", e);
+            }
+        }
+    }
+
+    private static synchronized AuditLoggingServer getInstance(String token, int port) {
+        try {
+            if (INSTANCE == null) {
+                INSTANCE = new AuditLoggingServer(token, port);
+            }
+        } catch (Exception e) {
+            LOGGER.error("Could not initialize SCL REST server", e);
+        }
+        return INSTANCE;
+    }
+
+    public static synchronized void start(String token, int port) throws Exception {
+        // Ensure that an instance is created
+        getInstance(token, port);
+        if (serverThread == null && server != null) {
+            serverThread = new ServiceServerThread();
+            serverThread.start();
+        }
+    }
+    
+    public static synchronized void stop() throws Exception {
+        if (server != null)
+            server.stop();
+        serverThread = null;
+    }
+}
index 6631fd69efb62686c54749abf575a75889f57789..5f42fcdad27f165b8c399e773aa233ed07fd1b18 100644 (file)
@@ -58,6 +58,7 @@
                <module>org.simantics.annotation.ontology</module>
                <module>org.simantics.annotation.ui</module>
                <module>org.simantics.application</module>
+               <module>org.simantics.auditlogging</module>
                <module>org.simantics.backup</module>
                <module>org.simantics.backup.db</module>
                <module>org.simantics.backup.ontology</module>
index 3c952a4d87c44cf528d95a6b9e08f4fa3b05bd0c..e6bc9771d67b6df4cf2ee8875390d5a19dfcfeb3 100644 (file)
          version="0.0.0"
          unpack="false"/>
 
+   <plugin
+         id="org.simantics.auditlogging"
+         download-size="0"
+         install-size="0"
+         version="0.0.0"
+         unpack="false"/>
+
 </feature>