----- Mail original -----
Envoyé: Vendredi 28 Septembre 2018 15:51:56
Objet: Re: Add posibility to add custom ModuleReaderFactory to ModuleFinder
Post by Alex SviridovHi Alan
Thank you for your answer. But my main problem is not jars inside .war
- this is a so far from my current problem. Now I need to 1) add .war
file to layer 2). to map file location, for example instead of
"module-info.java" I must find "WEB-INF/classes/module-info.java" etc.
That is all I need. How can I do it without implementing ModuleFinder?
You'll need a ModuleFinder because the packaging formats that
ModuleFinder.of(Path) is required to support doesn't know anything about
WAR files. It's not super difficult to develop your own. I attach a
simple implementation that may get you started. It's really basic but
would need a few iterations to be robust. Invoke
WarModuleFinder.of(Path) with the file path to the WAR file and it will
create a ModuleFinder that can find the application module in the WAR
file. A more complete implementation would be a lot more robust and
polished that this sample, it would also find the modules WEB-INF/lib.
Once you have a ModuleFinder then you specify it to Conguration::resolve
method when resolving the application as the root module. You'll
Path war = Path.of("app.war");
ModuleFinder finder = WarModuleFinder.of(war);
String appModuleName = finder.findAll().stream()
.findFirst()
.map(ModuleReference::descriptor)
.map(ModuleDescriptor::name)
.orElseThrow();
ModuleLayer boot = ModuleLayer.boot();
Configuration cf = boot.configuration().resolve(finder,
ModuleFinder.of(), Set.of(appModuleName));
ModuleLayer layer = boot.defineModulesWithOneLoader(cf,
ClassLoader.getSystemClassLoader());
and now you have a module layer with the application module loaded from
the WEB-INF/classes part of the WAR file.
-Alan
static class WarModuleFinder implements ModuleFinder {
private final FileSystem warfs;
private final Path classes;
private final ModuleReference mref;
private WarModuleFinder(Path warfile) throws IOException {
ClassLoader scl = ClassLoader.getSystemClassLoader();
FileSystem fs = FileSystems.newFileSystem(warfile, scl);
Path classes = fs.getPath("/WEB-INF/classes");
ModuleDescriptor descriptor;
try (InputStream in =
Files.newInputStream(classes.resolve("module-info.class"))) {
descriptor = ModuleDescriptor.read(in, () ->
packages(classes));
}
this.warfs = fs;
this.classes = classes;
this.mref = new ModuleReference(descriptor, classes.toUri()) {
public ModuleReader open() {
return new WarModuleReader();
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[module ");
sb.append(descriptor().name());
sb.append(", location=");
sb.append(location());
sb.append("]");
return sb.toString();
}
};
}
static WarModuleFinder of(Path war) throws IOException {
return new WarModuleFinder(war);
}
public Optional<ModuleReference> find(String name) {
if (name.equals(mref.descriptor().name())) {
return Optional.of(mref);
} else {
return Optional.empty();
}
}
public Set<ModuleReference> findAll() {
return Set.of(mref);
}
private Set<String> packages(Path classes) {
try {
return Files.find(classes, Integer.MAX_VALUE,
(path, attrs) -> !attrs.isDirectory())
.map(entry -> classes.relativize(entry).toString())
.map(this::toPackageName)
.flatMap(Optional::stream)
.collect(Collectors.toSet());
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
private Optional<String> toPackageName(String name) {
int index = name.lastIndexOf("/");
if (index > 0) {
return Optional.of(name.substring(0,
index).replace('/', '.'));
} else {
return Optional.empty();
}
}
class WarModuleReader implements ModuleReader {
private volatile boolean closed;
private void ensureOpen() throws IOException {
if (closed) throw new IOException("ModuleReader is
closed");
}
public Optional<URI> find(String name) throws IOException {
ensureOpen();
if (!name.startsWith("/")) {
Path entry = classes.resolve(name);
if (Files.exists(entry)) {
return Optional.of(entry.toUri());
}
}
return Optional.empty();
}
public Stream<String> list() throws IOException {
ensureOpen();
return Files.walk(classes)
.map(entry -> classes.relativize(entry).toString())
.filter(name -> name.length() > 0);
}
public void close() {
closed = true;
}
}
}
which give you the following code, if you
- move the code from the constructor to the static factory
- untangle the WarModuleFinder and the WarModuleReader
- use Optional the monadic way (why there is no filter on OptionalInt BTW ?)
- sprinkle some vars on it
import static java.util.function.Predicate.not;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class WarModuleFinder implements ModuleFinder {
private final ModuleReference mref;
private WarModuleFinder(ModuleReference mref) {
this.mref = mref;
}
public static WarModuleFinder of(Path war) throws IOException {
var systemClassLoader = ClassLoader.getSystemClassLoader();
var fileSystem = FileSystems.newFileSystem(war, systemClassLoader);
var classes = fileSystem.getPath("/WEB-INF/classes");
ModuleDescriptor descriptor;
try (InputStream in = Files.newInputStream(classes.resolve("module-info.class"))) {
descriptor = ModuleDescriptor.read(in, () -> packages(classes));
}
return new WarModuleFinder(new ModuleReference(descriptor, classes.toUri()) {
@Override
public ModuleReader open() {
return new WarModuleReader(classes);
}
@Override
public String toString() {
return "[module " + descriptor().name() + ", location=" + location() + ']';
}
});
}
@Override
public Optional<ModuleReference> find(String name) {
return Optional.of(mref).filter(mref -> name.equals(mref.descriptor().name()));
}
@Override
public Set<ModuleReference> findAll() {
return Set.of(mref);
}
private static Set<String> packages(Path classes) {
try {
return Files.find(classes, Integer.MAX_VALUE, (path, attrs) -> !attrs.isDirectory())
.map(entry -> toPackageName(classes.relativize(entry).toString()))
.flatMap(Optional::stream)
.collect(Collectors.toSet());
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
}
private static Optional<String> toPackageName(String name) {
return Optional.of(name.lastIndexOf("/"))
.filter(index -> index > 0)
.map(index -> name.substring(0, index).replace('/', '.'));
}
private static class WarModuleReader implements ModuleReader {
private final Path classes;
private volatile boolean closed;
private WarModuleReader(Path classes) {
this.classes = classes;
}
private void ensureOpen() throws IOException {
if (closed) {
throw new IOException("ModuleReader is closed");
}
}
@Override
public Optional<URI> find(String name) throws IOException {
ensureOpen();
return Optional.of(name)
.filter(not(_name -> _name.startsWith("/")))
.map(classes::resolve)
.filter(Files::exists)
.map(Path::toUri);
}
@Override
public Stream<String> list() throws IOException {
ensureOpen();
return Files.walk(classes).map(entry -> classes.relativize(entry).toString()).filter(not(String::isEmpty));
}
@Override
public void close() {
closed = true;
}
}
}
Rémi