commit 076216248184ab5d084cb1877f8e05ea08399ebc
Author: Tomáš Šimandl <tomas.simandl@gmail.com>
Date:   Sat Jan 26 09:07:06 2019 +0100

    Updated modularization
    
    + removed unfunctional interface
    + added mechanism to stop watcher on modules folder changes
    + run module method execution with own class loader
    + small refactor

diff --git a/sources/src/main/java/cz/zcu/kiv/offscreen/ApplicationListener.java b/sources/src/main/java/cz/zcu/kiv/offscreen/ApplicationListener.java
new file mode 100644
index 0000000..11851c7
--- /dev/null
+++ b/sources/src/main/java/cz/zcu/kiv/offscreen/ApplicationListener.java
@@ -0,0 +1,18 @@
+package cz.zcu.kiv.offscreen;
+
+import cz.zcu.kiv.offscreen.modularization.ModuleProvider;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+public class ApplicationListener implements ServletContextListener {
+
+    @Override
+    public void contextInitialized(ServletContextEvent servletContextEvent) {
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent servletContextEvent) {
+        ModuleProvider.getInstance().stopWatcher();
+    }
+}
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
index 3d8681d..11e01f7 100644
--- a/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleLoader.java
+++ b/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleLoader.java
@@ -1,6 +1,5 @@
 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;
@@ -8,7 +7,6 @@ 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;
@@ -28,13 +26,17 @@ public class ModuleLoader {
     private static final String MODULE_NAME_IDENTIFIER = "Module-Name";
 
     private final String modulesPath;
+    private final String methodName;
+    private final Class methodParamClass;
 
-    public ModuleLoader(String modulesPath) {
+    public ModuleLoader(String modulesPath, String methodName, Class methodParamClass) {
         this.modulesPath = modulesPath;
+        this.methodName = methodName;
+        this.methodParamClass = methodParamClass;
         logger.info("Initializing new ModuleLoader with folder path: " + modulesPath);
     }
 
-    public Set<Pair<String, IModule>> loadModules() {
+    public Set<Pair<String, Class>> loadModules() {
         logger.info("Loading all modules from file.");
         final File[] modules = loadJarFiles();
         return Arrays.stream(modules)
@@ -44,10 +46,10 @@ public class ModuleLoader {
                 .collect(Collectors.toSet());
     }
 
-    public Optional<File> getModulesFolder(){
+    public Optional<File> getModulesFolder() {
 
         final URL fileURL = getClass().getClassLoader().getResource(modulesPath);
-        if (fileURL == null){
+        if (fileURL == null) {
             logger.warn("Can not open modules directory.");
             return Optional.empty();
         }
@@ -70,14 +72,15 @@ public class ModuleLoader {
     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);
+        if (folderOptional.isPresent()) {
+            File[] files = folderOptional.get().listFiles(MODULE_FILTER);
+            logger.info(files == null ? 0 : files.length + " modules were read from file");
+            return files;
         }
         return new File[0];
     }
 
-    private Optional<Pair<String, IModule>> loadModule(File moduleFile) {
+    private Optional<Pair<String, Class>> loadModule(File moduleFile) {
         try {
             final JarInputStream jis = new JarInputStream(new FileInputStream(moduleFile));
             final Manifest mf = jis.getManifest();
@@ -86,10 +89,11 @@ public class ModuleLoader {
             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();
+            final Class<?> clazz = Class.forName(moduleClassName, true, loader);
+            // checking if method exists, if not throw exception
+            clazz.getMethod(methodName, methodParamClass);
 
-            return Optional.of(new Pair<>(moduleVisibleName, module));
+            return Optional.of(new Pair<>(moduleVisibleName, clazz));
 
         } catch (Exception e) {
             logger.debug("Invalid module throw exception: ", e);
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
index 75748ee..5043564 100644
--- a/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleProvider.java
+++ b/sources/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleProvider.java
@@ -6,8 +6,6 @@ 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;
@@ -17,14 +15,21 @@ import java.util.concurrent.*;
 
 public class ModuleProvider {
 
+    public static final String METHOD_NAME = "getRawJson";
+    public static final Class METHOD_PARAMETER_CLASS = String.class;
+
     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 ConcurrentMap<String, Pair<String, Class>> modules = new ConcurrentHashMap<>();
     private final ModuleLoader loader;
     private boolean watch = true;
 
+    private ScheduledExecutorService executor;
+    private ScheduledFuture scheduledFuture;
+    private WatchService watcher = null;
+
 
     public static ModuleProvider getInstance() {
         if (instance == null) {
@@ -34,16 +39,34 @@ public class ModuleProvider {
     }
 
     private ModuleProvider() {
-        this.loader = new ModuleLoader(MODULES_PATH);
+        this.loader = new ModuleLoader(MODULES_PATH, METHOD_NAME, METHOD_PARAMETER_CLASS);
 
         processModules(loader.loadModules());
 
-        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+        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);
+        scheduledFuture = executor.scheduleWithFixedDelay(task, 1, 1, TimeUnit.MINUTES);
+    }
+
+    public void stopWatcher() {
+        logger.debug("Stopping WatcherProvider");
+        watch = false;
+
+        try {
+            logger.info("Closing WatcherService");
+            if (watcher != null) watcher.close();
+        } catch (IOException e) {
+            logger.debug("Closing watcher throw exception: ", e);
+        }
+
+        logger.info("Canceling scheduler");
+        if (scheduledFuture != null) scheduledFuture.cancel(true);
+
+        logger.info("Shutting down an ScheduledExecutorService");
+        if (executor != null) executor.shutdown();
     }
 
     private void initModulesWatcher() {
@@ -56,7 +79,7 @@ public class ModuleProvider {
             }
 
             Path path = folderFileOptional.get().toPath();
-            WatchService watcher = FileSystems.getDefault().newWatchService();
+            watcher = FileSystems.getDefault().newWatchService();
             path.register(watcher,
                     StandardWatchEventKinds.ENTRY_CREATE,
                     StandardWatchEventKinds.ENTRY_DELETE,
@@ -89,21 +112,22 @@ public class ModuleProvider {
         }
     }
 
-    private void processModules(Set<Pair<String, IModule>> unprocessedModules) {
-        Map<String, Pair<String, IModule>> localModules = new HashMap<>();
+    private void processModules(Set<Pair<String, Class>> unprocessedModules) {
+        long startTime = System.nanoTime();
+        Map<String, Pair<String, Class>> localModules = new HashMap<>();
 
-        logger.info("Processing " + localModules.size() + " modules.");
+        logger.info("Processing " + unprocessedModules.size() + " modules.");
 
-        for (Pair<String, IModule> pair : unprocessedModules) {
+        for (Pair<String, Class> pair : unprocessedModules) {
             localModules.put(String.valueOf(pair.getKey().hashCode()), pair);
         }
 
         modules.clear();
         modules.putAll(localModules);
-        logger.debug("Modules were processed");
+        logger.debug("Modules were loaded and processed in " + (System.nanoTime() - startTime) / 1000000d + " milliseconds");
     }
 
-    public Map<String, Pair<String, IModule>> getModules() {
+    public Map<String, Pair<String, Class>> 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 a7b732a..6d9f8a0 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
@@ -52,21 +52,21 @@ public class UploadFiles extends BaseServlet {
     @Override
     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
         logger.debug("Processing request");
-        Map<String, String> jsonData = new FileLoader().loadFile(request);
+        Map<String, String> data = new FileLoader().loadFile(request);
 
-        if(!jsonData.containsKey("file") || !jsonData.containsKey("jsonFileFormat")) {
+        if(!data.containsKey("file") || !data.containsKey("fileFormat")) {
             request.setAttribute("errorMessage", "<strong>Invalid request!</strong><br/>");
             logger.debug("Invalid request");
             doGet(request, response);
 
-        } else if (Strings.isNullOrEmpty(jsonData.get("file"))) {
+        } else if (Strings.isNullOrEmpty(data.get("file"))) {
             request.setAttribute("errorMessage", "<strong>Unsupported file</strong><br/>");
-            logger.debug("Empty diagram json");
+            logger.debug("Empty diagram string");
             doGet(request, response);
         } else {
-            request.getSession().setAttribute("json_graph", jsonData.get("file"));
-            request.getSession().setAttribute("json_graph_type", jsonData.get("jsonFileFormat"));
-            request.getSession().setAttribute("graph_filename", jsonData.get("filename"));
+            request.getSession().setAttribute("diagram_string", data.get("file"));
+            request.getSession().setAttribute("diagram_type", data.get("fileFormat"));
+            request.getSession().setAttribute("diagram_filename", data.get("filename"));
             response.sendRedirect(getServletContext().getInitParameter("APP_HOME_URL") + "graph");
             logger.debug("send redirect to /graph");
         }
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 23de025..bef1b55 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
@@ -2,21 +2,18 @@ package cz.zcu.kiv.offscreen.servlets.api;
 
 import com.google.common.base.Strings;
 import com.google.gson.JsonObject;
-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.commons.lang.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Optional;
 
 /**
  * This class is used for loading diagrams from session.
@@ -42,27 +39,25 @@ public class GetSessionDiagram extends BaseServlet {
      * Add file which was uploaded and is stored in session to response or set http status code to BAD_REQUEST.
      */
     private void getDiagramFromSession(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        String jsonToDisplay = (String) request.getSession().getAttribute("json_graph");
-        String jsonType = (String) request.getSession().getAttribute("json_graph_type");
-        String filename = (String) request.getSession().getAttribute("graph_filename");
+        String diagramToDisplay = (String) request.getSession().getAttribute("diagram_string");
+        String diagramType = (String) request.getSession().getAttribute("diagram_type");
+        String filename = (String) request.getSession().getAttribute("diagram_filename");
 
-        if (!Strings.isNullOrEmpty(jsonToDisplay) && jsonType != null) {
+        if (!Strings.isNullOrEmpty(diagramToDisplay) && diagramType != null) {
 
             String rawJson;
 
-            if (jsonType.equals("raw")) {
+            if (diagramType.equals("raw")) {
                 logger.debug("Processing Raw json");
-                rawJson = jsonToDisplay;
+                rawJson = diagramToDisplay;
             } else {
-                logger.debug("Processing json with module");
-
-                Pair<String, IModule> module = ModuleProvider.getInstance().getModules().get(jsonType);
-                if (module == null){
+                Optional<String> optional = callModuleConverter(diagramType, diagramToDisplay);
+                if(optional.isPresent()){
+                    rawJson = optional.get();
+                } else {
                     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();
@@ -81,15 +76,43 @@ public class GetSessionDiagram extends BaseServlet {
     }
 
     /**
-     * Convert input spade JSON to frontend backend JSON and return it.
+     * Method find module by type and call method for getting RAW JSON from module. If result is null, empty or
+     * blank, method returns empty Optional.
+     *
+     * @param type type of converter which is key to map of modules
+     * @param stringToConvert string which will be converted
+     * @return Optional of RAW JSON or empty Optional
      */
-    private String convertSpadeToRawJson(String spadeJson) {
-        GraphManager graphManager = new GraphJSONDataLoader(spadeJson).loadData();
-        JSONConfigLoader configLoader = new JSONConfigLoader(graphManager);
+    private Optional<String> callModuleConverter(String type, String stringToConvert){
+        logger.debug("Processing json with module");
+
+        Pair<String, Class> module = ModuleProvider.getInstance().getModules().get(type);
+        if (module == null){
+            logger.debug("No loader available for type: " + type + ". Response BAD REQUEST");
+            return Optional.empty();
+        }
+
+        try {
+            final Class<?> moduleClass = module.getValue();
+            // switching to class loader of module
+            final ClassLoader appClassLoader = Thread.currentThread().getContextClassLoader();
+            Thread.currentThread().setContextClassLoader(moduleClass.getClassLoader());
+
+            final Method moduleMethod = moduleClass.getMethod(ModuleProvider.METHOD_NAME, ModuleProvider.METHOD_PARAMETER_CLASS);
+            String rawJson = String.valueOf(moduleMethod.invoke(moduleClass.newInstance(), stringToConvert));
 
-        Graph graph = graphManager.createGraph(configLoader);
-        JSONObject json = JSONObject.fromObject(graph);
+            // switching back to application class loader
+            Thread.currentThread().setContextClassLoader(appClassLoader);
 
-        return json.toString();
+            if(StringUtils.isBlank(rawJson)){
+                return Optional.empty();
+            } else {
+                return Optional.of(rawJson);
+            }
+
+        } catch (Exception e) {
+            logger.error("Can not call convert method in module. Module name: " + module.getKey(), e);
+            return Optional.empty();
+        }
     }
 }
diff --git a/sources/src/main/webapp/WEB-INF/web.xml b/sources/src/main/webapp/WEB-INF/web.xml
index ae885a3..d3c54be 100644
--- a/sources/src/main/webapp/WEB-INF/web.xml
+++ b/sources/src/main/webapp/WEB-INF/web.xml
@@ -131,6 +131,9 @@
 	<listener>
 		<listener-class>cz.zcu.kiv.offscreen.session.SessionManager</listener-class>
 	</listener>
+	<listener>
+		<listener-class>cz.zcu.kiv.offscreen.ApplicationListener</listener-class>
+	</listener>
 
 	<!-- Tomcat CORS filter; https://tomcat.apache.org/tomcat-8.0-doc/config/filter.html#CORS_Filter -->
 	<filter>
diff --git a/sources/src/main/webapp/uploadFiles.jsp b/sources/src/main/webapp/uploadFiles.jsp
index 72f1fcd..86a61fc 100644
--- a/sources/src/main/webapp/uploadFiles.jsp
+++ b/sources/src/main/webapp/uploadFiles.jsp
@@ -72,9 +72,9 @@
 
 					<div class="form-field">
 						Select type of data file:<br>
-						<label for="raw"><input type="radio" name="jsonFileFormat" value="raw" id="raw" checked> Raw JSON</label><br>
+						<label for="raw"><input type="radio" name="fileFormat" 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>
+							<label for="${module.key}"><input type="radio" name="fileFormat" value="${module.key}" id="${module.key}"> ${module.value.key}</label><br>
 						</c:forEach>
 					</div>
 
