All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.modules.LocalModuleFinder Maven / Gradle / Ivy

There is a newer version: 2.1.5.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.modules;

import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.UndeclaredThrowableException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Predicate;

import org.jboss.modules.filter.PathFilter;
import org.jboss.modules.filter.PathFilters;
import org.jboss.modules.xml.ModuleXmlParser;

import static java.security.AccessController.doPrivileged;
import static org.jboss.modules.Utils.MODULE_FILE;

/**
 * A module finder which locates module specifications which are stored in a local module
 * repository on the filesystem, which uses {@code module.xml} descriptors.
 *
 * @author David M. Lloyd
 * @author Richard Opalka
 */
public final class LocalModuleFinder implements IterableModuleFinder, AutoCloseable {

    private static final File[] NO_FILES = new File[0];

    private final File[] repoRoots;
    private final PathFilter pathFilter;
    private final AccessControlContext accessControlContext;
    private final List resourceLoaderList = new ArrayList<>(64);
    private final ModuleXmlParser.ResourceRootFactory resourceRootFactory;

    private static final ResourceLoader TERMINATED_MARKER = new ResourceLoader() {
        public String getRootName() {
            return null;
        }

        public ClassSpec getClassSpec(final String fileName) throws IOException {
            return null;
        }

        public PackageSpec getPackageSpec(final String name) throws IOException {
            return null;
        }

        public Resource getResource(final String name) {
            return null;
        }

        public String getLibrary(final String name) {
            return null;
        }

        public Collection getPaths() {
            return null;
        }
    };

    private LocalModuleFinder(final File[] repoRoots, final PathFilter pathFilter, final boolean cloneRoots) {
        this.repoRoots = cloneRoots && repoRoots.length > 0 ? repoRoots.clone() : repoRoots;
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            for (File repoRoot : this.repoRoots) {
                if (repoRoot != null) sm.checkPermission(new FilePermission(new File(repoRoot, "-").getPath(), "read"));
            }
        }
        this.pathFilter = pathFilter;
        this.accessControlContext = AccessController.getContext();
        resourceRootFactory = (rootPath, loaderPath, loaderName) -> {
            final ResourceLoader loader = ModuleXmlParser.ResourceRootFactory.getDefault().createResourceLoader(rootPath, loaderPath, loaderName);
            final List list = this.resourceLoaderList;
            synchronized (list) {
                if (list.size() == 1 && list.get(0) == TERMINATED_MARKER) {
                    safeClose(loader);
                    throw new IllegalStateException("Module finder is closed");
                }
                list.add(loader);
            }
            return loader;
        };
    }

    /**
     * Construct a new instance.
     *
     * @param repoRoots the repository roots to use
     * @param pathFilter the path filter to use
     */
    public LocalModuleFinder(final File[] repoRoots, final PathFilter pathFilter) {
        this(repoRoots, pathFilter, true);
    }

    /**
     * Construct a new instance.
     *
     * @param repoRoots the repository roots to use
     */
    public LocalModuleFinder(final File[] repoRoots) {
        this(repoRoots, PathFilters.acceptAll());
    }

    /**
     * Construct a new instance, using the {@code module.path} system property or the {@code JAVA_MODULEPATH} environment variable
     * to get the list of module repository roots.
     * 

* This is equivalent to a call to {@link LocalModuleFinder#LocalModuleFinder(boolean) LocalModuleFinder(true)}. *

*/ public LocalModuleFinder() { this(true); } /** * Construct a new instance, using the {@code module.path} system property or the {@code JAVA_MODULEPATH} environment variable * to get the list of module repository roots. * * @param supportLayersAndAddOns {@code true} if the identified module repository roots should be checked for * an internal structure of child "layer" and "add-on" directories that may also * be treated as module roots lower in precedence than the parent root. Any "layers" * subdirectories whose names are specified in a {@code layers.conf} file found in * the module repository root will be added in the precedence of order specified * in the {@code layers.conf} file; all "add-on" subdirectories will be added at * a lower precedence than all "layers" and with no guaranteed precedence order * between them. If {@code false} no check for "layer" and "add-on" directories * will be performed. * */ public LocalModuleFinder(boolean supportLayersAndAddOns) { this(getRepoRoots(supportLayersAndAddOns), PathFilters.acceptAll(), false); } static File[] getRepoRoots(final boolean supportLayersAndAddOns) { return supportLayersAndAddOns ? LayeredModulePathFactory.resolveLayeredModulePath(getModulePathFiles()) : getModulePathFiles(); } private static File[] getModulePathFiles() { return getFiles(System.getProperty("module.path", System.getenv("JAVA_MODULEPATH")), 0, 0); } private static File[] getFiles(final String modulePath, final int stringIdx, final int arrayIdx) { if (modulePath == null) return NO_FILES; final int i = modulePath.indexOf(File.pathSeparatorChar, stringIdx); final File[] files; if (i == -1) { files = new File[arrayIdx + 1]; files[arrayIdx] = new File(modulePath.substring(stringIdx)).getAbsoluteFile(); } else { files = getFiles(modulePath, i + 1, arrayIdx + 1); files[arrayIdx] = new File(modulePath.substring(stringIdx, i)).getAbsoluteFile(); } return files; } public ModuleSpec findModule(final String name, final ModuleLoader delegateLoader) throws ModuleLoadException { final String child = PathUtils.basicModuleNameToPath(name); if (child == null) { return null; // not valid, so not found } final PathFilter pathFilter = this.pathFilter; if (pathFilter.accept(child + "/")) { try { return doPrivileged((PrivilegedExceptionAction) () -> parseModuleXmlFile(resourceRootFactory, name, delegateLoader, repoRoots), accessControlContext); } catch (PrivilegedActionException e) { try { throw e.getCause(); } catch (IOException e1) { throw new ModuleLoadException(e1); } catch (RuntimeException | Error | ModuleLoadException e1) { throw e1; } catch (Throwable t) { throw new UndeclaredThrowableException(t); } } } return null; } /** * Parse a {@code module.xml} file and return the corresponding module specification. * * @param identifier the identifier to load * @param delegateLoader the delegate module loader to use for module specifications * @param roots the repository root paths to search * @return the module specification * @throws IOException if reading the module file failed * @throws ModuleLoadException if creating the module specification failed (e.g. due to a parse error) * @deprecated Use {@link #parseModuleXmlFile(String, ModuleLoader, File...)} instead. */ @Deprecated public static ModuleSpec parseModuleXmlFile(final ModuleIdentifier identifier, final ModuleLoader delegateLoader, final File... roots) throws IOException, ModuleLoadException { return parseModuleXmlFile(identifier.toString(), delegateLoader, roots); } /** * Parse a {@code module.xml} file and return the corresponding module specification. * * @param name the name of the module to load * @param delegateLoader the delegate module loader to use for module specifications * @param roots the repository root paths to search * @return the module specification * @throws IOException if reading the module file failed * @throws ModuleLoadException if creating the module specification failed (e.g. due to a parse error) */ public static ModuleSpec parseModuleXmlFile(final String name, final ModuleLoader delegateLoader, final File... roots) throws IOException, ModuleLoadException { return parseModuleXmlFile(ModuleXmlParser.ResourceRootFactory.getDefault(), name, delegateLoader, roots); } static ModuleSpec parseModuleXmlFile(final ModuleXmlParser.ResourceRootFactory factory, final String name, final ModuleLoader delegateLoader, final File... roots) throws IOException, ModuleLoadException { final String child = PathUtils.basicModuleNameToPath(name); if (child == null) { return null; // not valid, so not found } for (File root : roots) { File file = new File(root, child); File moduleXml = new File(file, MODULE_FILE); if (moduleXml.exists()) { final ModuleSpec spec = ModuleXmlParser.parseModuleXml(factory, delegateLoader, name, file, moduleXml); if (spec == null) break; return spec; } } return null; } private static final Path MODULE_FILE_PATH = new File(MODULE_FILE).toPath(); private static final Predicate ITER_FILTER = new Predicate() { public boolean test(final Path path) { final Path fileName = path.getFileName(); return fileName != null && fileName.equals(MODULE_FILE_PATH); } }; public Iterator iterateModules(final String baseName, final boolean recursive, final ModuleLoader delegateLoader) { return new Iterator() { private final Iterator rootIter = Arrays.asList(repoRoots).iterator(); private final Set found = new HashSet<>(); private Path rootPath; private Iterator pathIter; private String next; public boolean hasNext() { while (next == null) { while (pathIter == null) { if (! rootIter.hasNext()) { return false; } Path path = rootIter.next().toPath(); rootPath = path; if (baseName != null && ! baseName.isEmpty()) { path = path.resolve(PathUtils.basicModuleNameToPath(baseName)); } try { pathIter = Files.walk(path, recursive ? Integer.MAX_VALUE : 1).filter(ITER_FILTER).iterator(); } catch (IOException ignored) { pathIter = null; continue; } } if (pathIter.hasNext()) { final Path nextPath = pathIter.next(); if (nextPath.getParent() == null) { continue; } try (InputStream stream = Files.newInputStream(nextPath)) { final ModuleSpec moduleSpec = ModuleXmlParser.parseModuleXml(ModuleXmlParser.ResourceRootFactory.getDefault(), nextPath.getParent().toString(), stream, nextPath.toString(), delegateLoader, (String)null); this.next = moduleSpec.getName(); if (found.add(this.next)) { return true; } this.next = null; } catch (IOException | ModuleLoadException e) { // ignore } } else { pathIter = null; } } return true; } public String next() { if (! hasNext()) throw new NoSuchElementException(); try { return next; } finally { next = null; } } }; } public String toString() { final StringBuilder b = new StringBuilder(); b.append("local module finder @").append(Integer.toHexString(hashCode())).append(" (roots: "); final int repoRootsLength = repoRoots.length; for (int i = 0; i < repoRootsLength; i++) { final File root = repoRoots[i]; b.append(root); if (i != repoRootsLength - 1) { b.append(','); } } b.append(')'); return b.toString(); } /** * Close this module loader and release all backing files. Note that subsequent load attempts will fail with an * error after this method is called. */ public void close() { final List list = this.resourceLoaderList; final ArrayList toClose; synchronized (list) { if (list.size() == 1 && list.get(0) == TERMINATED_MARKER) { return; } toClose = new ArrayList<>(list); list.clear(); list.add(TERMINATED_MARKER); } for (ResourceLoader resourceLoader : toClose) { safeClose(resourceLoader); } } private static void safeClose(AutoCloseable closeable) { if (closeable != null) try { closeable.close(); } catch (Throwable ignored) {} } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy