org.eclipse.jetty.start.BaseHome Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.start;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import org.eclipse.jetty.start.config.CommandLineConfigSource;
import org.eclipse.jetty.start.config.ConfigSource;
import org.eclipse.jetty.start.config.ConfigSources;
import org.eclipse.jetty.start.config.DirConfigSource;
import org.eclipse.jetty.start.config.JettyBaseConfigSource;
import org.eclipse.jetty.start.config.JettyHomeConfigSource;
/**
* File access for ${jetty.home}
, ${jetty.base}
, directories.
*
* By default, both ${jetty.home}
and ${jetty.base}
are the same directory, but they can point at different directories.
*
* The ${jetty.home}
directory is where the main Jetty binaries and default configuration is housed.
*
* The ${jetty.base}
directory is where the execution specific configuration and webapps are obtained from.
*/
public class BaseHome
{
public static class SearchDir
{
private Path dir;
private String name;
public SearchDir(String name)
{
this.name = name;
}
public Path getDir()
{
return dir;
}
public Path resolve(Path subpath)
{
return dir.resolve(subpath);
}
public Path resolve(String subpath)
{
return dir.resolve(FS.separators(subpath));
}
public SearchDir setDir(Path path)
{
if (path != null)
{
this.dir = path.toAbsolutePath();
}
return this;
}
public SearchDir setDir(String path)
{
if (path != null)
{
return setDir(FS.toPath(path));
}
return this;
}
public String toShortForm(Path path)
{
Path relative = dir.relativize(path);
return String.format("${%s}%s%s", name, FS.separator(), relative);
}
}
public static final String JETTY_BASE = "jetty.base";
public static final String JETTY_HOME = "jetty.home";
private static final EnumSet SEARCH_VISIT_OPTIONS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
private static final int MAX_SEARCH_DEPTH = Integer.getInteger("org.eclipse.jetty.start.searchDepth", 10);
private final ConfigSources sources;
private final Path homeDir;
private final Path baseDir;
public BaseHome() throws IOException
{
this(new String[0]);
}
public BaseHome(String[] cmdLine) throws IOException
{
this(new CommandLineConfigSource(cmdLine));
}
public BaseHome(CommandLineConfigSource cmdLineSource) throws IOException
{
sources = new ConfigSources();
sources.add(cmdLineSource);
this.homeDir = cmdLineSource.getHomePath();
this.baseDir = cmdLineSource.getBasePath();
// TODO this is cyclic construction as start log uses BaseHome, but BaseHome constructor
// calls other constructors that log. This appears to be a workable sequence.
StartLog.getInstance().initialize(this, cmdLineSource);
sources.add(new JettyBaseConfigSource(cmdLineSource.getBasePath()));
sources.add(new JettyHomeConfigSource(cmdLineSource.getHomePath()));
System.setProperty(JETTY_HOME, homeDir.toAbsolutePath().toString());
System.setProperty(JETTY_BASE, baseDir.toAbsolutePath().toString());
}
public BaseHome(ConfigSources sources)
{
this.sources = sources;
Path home = null;
Path base = null;
for (ConfigSource source : sources)
{
if (source instanceof CommandLineConfigSource)
{
CommandLineConfigSource cmdline = (CommandLineConfigSource)source;
home = cmdline.getHomePath();
base = cmdline.getBasePath();
}
else if (source instanceof JettyBaseConfigSource)
{
base = ((JettyBaseConfigSource)source).getDir();
}
else if (source instanceof JettyHomeConfigSource)
{
home = ((JettyHomeConfigSource)source).getDir();
}
}
Objects.requireNonNull(home, "jetty.home cannot be null");
this.homeDir = home;
this.baseDir = (base != null) ? base : home;
System.setProperty(JETTY_HOME, homeDir.toAbsolutePath().toString());
System.setProperty(JETTY_BASE, baseDir.toAbsolutePath().toString());
}
public String getBase()
{
if (baseDir == null)
{
return null;
}
return baseDir.toString();
}
public Path getBasePath()
{
return baseDir;
}
/**
* Create a {@link Path} reference to some content in "${jetty.base}"
*
* @param path the path to reference
* @return the file reference
*/
public Path getBasePath(String path)
{
return baseDir.resolve(path).normalize().toAbsolutePath();
}
public ConfigSources getConfigSources()
{
return this.sources;
}
public String getHome()
{
return homeDir.toString();
}
public Path getHomePath()
{
return homeDir;
}
/**
* Get a specific path reference.
*
* Path references are searched based on the config source search order.
*
* - If provided path is an absolute reference., and exists, return that reference
* - If exists relative to
${jetty.base}
, return that reference
* - If exists relative to and
include-jetty-dir
locations, return that reference
* - If exists relative to
${jetty.home}
, return that reference
* - Return standard {@link Path} reference obtained from {@link java.nio.file.FileSystem#getPath(String, String...)} (no exists check performed)
*
*
* @param path the path to get.
* @return the path reference.
*/
public Path getPath(final String path)
{
Path apath = FS.toPath(path);
if (apath.isAbsolute())
{
if (FS.exists(apath))
{
return apath;
}
}
for (ConfigSource source : sources)
{
if (source instanceof DirConfigSource)
{
DirConfigSource dirsource = (DirConfigSource)source;
Path file = dirsource.getDir().resolve(apath);
if (FS.exists(file))
{
return file;
}
}
}
// Finally, as an anonymous path
return FS.toPath(path);
}
/**
* Search specified Path with pattern and return hits
*
* @param dir the path to a directory to start search from
* @param searchDepth the number of directories deep to perform the search
* @param pattern the raw pattern to use for the search (must be relative)
* @return the list of Paths found
* @throws IOException if unable to search the path
*/
public List getPaths(Path dir, int searchDepth, String pattern) throws IOException
{
if (PathMatchers.isAbsolute(pattern))
{
throw new RuntimeException("Pattern cannot be absolute: " + pattern);
}
List hits = new ArrayList<>();
if (FS.isValidDirectory(dir))
{
PathMatcher matcher = PathMatchers.getMatcher(pattern);
PathFinder finder = new PathFinder();
finder.setFileMatcher(matcher);
finder.setBase(dir);
finder.setIncludeDirsInResults(true);
Files.walkFileTree(dir, SEARCH_VISIT_OPTIONS, searchDepth, finder);
hits.addAll(finder.getHits());
Collections.sort(hits, new NaturalSort.Paths());
}
return hits;
}
/**
* Get a List of {@link Path}s from a provided pattern.
*
* Resolution Steps:
*
* - If the pattern starts with "regex:" or "glob:" then a standard {@link PathMatcher} is built using
* {@link java.nio.file.FileSystem#getPathMatcher(String)} as a file search.
* - If pattern starts with a known filesystem root (using information from {@link java.nio.file.FileSystem#getRootDirectories()}) then this is assumed to
* be a absolute file system pattern.
* - All other patterns are treated as relative to BaseHome information:
*
* - Search ${jetty.home} first
* - Search ${jetty.base} for overrides
*
*
*
*
* Pattern examples:
*
* lib/logging/*.jar
* - Relative pattern, not recursive, search
${jetty.home}
then ${jetty.base}
for lib/logging/*.jar content
*
* lib/**/*-dev.jar
* - Relative pattern, recursive search
${jetty.home}
then ${jetty.base}
for files under lib
ending in
* -dev.jar
*
* etc/jetty.xml
* - Relative pattern, no glob, search for
${jetty.home}/etc/jetty.xml
then ${jetty.base}/etc/jetty.xml
*
* glob:/opt/app/common/*-corp.jar
* - PathMapper pattern, glob, search
/opt/app/common/
for *-corp.jar
*
*
*
*
* Notes:
*
* - FileSystem case sensitivity is implementation specific (eg: linux is case-sensitive, windows is case-insensitive).
* See {@link java.nio.file.FileSystem#getPathMatcher(String)} for more details
* - Pattern slashes are implementation neutral (use '/' always and you'll be fine)
* - Recursive searching is limited to 30 levels deep (not configurable)
* - File System loops are detected and skipped
*
*
* @param pattern the pattern to search.
* @return the collection of paths found
* @throws IOException if error during search operation
*/
public List getPaths(String pattern) throws IOException
{
StartLog.debug("getPaths('%s')", pattern);
List hits = new ArrayList<>();
if (PathMatchers.isAbsolute(pattern))
{
// Perform absolute path pattern search
// The root to start search from
Path root = PathMatchers.getSearchRoot(pattern);
// The matcher for file hits
PathMatcher matcher = PathMatchers.getMatcher(pattern);
if (FS.isValidDirectory(root))
{
PathFinder finder = new PathFinder();
finder.setIncludeDirsInResults(true);
finder.setFileMatcher(matcher);
finder.setBase(root);
Files.walkFileTree(root, SEARCH_VISIT_OPTIONS, MAX_SEARCH_DEPTH, finder);
hits.addAll(finder.getHits());
}
}
else
{
// Perform relative path pattern search
Path relativePath = PathMatchers.getSearchRoot(pattern);
PathMatcher matcher = PathMatchers.getMatcher(pattern);
PathFinder finder = new PathFinder();
finder.setIncludeDirsInResults(true);
finder.setFileMatcher(matcher);
// walk config sources backwards ...
ListIterator iter = sources.reverseListIterator();
while (iter.hasPrevious())
{
ConfigSource source = iter.previous();
if (source instanceof DirConfigSource)
{
DirConfigSource dirsource = (DirConfigSource)source;
Path dir = dirsource.getDir();
Path deepDir = dir.resolve(relativePath);
if (FS.isValidDirectory(deepDir))
{
finder.setBase(dir);
Files.walkFileTree(deepDir, SEARCH_VISIT_OPTIONS, MAX_SEARCH_DEPTH, finder);
}
}
}
hits.addAll(finder.getHits());
}
Collections.sort(hits, new NaturalSort.Paths());
return hits;
}
public boolean isBaseDifferent()
{
return homeDir.compareTo(baseDir) != 0;
}
/**
* Replace/Shorten arbitrary path with property strings "${jetty.home}"
or "${jetty.base}"
where appropriate.
*
* @param path the path to shorten
* @return the potentially shortened path
*/
public String toShortForm(final Path path)
{
Path apath = path.toAbsolutePath();
for (ConfigSource source : sources)
{
if (source instanceof DirConfigSource)
{
DirConfigSource dirsource = (DirConfigSource)source;
Path dir = dirsource.getDir();
if (apath.startsWith(dir))
{
if (dirsource.isPropertyBased())
{
Path relative = dir.relativize(apath);
return String.format("%s%s%s", dirsource.getId(), FS.separator(), relative.toString());
}
else
{
return apath.toString();
}
}
}
}
return apath.toString();
}
/**
* Replace/Shorten arbitrary path with property strings "${jetty.home}"
or "${jetty.base}"
where appropriate.
*
* @param path the path to shorten
* @return the potentially shortened path
*/
public String toShortForm(final String path)
{
if ((path == null) || (path.charAt(0) == '<'))
{
return path;
}
return toShortForm(FS.toPath(path));
}
}