Projekt

Obecné

Profil

« Předchozí | Další » 

Revize 9bebe997

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

dynamic module loading with SPI

Zobrazit rozdíly:

sources/imiger-core/src/main/java/cz/zcu/kiv/offscreen/modularization/DynamicServiceLoader.java
1
package cz.zcu.kiv.offscreen.modularization;
2

  
3
import org.apache.logging.log4j.LogManager;
4
import org.apache.logging.log4j.Logger;
5
import org.apache.logging.log4j.message.ParameterizedMessage;
6

  
7
import java.io.IOException;
8
import java.net.URISyntaxException;
9
import java.net.URL;
10
import java.net.URLClassLoader;
11
import java.nio.charset.StandardCharsets;
12
import java.nio.file.FileSystem;
13
import java.nio.file.Files;
14
import java.nio.file.Path;
15
import java.nio.file.Paths;
16
import java.nio.file.spi.FileSystemProvider;
17
import java.util.*;
18
import java.util.function.Predicate;
19
import java.util.stream.Collectors;
20
import java.util.stream.Stream;
21

  
22
/**
23
 * @author Tomáš Šimandl
24
 */
25
class DynamicServiceLoader<S> {
26

  
27
    private static final Logger logger = LogManager.getLogger();
28
    /** Valid file extension of Java module. */
29
    private static final String VALID_MODULE_EXTENSION = "jar";
30
    /** Filter to get only jar files from modules folder. */
31
    private static final Predicate<Path> MODULE_FILTER = path -> path.toString().toLowerCase().endsWith(VALID_MODULE_EXTENSION);
32
    /** Interface or abstract class representing the service to be loaded. */
33
    private final Class<S> service;
34
    /** Instance of static SPI loader. */
35
    private ServiceLoader<S> serviceLoader;
36
    /** Path to folder where are modules. Path is relative to resources. */
37
    private final String servicesPath;
38
    /** Instance of file system provider capable of exploring jar files. */
39
    private final FileSystemProvider provider;
40

  
41
    /**
42
     * @param service Interface or abstract class representing the service to be loaded.
43
     * @param servicesPath Path to folder to load services from. Path is relative to resources.
44
     */
45
    DynamicServiceLoader(Class<S> service, String servicesPath) {
46
        this.service = service;
47
        this.serviceLoader = ServiceLoader.load(service);
48
        this.servicesPath = servicesPath;
49
        this.provider = getZipFileSystemProvider();
50

  
51
        logger.info("Initialized new DynamicServiceLoader with folder path: " + servicesPath);
52
    }
53

  
54
    /**
55
     * Loads services implements {@link S} interface.
56
     * @return Iterator of loaded services.
57
     */
58
    public Iterator<S> load() {
59
        return serviceLoader.iterator();
60
    }
61

  
62
    /**
63
     * Dynamically reloads services from services path.
64
     */
65
    public void reload() {
66
        logger.info("Reloading all modules from path.");
67

  
68
        Optional<Path> servicesPath = getServicesPath();
69
        if (servicesPath.isPresent()) {
70
            List<URL> serviceURLs = new ArrayList<>();
71
            List<String> implementationsToLoad = new ArrayList<>();
72

  
73
            try (Stream<Path> paths = Files.walk(servicesPath.get())) {
74
                List<Path> jarPaths = paths.filter(MODULE_FILTER).collect(Collectors.toList());
75
                for (Path jarPath : jarPaths) {
76
                    FileSystem fileSystem = provider.newFileSystem(jarPath, new HashMap<>());
77
                    Path serviceFilePath = fileSystem.getPath("/META-INF", "services", service.getCanonicalName());
78

  
79
                    if (Files.exists(serviceFilePath)) {
80
                        serviceURLs.add(jarPath.toUri().toURL());
81

  
82
                        List<String> serviceLines = Files.readAllLines(serviceFilePath, StandardCharsets.UTF_8);
83
                        serviceLines.removeIf(line -> line.startsWith("#"));
84
                        implementationsToLoad.addAll(serviceLines);
85
                    }
86
                }
87
            } catch (IOException e) {
88
                logger.warn("Can not open services directory or service jar file.", e);
89
            }
90

  
91
            ClassLoader classLoader = URLClassLoader.newInstance(serviceURLs.toArray(new URL[0]), getClass().getClassLoader());
92
            implementationsToLoad.forEach(className -> loadClass(classLoader, className));
93

  
94
            serviceLoader = ServiceLoader.load(service, classLoader);
95
            serviceLoader.reload();
96
        }
97
    }
98

  
99
    /**
100
     * Checks if modules path exists and if it is a directory.
101
     * On success return Optional of opened folder otherwise returns empty optional.
102
     * @return Optional of opened folder otherwise returns empty optional.
103
     */
104
    Optional<Path> getServicesPath() {
105
        final URL servicesURL = getClass().getClassLoader().getResource(servicesPath);
106
        if (servicesURL == null) {
107
            logger.warn("Cannot open services directory.");
108
            return Optional.empty();
109
        }
110

  
111
        try {
112
            final Path servicesPath = Paths.get(servicesURL.toURI());
113

  
114
            if (!Files.exists(servicesPath) || !Files.isDirectory(servicesPath)) {
115
                logger.warn("Services path does not exist or it is not a directory.");
116
                return Optional.empty();
117
            }
118
            return Optional.of(servicesPath);
119

  
120
        } catch (URISyntaxException e) {
121
            logger.warn("Can not open services directory", e);
122
            return Optional.empty();
123
        }
124
    }
125

  
126
    /**
127
     * @return File system provider capable of exploring jar files.
128
     */
129
    private FileSystemProvider getZipFileSystemProvider() {
130
        for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
131
            if (VALID_MODULE_EXTENSION.equals(provider.getScheme())) {
132
                return provider;
133
            }
134
        }
135
        return null;
136
    }
137

  
138
    /**
139
     * Dynamically loads class using classloader.
140
     * @param classLoader Class loader used for loading class.
141
     * @param className Fully qualified class name.
142
     */
143
    private void loadClass(ClassLoader classLoader, String className) {
144
        try {
145
            classLoader.loadClass(className);
146
        } catch (ClassNotFoundException e) {
147
            logger.warn(new ParameterizedMessage("Cannot load class {}.", className), e);
148
        }
149
    }
150
}
sources/imiger-core/src/main/java/cz/zcu/kiv/offscreen/modularization/ModuleProvider.java
5 5
import org.apache.logging.log4j.Logger;
6 6

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

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

  
21 19
    private static final Logger logger = LogManager.getLogger();
20
    /** Path to folder with modules relative to resources */
21
    private static final String MODULES_PATH = "/../lib/";
22 22
    /** Instance of this class used for singleton pattern. */
23 23
    private static ModuleProvider instance = null;
24 24

  
25 25
    /** Queue containing actual loaded modules. */
26 26
    private Map<String, IModule> modules = new ConcurrentHashMap<>();
27
    /** Instance of ServiceLoader. */
28
    private final ServiceLoader<IModule> loader;
27
    /** Instance of DynamicServiceLoader. */
28
    private final DynamicServiceLoader<IModule> serviceLoader;
29 29

  
30 30
    /** Instance of ScheduledExecutorService used for scheduling of module folder watcher. */
31 31
    private ScheduledExecutorService executor;
......
53 53
     * When watcher fails than is automatically starts new watcher after 5 minutes timeout.
54 54
     */
55 55
    private ModuleProvider() {
56
        this.loader = ServiceLoader.load(IModule.class);
56
        serviceLoader = new DynamicServiceLoader<>(IModule.class, MODULES_PATH);
57 57

  
58
        processModules(loader.iterator());
58
        processModules(serviceLoader.load());
59 59

  
60 60
        executor = Executors.newSingleThreadScheduledExecutor();
61 61
        Runnable task = this::initModulesWatcher;
......
91 91
     * and storing (method processModules) of all modules.
92 92
     */
93 93
    private void initModulesWatcher() {
94
        Optional<Path> modulesFolderOptional = serviceLoader.getServicesPath();
95
        if (!modulesFolderOptional.isPresent()) {
96
            return;
97
        }
98

  
94 99
        try {
95 100
            logger.debug("Initializing new WatcherService for modules directory");
96 101

  
97
            Path path = Paths.get(getClass().getClassLoader().getResource("/../lib/").toURI());
98 102
            watcher = FileSystems.getDefault().newWatchService();
103

  
104
            Path path = modulesFolderOptional.get();
99 105
            path.register(watcher,
100 106
                    StandardWatchEventKinds.ENTRY_CREATE,
101 107
                    StandardWatchEventKinds.ENTRY_DELETE,
......
113 119
                        continue;
114 120
                    }
115 121

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

  
......
124 130
                }
125 131
            }
126 132

  
127
        } catch (URISyntaxException | IOException | InterruptedException e) {
133
        } catch (IOException | InterruptedException e) {
128 134
            logger.error("Modules directory watcher throw an exception: ", e);
129 135
        }
130 136
    }
......
138 144
     */
139 145
    private void processModules(Iterator<IModule> unprocessedModules) {
140 146
        long startTime = System.nanoTime();
141
        Map<String, IModule> localModules = new HashMap<>();
142

  
143 147
        logger.info("Processing modules.");
144 148

  
145
        unprocessedModules.forEachRemaining(module -> localModules.put(String.valueOf(module.getModuleName().hashCode()), module));
146

  
147 149
        modules.clear();
148
        modules.putAll(localModules);
150
        unprocessedModules.forEachRemaining(module -> modules.put(String.valueOf(module.getModuleName().hashCode()), module));
151

  
149 152
        logger.debug("Modules were loaded and processed in " + (System.nanoTime() - startTime) / 1000000d + " milliseconds");
150 153
    }
151 154

  
sources/imiger-plugin-template/pom.xml
26 26
        <plugins>
27 27
            <plugin>
28 28
                <groupId>org.apache.maven.plugins</groupId>
29
                <artifactId>maven-jar-plugin</artifactId>
30
                <version>3.1.1</version>
29
                <artifactId>maven-assembly-plugin</artifactId>
30
                <configuration>
31
                    <descriptorRefs>
32
                        <descriptorRef>jar-with-dependencies</descriptorRef>
33
                    </descriptorRefs>
34
                </configuration>
35
                <executions>
36
                    <execution>
37
                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
38
                        <phase>package</phase> <!-- bind to the packaging phase -->
39
                        <goals>
40
                            <goal>single</goal>
41
                        </goals>
42
                    </execution>
43
                </executions>
31 44
            </plugin>
32 45
        </plugins>
33 46
    </build>
sources/imiger-spade-converter/pom.xml
19 19
        <plugins>
20 20
            <plugin>
21 21
                <groupId>org.apache.maven.plugins</groupId>
22
                <artifactId>maven-jar-plugin</artifactId>
23
                <version>3.1.1</version>
22
                <artifactId>maven-assembly-plugin</artifactId>
23
                <configuration>
24
                    <descriptorRefs>
25
                        <descriptorRef>jar-with-dependencies</descriptorRef>
26
                    </descriptorRefs>
27
                </configuration>
28
                <executions>
29
                    <execution>
30
                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
31
                        <phase>package</phase> <!-- bind to the packaging phase -->
32
                        <goals>
33
                            <goal>single</goal>
34
                        </goals>
35
                    </execution>
36
                </executions>
24 37
            </plugin>
25 38
        </plugins>
26 39
    </build>

Také k dispozici: Unified diff