/*
* 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.lang.reflect.UndeclaredThrowableException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
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 <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
* @author <a href="mailto:ropalka@redhat.com">Richard Opalka</a>
*/
public final class LocalModuleFinder implements ModuleFinder {
private static final File[] NO_FILES = new File[0];
private final File[] repoRoots;
private final PathFilter pathFilter;
private final AccessControlContext accessControlContext;
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();
}
/**
* 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.
* <p>
* This is equivalent to a call to {@link LocalModuleFinder#LocalModuleFinder(boolean) LocalModuleFinder(true)}.
* </p>
*/
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;
}
private static String toPathString(final String moduleName) {
return moduleName.replace('.', File.separatorChar);
}
private static String toLegacyPathString(final String moduleName) {
final ModuleIdentifier moduleIdentifier = ModuleIdentifier.fromString(moduleName);
final String name = moduleIdentifier.getName();
final String slot = moduleIdentifier.getSlot();
final StringBuilder builder = new StringBuilder(name.length() + slot.length() + 2);
builder.append(name.replace('.', File.separatorChar));
builder.append(File.separatorChar).append(slot);
return builder.toString();
}
public ModuleSpec findModule(final String name, final ModuleLoader delegateLoader) throws ModuleLoadException {
final String child1 = toPathString(name);
final String child2 = toLegacyPathString(name);
final PathFilter pathFilter = this.pathFilter;
if (pathFilter.accept(child1 + "/") || pathFilter.accept(child2 + "/")) {
try {
return doPrivileged((PrivilegedExceptionAction<ModuleSpec>) () -> parseModuleXmlFile(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 {
final String child1 = toPathString(name);
final String child2 = toLegacyPathString(name);
for (File root : roots) {
File file = new File(root, child1);
File moduleXml = new File(file, MODULE_FILE);
if (! moduleXml.exists()) {
file = new File(root, child2);
moduleXml = new File(file, MODULE_FILE);
}
if (moduleXml.exists()) {
final ModuleSpec spec = ModuleXmlParser.parseModuleXml(delegateLoader, name, file, moduleXml);
if (spec == null) break;
return spec;
}
}
return 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();
}
}