Add posibility to add custom ModuleReaderFactory to ModuleFinder

Alan Bateman Alan.Bateman at oracle.com
Fri Sep 28 13:51:56 UTC 2018


On 28/09/2018 13:10, Alex Sviridov wrote:
> Hi 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 
probably start with something like:

         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()) {
                 @Override
                 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);
         }

         @Override
         public Optional<ModuleReference> find(String name) {
             if (name.equals(mref.descriptor().name())) {
                 return Optional.of(mref);
             } else {
                 return Optional.empty();
             }
         }

         @Override
         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;
             }
         }
     }



More information about the jigsaw-dev mailing list