commit 00b3d224dbb7f80ce3b5fdc30029e1f6fc76319c
Author: Tomáš Šimandl <tomas.simandl@gmail.com>
Date:   Fri Jan 25 17:53:28 2019 +0100

    Prepared application for loading json convertors from jar modules

diff --git a/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/IModule.java b/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/IModule.java
new file mode 100644
index 0000000..4094a49
--- /dev/null
+++ b/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/IModule.java
@@ -0,0 +1,6 @@
+package cz.zcu.kiv.offscreen.modularization;
+
+public interface IModule {
+
+    String getRawJson(String file);
+}
diff --git a/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleLoader.java b/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleLoader.java
new file mode 100644
index 0000000..3d8681d
--- /dev/null
+++ b/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleLoader.java
@@ -0,0 +1,99 @@
+package cz.zcu.kiv.offscreen.modularization;
+
+import com.sun.jndi.toolkit.url.Uri;
+import javafx.util.Pair;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilenameFilter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+
+public class ModuleLoader {
+
+    private static final Logger logger = LogManager.getLogger();
+    private static final FilenameFilter MODULE_FILTER = (file, name) -> name.contains(".jar");
+    private static final String MODULE_CLASS_IDENTIFIER = "Module-Class";
+    private static final String MODULE_NAME_IDENTIFIER = "Module-Name";
+
+    private final String modulesPath;
+
+    public ModuleLoader(String modulesPath) {
+        this.modulesPath = modulesPath;
+        logger.info("Initializing new ModuleLoader with folder path: " + modulesPath);
+    }
+
+    public Set<Pair<String, IModule>> loadModules() {
+        logger.info("Loading all modules from file.");
+        final File[] modules = loadJarFiles();
+        return Arrays.stream(modules)
+                .map(this::loadModule)
+                .filter(Optional::isPresent)
+                .map(Optional::get)
+                .collect(Collectors.toSet());
+    }
+
+    public Optional<File> getModulesFolder(){
+
+        final URL fileURL = getClass().getClassLoader().getResource(modulesPath);
+        if (fileURL == null){
+            logger.warn("Can not open modules directory.");
+            return Optional.empty();
+        }
+
+        try {
+            final File modulesFolder = new File(fileURL.toURI());
+
+            if (!modulesFolder.exists() || !modulesFolder.isDirectory()) {
+                logger.warn("Modules folder is not exists or it is not a directory");
+                return Optional.empty();
+            }
+            return Optional.of(modulesFolder);
+
+        } catch (URISyntaxException e) {
+            logger.warn("Can not open modules directory", e);
+            return Optional.empty();
+        }
+    }
+
+    private File[] loadJarFiles() {
+
+        Optional<File> folderOptional = getModulesFolder();
+        if(folderOptional.isPresent()){
+            logger.info(folderOptional.get().length() + " modules were read from file");
+            return folderOptional.get().listFiles(MODULE_FILTER);
+        }
+        return new File[0];
+    }
+
+    private Optional<Pair<String, IModule>> loadModule(File moduleFile) {
+        try {
+            final JarInputStream jis = new JarInputStream(new FileInputStream(moduleFile));
+            final Manifest mf = jis.getManifest();
+            final Attributes attributes = mf.getMainAttributes();
+            final String moduleClassName = attributes.getValue(MODULE_CLASS_IDENTIFIER);
+            final String moduleVisibleName = attributes.getValue(MODULE_NAME_IDENTIFIER);
+
+            final ClassLoader loader = URLClassLoader.newInstance(new URL[]{moduleFile.toURI().toURL()});
+            final Class clazz = Class.forName(moduleClassName, true, loader);
+            final IModule module = (IModule) clazz.asSubclass(IModule.class).newInstance();
+
+            return Optional.of(new Pair<>(moduleVisibleName, module));
+
+        } catch (Exception e) {
+            logger.debug("Invalid module throw exception: ", e);
+            return Optional.empty();
+        }
+    }
+}
diff --git a/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleProvider.java b/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleProvider.java
new file mode 100644
index 0000000..75748ee
--- /dev/null
+++ b/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleProvider.java
@@ -0,0 +1,109 @@
+package cz.zcu.kiv.offscreen.modularization;
+
+import javafx.util.Pair;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.*;
+
+public class ModuleProvider {
+
+    private static final Logger logger = LogManager.getLogger();
+    private static final String MODULES_PATH = "modules";
+    private static ModuleProvider instance = null;
+
+    private ConcurrentMap<String, Pair<String, IModule>> modules = new ConcurrentHashMap<>();
+    private final ModuleLoader loader;
+    private boolean watch = true;
+
+
+    public static ModuleProvider getInstance() {
+        if (instance == null) {
+            instance = new ModuleProvider();
+        }
+        return instance;
+    }
+
+    private ModuleProvider() {
+        this.loader = new ModuleLoader(MODULES_PATH);
+
+        processModules(loader.loadModules());
+
+        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+        Runnable task = this::initModulesWatcher;
+        logger.debug("Scheduling Modules Watcher thread.");
+        // task will be scheduled after 1 minute
+        // When task ends (on failure) after one minute will be planed again
+        executor.scheduleWithFixedDelay(task, 1, 1, TimeUnit.MINUTES);
+    }
+
+    private void initModulesWatcher() {
+        try {
+            logger.debug("Initializing new WatcherService for modules directory");
+
+            Optional<File> folderFileOptional = loader.getModulesFolder();
+            if (!folderFileOptional.isPresent()) {
+                return;
+            }
+
+            Path path = folderFileOptional.get().toPath();
+            WatchService watcher = FileSystems.getDefault().newWatchService();
+            path.register(watcher,
+                    StandardWatchEventKinds.ENTRY_CREATE,
+                    StandardWatchEventKinds.ENTRY_DELETE,
+                    StandardWatchEventKinds.ENTRY_MODIFY);
+
+            while (true) {
+                WatchKey key = watcher.take();
+                logger.debug("Watcher events was detected");
+
+                for (WatchEvent<?> event : key.pollEvents()) {
+                    WatchEvent.Kind<?> kind = event.kind();
+
+                    if (kind == StandardWatchEventKinds.OVERFLOW) {
+                        logger.debug("Overflow watcher event was detected");
+                        continue;
+                    }
+
+                    processModules(loader.loadModules());
+                    break; // watching only one folder and loading all files every loop => Only one iteration is needed.
+                }
+
+                if (!key.reset() || !watch) {
+                    logger.warn("Stopping modules directory watcher");
+                    break;
+                }
+            }
+
+        } catch (IOException | InterruptedException e) {
+            logger.error("Modules directory watcher throw an exception: ", e);
+        }
+    }
+
+    private void processModules(Set<Pair<String, IModule>> unprocessedModules) {
+        Map<String, Pair<String, IModule>> localModules = new HashMap<>();
+
+        logger.info("Processing " + localModules.size() + " modules.");
+
+        for (Pair<String, IModule> pair : unprocessedModules) {
+            localModules.put(String.valueOf(pair.getKey().hashCode()), pair);
+        }
+
+        modules.clear();
+        modules.putAll(localModules);
+        logger.debug("Modules were processed");
+    }
+
+    public Map<String, Pair<String, IModule>> getModules() {
+        return modules;
+    }
+}
diff --git a/sources/src/main/java/cz/zcu/kiv/offscreen/servlets/UploadFiles.java b/sources/src/main/java/cz/zcu/kiv/offscreen/servlets/UploadFiles.java
index aed098c..a7b732a 100644
--- a/sources/src/main/java/cz/zcu/kiv/offscreen/servlets/UploadFiles.java
+++ b/sources/src/main/java/cz/zcu/kiv/offscreen/servlets/UploadFiles.java
@@ -1,6 +1,7 @@
 package cz.zcu.kiv.offscreen.servlets;
 
 import com.google.common.base.Strings;
+import cz.zcu.kiv.offscreen.modularization.ModuleProvider;
 import cz.zcu.kiv.offscreen.storage.FileLoader;
 import cz.zcu.kiv.offscreen.user.DB;
 import cz.zcu.kiv.offscreen.user.Diagram;
@@ -40,6 +41,7 @@ public class UploadFiles extends BaseServlet {
 
         List<Map<String, String>> publicDiagramList = diagram.getDiagramPublicList();
         request.setAttribute("diagramsPublic", publicDiagramList);
+        request.setAttribute("processingModules", ModuleProvider.getInstance().getModules());
 
         // render
         RequestDispatcher rd = getServletContext().getRequestDispatcher("/uploadFiles.jsp");
diff --git a/sources/src/main/java/cz/zcu/kiv/offscreen/servlets/api/GetSessionDiagram.java b/sources/src/main/java/cz/zcu/kiv/offscreen/servlets/api/GetSessionDiagram.java
index 16c90a8..23de025 100644
--- a/sources/src/main/java/cz/zcu/kiv/offscreen/servlets/api/GetSessionDiagram.java
+++ b/sources/src/main/java/cz/zcu/kiv/offscreen/servlets/api/GetSessionDiagram.java
@@ -6,7 +6,10 @@ import cz.zcu.kiv.offscreen.api.Graph;
 import cz.zcu.kiv.offscreen.graph.GraphManager;
 import cz.zcu.kiv.offscreen.graph.loader.GraphJSONDataLoader;
 import cz.zcu.kiv.offscreen.graph.loader.JSONConfigLoader;
+import cz.zcu.kiv.offscreen.modularization.IModule;
+import cz.zcu.kiv.offscreen.modularization.ModuleProvider;
 import cz.zcu.kiv.offscreen.servlets.BaseServlet;
+import javafx.util.Pair;
 import net.sf.json.JSONObject;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -47,14 +50,19 @@ public class GetSessionDiagram extends BaseServlet {
 
             String rawJson;
 
-            switch (jsonType) {
-                case "spade":
-                    logger.debug("Processing Spade json");
-                    rawJson = convertSpadeToRawJson(jsonToDisplay);
-                    break;
-                default:
-                    logger.debug("Processing Raw json");
-                    rawJson = jsonToDisplay;
+            if (jsonType.equals("raw")) {
+                logger.debug("Processing Raw json");
+                rawJson = jsonToDisplay;
+            } else {
+                logger.debug("Processing json with module");
+
+                Pair<String, IModule> module = ModuleProvider.getInstance().getModules().get(jsonType);
+                if (module == null){
+                    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+                    logger.debug("No loader available for type: " + jsonType + ". Response BAD REQUEST");
+                    return;
+                }
+                rawJson = module.getValue().getRawJson(jsonToDisplay);
             }
 
             JsonObject jsonObject = new JsonObject();
diff --git a/sources/src/main/webapp/uploadFiles.jsp b/sources/src/main/webapp/uploadFiles.jsp
index 3ec9b7a..72f1fcd 100644
--- a/sources/src/main/webapp/uploadFiles.jsp
+++ b/sources/src/main/webapp/uploadFiles.jsp
@@ -72,8 +72,10 @@
 
 					<div class="form-field">
 						Select type of data file:<br>
-						<label for="spade"><input type="radio" name="jsonFileFormat" value="spade" id="spade" checked> Spade JSON</label><br>
-						<label for="raw"><input type="radio" name="jsonFileFormat" value="raw" id="raw"> Raw JSON</label><br>
+						<label for="raw"><input type="radio" name="jsonFileFormat" value="raw" id="raw" checked> Raw JSON</label><br>
+						<c:forEach items="${processingModules}" var="module">
+							<label for="${module.key}"><input type="radio" name="jsonFileFormat" value="${module.key}" id="${module.key}">${module.value.key}</label><br>
+						</c:forEach>
 					</div>
 
 					<button id="btnLoad" type="submit">Start visualization</button>
