[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-jigsaw-dev
Subject:    Re: Add posibility to add custom ModuleReaderFactory to ModuleFinder
From:       Remi Forax <forax () univ-mlv ! fr>
Date:       2018-09-28 16:02:13
Message-ID: 2004461303.656236.1538150533874.JavaMail.zimbra () u-pem ! fr
[Download RAW message or body]



----- Mail original -----
> De: "Alan Bateman" <Alan.Bateman@oracle.com>
> À: "Alex Sviridov" <ooo_saturn7@mail.ru>, "jigsaw-dev" \
> <jigsaw-dev@openjdk.java.net> Envoyé: Vendredi 28 Septembre 2018 15:51:56
> Objet: Re: Add posibility to add custom ModuleReaderFactory to ModuleFinder

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


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


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic