Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 22d0b32a

Přidáno uživatelem Pavel Fidranský před asi 6 roky(ů)

simplified modularization by using Java's native SPI

Zobrazit rozdíly:

sources/imiger-core/pom.xml
58 58
    </build>
59 59

  
60 60
    <dependencies>
61
        <dependency>
62
            <groupId>cz.zcu.kiv</groupId>
63
            <artifactId>imiger-module</artifactId>
64
            <version>${project.version}</version>
65
        </dependency>
61 66
        <dependency>
62 67
            <groupId>cz.zcu.kiv</groupId>
63 68
            <artifactId>imiger-spade-converter</artifactId>
sources/imiger-core/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleLoader.java
1
package cz.zcu.kiv.offscreen.modularization;
2

  
3
import javafx.util.Pair;
4
import org.apache.commons.io.FileUtils;
5
import org.apache.commons.io.FilenameUtils;
6
import org.apache.logging.log4j.LogManager;
7
import org.apache.logging.log4j.Logger;
8

  
9
import java.io.FileInputStream;
10
import java.io.IOException;
11
import java.net.URL;
12
import java.net.URLClassLoader;
13
import java.util.Arrays;
14
import java.util.Optional;
15
import java.util.Set;
16
import java.util.jar.Attributes;
17
import java.util.jar.JarInputStream;
18
import java.util.jar.Manifest;
19
import java.util.stream.Collectors;
20

  
21
/**
22
 * @author Tomáš Šimandl
23
 */
24
class ModuleLoader {
25

  
26
    private static final Logger logger = LogManager.getLogger();
27
    /** Valid file extension of Java module. */
28
    private static final String VALID_MODULE_EXTENSION = "jar";
29
    /** Identification of section in manifest containing other information about the module. */
30
    private static final String MODULE_SECTION_IDENTIFIER = "cz.zcu.kiv.imiger.plugin";
31
    /** Identification of class name in modules manifest. */
32
    private static final String MODULE_CLASS_IDENTIFIER = "Module-Class";
33
    /** Identification of modules visible name in modules manifest. */
34
    private static final String MODULE_NAME_IDENTIFIER = "Module-Name";
35

  
36
    /** Name of method which must contains every module. */
37
    private final String methodName;
38
    /** Class type of parameter to method which must contains every module. */
39
    private final Class methodParamClass;
40

  
41

  
42
    /**
43
     * Only story  input parameters
44
     * @param methodName Name of method which must contains every module.
45
     * @param methodParamClass Class type of parameter to method which must contains every module.
46
     */
47
    ModuleLoader(String methodName, Class methodParamClass) {
48
        this.methodName = methodName;
49
        this.methodParamClass = methodParamClass;
50
        logger.info("Initializing new ModuleLoader");
51
    }
52

  
53
    /**
54
     * Method starts process of loading all modules from modules folder (defined in constructor).
55
     * @return set of loaded modules in Pair where key is: 'Visible name' and value: 'Class to be called'.
56
     */
57
    Set<Pair<String, Class>> loadModules() {
58
        logger.info("Loading all modules from file.");
59
        final URL[] resources = ((URLClassLoader) getClass().getClassLoader()).getURLs();
60
        return Arrays.stream(resources)
61
                .filter(this::isJarFile) // remove resources which are not jar files
62
                .map(this::loadModule) // call method loadModule for every module
63
                .filter(Optional::isPresent) // remove modules which were not loaded
64
                .map(Optional::get) // extract modules from Optional class
65
                .collect(Collectors.toSet()); // return set of modules
66
    }
67

  
68
    /**
69
     * Checks if resource is a jar file by its extension.
70
     *
71
     * @param resourceUrl URL of the resource in local filesystem
72
     * @return true if the resource is a jar file, otherwise false
73
     */
74
    private boolean isJarFile(URL resourceUrl) {
75
        return FilenameUtils.getExtension(resourceUrl.getFile()).equals(VALID_MODULE_EXTENSION);
76
    }
77

  
78
    /**
79
     * Load one particular module given by input File containing opened jar file.
80
     * Method returns module in Optional in Pair where key is visible modules name and value is access Class from module.
81
     *
82
     * @param moduleUrl URL of the module in local filesystem
83
     * @return opened module or empty Optional where some error occurs.
84
     */
85
    private Optional<Pair<String, Class>> loadModule(URL moduleUrl) {
86
        JarInputStream jis = null;
87
        try {
88
            jis = new JarInputStream(new FileInputStream(FileUtils.toFile(moduleUrl)));
89
            final Manifest mf = jis.getManifest();
90
            final Attributes attributes = mf.getAttributes(MODULE_SECTION_IDENTIFIER);
91
            if (attributes == null) {
92
                throw new NotModuleException();
93
            }
94

  
95
            final String moduleClassName = attributes.getValue(MODULE_CLASS_IDENTIFIER);
96
            final String moduleVisibleName = attributes.getValue(MODULE_NAME_IDENTIFIER);
97

  
98
            final Class<?> clazz = Class.forName(moduleClassName, true, getClass().getClassLoader());
99
            // checking if method exists, if not throw exception
100
            clazz.getMethod(methodName, methodParamClass);
101

  
102
            return Optional.of(new Pair<>(moduleVisibleName, clazz));
103

  
104
        } catch (NotModuleException e) {
105
            return Optional.empty();
106
        } catch (Exception e) {
107
            logger.debug("Invalid module throw exception: ", e);
108
            return Optional.empty();
109
        } finally {
110
            try {
111
                if (jis != null) jis.close();
112
            } catch (IOException e) {
113
                logger.error("Can not close opened jar file: ", e);
114
            }
115
        }
116
    }
117
}
sources/imiger-core/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleProvider.java
1 1
package cz.zcu.kiv.offscreen.modularization;
2 2

  
3
import javafx.util.Pair;
3
import cz.zcu.kiv.imiger.spi.IModule;
4 4
import org.apache.logging.log4j.LogManager;
5 5
import org.apache.logging.log4j.Logger;
6 6

  
......
8 8
import java.net.URISyntaxException;
9 9
import java.nio.file.*;
10 10
import java.util.HashMap;
11
import java.util.Iterator;
11 12
import java.util.Map;
12
import java.util.Set;
13
import java.util.ServiceLoader;
13 14
import java.util.concurrent.*;
14 15

  
15 16
/**
......
17 18
 */
18 19
public class ModuleProvider {
19 20

  
20
    /** Name of accessed method in every module. */
21
    public static final String METHOD_NAME = "getRawJson";
22
    /** Class of input parameter to accessed method in every module. */
23
    public static final Class METHOD_PARAMETER_CLASS = String.class;
24

  
25 21
    private static final Logger logger = LogManager.getLogger();
26 22
    /** Instance of this class used for singleton pattern. */
27 23
    private static ModuleProvider instance = null;
28 24

  
29
    /**
30
     * Map containing actual loaded modules. Key is hash of visible name and value is pair of
31
     * visible name na accessed method.
32
     */
33
    private ConcurrentMap<String, Pair<String, Class>> modules = new ConcurrentHashMap<>();
34
    /** Instance of class ModuleLoader. */
35
    private final ModuleLoader loader;
25
    /** Queue containing actual loaded modules. */
26
    private Map<String, IModule> modules = new ConcurrentHashMap<>();
27
    /** Instance of ServiceLoader. */
28
    private final ServiceLoader<IModule> loader;
36 29

  
37 30
    /** Instance of ScheduledExecutorService used for scheduling of module folder watcher. */
38 31
    private ScheduledExecutorService executor;
......
60 53
     * When watcher fails than is automatically starts new watcher after 5 minutes timeout.
61 54
     */
62 55
    private ModuleProvider() {
63
        this.loader = new ModuleLoader(METHOD_NAME, METHOD_PARAMETER_CLASS);
56
        this.loader = ServiceLoader.load(IModule.class);
64 57

  
65
        processModules(loader.loadModules());
58
        processModules(loader.iterator());
66 59

  
67 60
        executor = Executors.newSingleThreadScheduledExecutor();
68 61
        Runnable task = this::initModulesWatcher;
......
120 113
                        continue;
121 114
                    }
122 115

  
123
                    processModules(loader.loadModules());
116
                    loader.reload();
117
                    processModules(loader.iterator());
124 118
                    break; // watching only one folder and loading all files every loop => Only one iteration is needed.
125 119
                }
126 120

  
......
142 136
     *
143 137
     * @param unprocessedModules Set of loaded modules where first argument is visible name and second is accessible method.
144 138
     */
145
    private void processModules(Set<Pair<String, Class>> unprocessedModules) {
139
    private void processModules(Iterator<IModule> unprocessedModules) {
146 140
        long startTime = System.nanoTime();
147
        Map<String, Pair<String, Class>> localModules = new HashMap<>();
141
        Map<String, IModule> localModules = new HashMap<>();
148 142

  
149
        logger.info("Processing " + unprocessedModules.size() + " modules.");
143
        logger.info("Processing modules.");
150 144

  
151
        for (Pair<String, Class> pair : unprocessedModules) {
152
            localModules.put(String.valueOf(pair.getKey().hashCode()), pair);
153
        }
145
        unprocessedModules.forEachRemaining(module -> localModules.put(String.valueOf(module.getModuleName().hashCode()), module));
154 146

  
155 147
        modules.clear();
156 148
        modules.putAll(localModules);
......
163 155
     * 
164 156
     * @return all loaded modules.
165 157
     */
166
    public Map<String, Pair<String, Class>> getModules() {
158
    public Map<String, IModule> getModules() {
167 159
        return modules;
168 160
    }
169 161
}
sources/imiger-core/src/main/java/cz/zcu/kiv/offscreen/modularization/NotModuleException.java
1
package cz.zcu.kiv.offscreen.modularization;
2

  
3
/**
4
 * Exception thrown when a jar file which is not a valid IMiGEr plugin is loaded.
5
 */
6
class NotModuleException extends Exception {
7
    NotModuleException() {
8
        super("Jar file is not a valid IMiGEr plugin!");
9
    }
10
}
sources/imiger-core/src/main/java/cz/zcu/kiv/offscreen/servlets/api/GetSessionDiagram.java
1 1
package cz.zcu.kiv.offscreen.servlets.api;
2 2

  
3 3
import com.google.gson.JsonObject;
4
import cz.zcu.kiv.imiger.spi.IModule;
4 5
import cz.zcu.kiv.offscreen.modularization.ModuleProvider;
5 6
import cz.zcu.kiv.offscreen.servlets.BaseServlet;
6
import javafx.util.Pair;
7 7
import org.apache.commons.lang3.StringUtils;
8 8
import org.apache.logging.log4j.LogManager;
9 9
import org.apache.logging.log4j.Logger;
......
11 11
import javax.servlet.http.HttpServletRequest;
12 12
import javax.servlet.http.HttpServletResponse;
13 13
import java.io.IOException;
14
import java.lang.reflect.Method;
15 14
import java.util.Optional;
16 15

  
17 16
/**
......
85 84
    private Optional<String> callModuleConverter(String type, String stringToConvert){
86 85
        logger.debug("Processing json with module");
87 86

  
88
        Pair<String, Class> module = ModuleProvider.getInstance().getModules().get(type);
89
        if (module == null){
87
        IModule module = ModuleProvider.getInstance().getModules().get(type);
88
        if (module == null) {
90 89
            logger.debug("No loader available for type: " + type + ". Response BAD REQUEST");
91 90
            return Optional.empty();
92 91
        }
93 92

  
94 93
        try {
95
            final Class<?> moduleClass = module.getValue();
96
            // switching to class loader of module
97
            final ClassLoader appClassLoader = Thread.currentThread().getContextClassLoader();
98
            Thread.currentThread().setContextClassLoader(moduleClass.getClassLoader());
94
            String rawJson = String.valueOf(module.getRawJson(stringToConvert));
99 95

  
100
            final Method moduleMethod = moduleClass.getMethod(ModuleProvider.METHOD_NAME, ModuleProvider.METHOD_PARAMETER_CLASS);
101
            String rawJson = String.valueOf(moduleMethod.invoke(moduleClass.newInstance(), stringToConvert));
102

  
103
            // switching back to application class loader
104
            Thread.currentThread().setContextClassLoader(appClassLoader);
105

  
106
            if(StringUtils.isBlank(rawJson)){
96
            if (StringUtils.isBlank(rawJson)){
107 97
                return Optional.empty();
108 98
            } else {
109 99
                return Optional.of(rawJson);
110 100
            }
111 101

  
112 102
        } catch (Exception e) {
113
            logger.error("Can not call convert method in module. Module name: " + module.getKey(), e);
103
            logger.error("Can not call convert method in module. Module name: " + module.getModuleName(), e);
114 104
            return Optional.empty();
115 105
        }
116 106
    }
sources/imiger-core/src/main/webapp/uploadFiles.jsp
74 74
						Select type of data file:<br>
75 75
						<label for="raw"><input type="radio" name="fileFormat" value="raw" id="raw" checked> Raw JSON</label><br>
76 76
						<c:forEach items="${processingModules}" var="module">
77
							<label for="${module.key}"><input type="radio" name="fileFormat" value="${module.key}" id="${module.key}"> ${module.value.key}</label><br>
77
							<label for="${module.key}"><input type="radio" name="fileFormat" value="${module.key}" id="${module.key}"> ${module.value.moduleName}</label><br>
78 78
						</c:forEach>
79 79
					</div>
80 80

  
sources/imiger-module/src/main/java/cz/zcu/kiv/imiger/spi/IModule.java
1
package cz.zcu.kiv.imiger.spi;
2

  
3
/**
4
 * Service Provider Interface (SPI) that must be implemented by all modules.
5
 */
6
public interface IModule {
7
    /**
8
     * @return Display name of the module.
9
     */
10
    String getModuleName();
11

  
12
    /**
13
     * Converts input string in any format to raw JSON processable by IMiGEr.
14
     *
15
     * @param stringToConvert String to be converted to raw JSON.
16
     * @return Raw JSON.
17
     */
18
    String getRawJson(String stringToConvert);
19
}
sources/imiger-plugin-template/pom.xml
26 26
        <plugins>
27 27
            <plugin>
28 28
                <groupId>org.apache.maven.plugins</groupId>
29
                <artifactId>maven-assembly-plugin</artifactId>
30
                <configuration>
31
                    <archive>
32
                        <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
33
                    </archive>
34
                    <descriptorRefs>
35
                        <descriptorRef>jar-with-dependencies</descriptorRef>
36
                    </descriptorRefs>
37
                </configuration>
38
                <executions>
39
                    <execution>
40
                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
41
                        <phase>package</phase> <!-- bind to the packaging phase -->
42
                        <goals>
43
                            <goal>single</goal>
44
                        </goals>
45
                    </execution>
46
                </executions>
29
                <artifactId>maven-jar-plugin</artifactId>
30
                <version>3.1.1</version>
47 31
            </plugin>
48 32
        </plugins>
49 33
    </build>
sources/imiger-plugin-template/src/main/java/cz/zcu/kiv/imiger/plugin/template/Converter.java
1 1
package cz.zcu.kiv.imiger.plugin.template;
2 2

  
3 3
import com.google.gson.Gson;
4
import cz.zcu.kiv.imiger.spi.IModule;
4 5
import cz.zcu.kiv.imiger.vo.Graph;
5 6

  
6
public class Converter {
7
public class Converter implements IModule {
8
    @Override
9
    public String getModuleName() {
10
        return "Module Template";
11
    }
7 12

  
8 13
    /**
9 14
     * Convert input file to RAW JSON and return it.
10 15
     */
16
    @Override
11 17
    public String getRawJson(String inputFile) {
12

  
13 18
        Graph graph = new Graph();
14 19
        return new Gson().toJson(graph);
15 20
    }
sources/imiger-plugin-template/src/main/resources/META-INF/MANIFEST.MF
1
Manifest-Version: 1.0
2
Created-By: Apache Maven ${maven.version}
3
Built-By: ${user.name}
4
Build-Jdk: ${java.version}
5
Module-Class: cz.zcu.kiv.imiger.plugin.template.Converter
6
Module-Name: My Convertor
sources/imiger-plugin-template/src/main/resources/META-INF/services/cz.zcu.kiv.imiger.spi.IModule
1
cz.zcu.kiv.imiger.plugin.template.Converter
sources/imiger-plugin-template/src/test/java/PluginTest.java
1
import cz.zcu.kiv.imiger.spi.IModule;
1 2
import org.junit.jupiter.api.Test;
2
import org.junit.platform.commons.util.StringUtils;
3 3

  
4 4
import java.io.File;
5
import java.io.FileInputStream;
6
import java.io.IOException;
7
import java.lang.reflect.Method;
5
import java.net.MalformedURLException;
8 6
import java.net.URL;
9 7
import java.net.URLClassLoader;
10
import java.util.jar.Attributes;
11
import java.util.jar.JarInputStream;
12
import java.util.jar.Manifest;
8
import java.util.Iterator;
9
import java.util.ServiceLoader;
13 10

  
14
import static org.junit.jupiter.api.Assertions.*;
11
import static org.junit.jupiter.api.Assertions.assertTrue;
15 12

  
16 13
/**
17 14
 * This class is only for testing integrity of Jar file.
15
 * <p>
18 16
 * Testing:
19
 *      Jar file can be open
20
 *      Manifest contains Module-Name and it is not blank
21
 *      Manifest contains Module-Class and this class exists in Jar file
22
 *      Given class contains method 'String getRawJson(String)'
23
 *
24
 *
25
 * !!! BEFORE USAGE PLEASE CHANGE VARIABLE 'ARCHIVE_WITH_DEPENDENCIES_PATH' !!!
17
 * <ul>
18
 *     <li>Jar file exists
19
 *     <li>Jar file is valid service provider
20
 *     <li>Jar file contains at least one service implementing {@link cz.zcu.kiv.imiger.spi.IModule} interface
21
 * </ul>
22
 * <p>
23
 * !!! BEFORE USAGE PLEASE CHANGE VARIABLE 'ARCHIVE_PATH' !!!
26 24
 */
27 25
class PluginTest {
28 26

  
29
    private static final String ARCHIVE_WITH_DEPENDENCIES_PATH = "target\\imiger-plugin-template-1.0-SNAPSHOT-jar-with-dependencies.jar";
30

  
27
    private static final String ARCHIVE_PATH = "target/imiger-plugin-template-1.0-SNAPSHOT.jar";
31 28

  
32 29
    // ========================================= DO NOT CHANGE UNDER THIS LINE =========================================
33 30

  
34
    private static final String MODULE_CLASS_IDENTIFIER = "Module-Class";
35
    private static final String MODULE_NAME_IDENTIFIER = "Module-Name";
36
    private static final String METHOD_NAME = "getRawJson";
37
    private static final Class METHOD_PARAMETER_CLASS = String.class;
38

  
39 31
    @Test
40
    void integrityTest() {
41
        try {
42
            File jarFile = new File(ARCHIVE_WITH_DEPENDENCIES_PATH);
43

  
44
            JarInputStream jis = new JarInputStream(new FileInputStream(jarFile));
45
            final Manifest mf = jis.getManifest();
46
            final Attributes attributes = mf.getMainAttributes();
47
            final String moduleClassName = attributes.getValue(MODULE_CLASS_IDENTIFIER);
48
            final String moduleVisibleName = attributes.getValue(MODULE_NAME_IDENTIFIER);
49

  
50
            assertFalse(StringUtils.isBlank(moduleClassName), "Blank module class name given by manifest attribute 'Module-Class'");
51
            assertFalse(StringUtils.isBlank(moduleVisibleName), "Blank module visible name given by manifest attribute 'Module-Name'");
32
    void integrityTest() throws MalformedURLException {
33
        File jarFile = new File(ARCHIVE_PATH);
34
        assertTrue(jarFile.exists(), "Jar file does not exist!");
52 35

  
53
            final ClassLoader loader = URLClassLoader.newInstance(new URL[]{jarFile.toURI().toURL()});
54
            final Class<?> clazz = Class.forName(moduleClassName, true, loader);
55
            // checking if method exists, if not throw exception
56
            Method method = clazz.getMethod(METHOD_NAME, METHOD_PARAMETER_CLASS);
36
        ServiceLoader<IModule> serviceLoader = ServiceLoader.load(IModule.class, new URLClassLoader(new URL[] { jarFile.toURI().toURL() }));
37
        Iterator<IModule> moduleIterator = serviceLoader.iterator();
57 38

  
58

  
59
            assertEquals(String.class, method.getReturnType(), "Invalid method return type.");
60

  
61
        } catch (IOException e){
62
            fail("Can not open given Jar file. Given path is probably incorrect.");
63
        } catch (ClassNotFoundException e) {
64
            fail("Class given by attribute 'Module-Class' in MANIFEST.MF was not found in Jar file.");
65
        } catch (NoSuchMethodException e) {
66
            fail("Class given by attribute 'Module-Class' in MANIFEST.MF do not contains method: String getRawJson(String).");
39
        int modulesCount = 0;
40
        while (moduleIterator.hasNext()) {
41
            modulesCount++;
42
            moduleIterator.next();
67 43
        }
44

  
45
        assertTrue(modulesCount > 0, "There should be at least one service provider in the Jar file!");
68 46
    }
69 47
}
sources/imiger-spade-converter/pom.xml
21 21
                <groupId>org.apache.maven.plugins</groupId>
22 22
                <artifactId>maven-jar-plugin</artifactId>
23 23
                <version>3.1.1</version>
24
                <configuration>
25
                    <archive>
26
                        <manifestSections>
27
                            <manifestSection>
28
                                <name>cz.zcu.kiv.imiger.plugin</name>
29
                                <manifestEntries>
30
                                    <Module-Class>cz.zcu.kiv.imiger.plugin.spade.Spade</Module-Class>
31
                                    <Module-Name>Spade JSON</Module-Name>
32
                                </manifestEntries>
33
                            </manifestSection>
34
                        </manifestSections>
35
                    </archive>
36
                </configuration>
37 24
            </plugin>
38 25
        </plugins>
39 26
    </build>
sources/imiger-spade-converter/src/main/java/cz/zcu/kiv/imiger/plugin/spade/Spade.java
1 1
package cz.zcu.kiv.imiger.plugin.spade;
2 2

  
3
import cz.zcu.kiv.imiger.spi.IModule;
3 4
import cz.zcu.kiv.imiger.vo.Graph;
4 5
import cz.zcu.kiv.imiger.plugin.spade.graph.GraphManager;
5 6
import cz.zcu.kiv.imiger.plugin.spade.graph.loader.GraphJSONDataLoader;
6 7
import cz.zcu.kiv.imiger.plugin.spade.graph.loader.JSONConfigLoader;
7 8
import net.sf.json.JSONObject;
8 9

  
9
public class Spade {
10
public class Spade implements IModule {
11
    @Override
12
    public String getModuleName() {
13
        return "Spade JSON";
14
    }
10 15

  
11 16
    /**
12
     * Convert input spade JSON to RAW JSON and return it.
17
     * Convert input spade JSON to RAW JSON.
18
     *
19
     * @param file String to be converted to raw JSON.
20
     * @return Raw JSON.
13 21
     */
22
    @Override
14 23
    public String getRawJson(String file) {
15 24
        GraphManager graphManager = new GraphJSONDataLoader(file).loadData();
16 25
        JSONConfigLoader configLoader = new JSONConfigLoader(graphManager);
sources/imiger-spade-converter/src/main/resources/META-INF/services/cz.zcu.kiv.imiger.spi.IModule
1
cz.zcu.kiv.imiger.plugin.spade.Spade

Také k dispozici: Unified diff