Revize 9bebe997
Přidáno uživatelem Pavel Fidranský před asi 6 roky(ů)
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
dynamic module loading with SPI