From 8878567139d41c92b32cd9e013cfcdd64c995e66 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 22 Nov 2024 15:01:24 -0600 Subject: [PATCH 01/22] Initial pass at implementation of EnvironmentContextProvider --- .../org/eclipse/jetty/deploy/AppProvider.java | 8 +- .../deploy/providers/ContextProvider.java | 13 + .../providers/EnvironmentContextProvider.java | 780 ++++++++++++++++++ .../deploy/providers/ScanningAppProvider.java | 67 +- .../org/eclipse/jetty/server/Deployable.java | 5 + .../src/main/config/etc/jetty-ee10-deploy.xml | 86 +- .../jetty/ee10/webapp/WebAppContext.java | 36 + .../src/main/config/etc/jetty-ee11-deploy.xml | 89 +- .../jetty/ee11/webapp/WebAppContext.java | 36 + .../src/main/config/etc/jetty-ee8-deploy.xml | 85 +- .../src/main/config/etc/jetty-ee9-deploy.xml | 88 +- .../jetty/ee9/webapp/WebAppContext.java | 35 + 12 files changed, 1020 insertions(+), 308 deletions(-) create mode 100644 jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java index a49fcc70dd54..e65e07e3920b 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java @@ -42,8 +42,12 @@ public interface AppProvider extends LifeCycle ContextHandler createContextHandler(App app) throws Exception; /** - * * @return The name of the {@link org.eclipse.jetty.util.component.Environment} this provider is for. + * @deprecated not used by all AppProviders, no generic replacement provided. */ - String getEnvironmentName(); + @Deprecated(forRemoval = true, since = "12.1.0") + default String getEnvironmentName() + { + return ""; + } } diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index dc2c0aecbe55..b58a5c39a402 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -138,6 +138,8 @@ public boolean accept(File dir, String name) } } + private String _environmentName; + public ContextProvider() { super(); @@ -145,6 +147,17 @@ public ContextProvider() setScanInterval(0); } + @SuppressWarnings("removal") // only to suppress deprecation in interface, still needed here + public String getEnvironmentName() + { + return _environmentName; + } + + public void setEnvironmentName(String environmentName) + { + _environmentName = environmentName; + } + public Map getProperties() { return _properties; diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java new file mode 100644 index 000000000000..4fa08049f033 --- /dev/null +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java @@ -0,0 +1,780 @@ +// +// ======================================================================== +// 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.deploy.providers; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.io.IOResources; +import org.eclipse.jetty.server.Deployable; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.FileID; +import org.eclipse.jetty.util.Loader; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.Environment; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; +import org.eclipse.jetty.xml.XmlConfiguration; +import org.slf4j.LoggerFactory; + +/** + * The webapps directory scanning provider. + *

+ * This provider scans one or more directories (typically "webapps") for contexts to + * deploy, which may be:

+ *

+ * To avoid double deployments and allow flexibility of the content of the scanned directories, the provider + * implements some heuristics to ignore some files found in the scans:

+ *

For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and + * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". + * The properties will be initialized with: + *

+ *

+ */ +@ManagedObject("Provider for start-up deployment of webapps based on presence in directory") +public class EnvironmentContextProvider extends ScanningAppProvider +{ + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(EnvironmentContextProvider.class); + + public EnvironmentContextProvider() + { + super(); + setFilenameFilter(new Filter()); + setScanInterval(0); + } + + private static Map asProperties(Attributes attributes) + { + Map props = new HashMap<>(); + attributes.getAttributeNameSet().stream() + .map((name) -> + { + // undo old prefixed entries + if (name.startsWith(Deployable.ATTRIBUTE_PREFIX)) + return name.substring(Deployable.ATTRIBUTE_PREFIX.length()); + else + return name; + }) + .forEach((name) -> props.put(name, Objects.toString(attributes.getAttribute(name)))); + return props; + } + + @Override + public ContextHandler createContextHandler(final App app) throws Exception + { + Environment environment = Environment.get(app.getEnvironmentName()); + + if (environment == null) + { + LOG.warn("Environment [{}] is not available for app [{}]. The available environments are: {}", + app.getEnvironmentName(), + app, + Environment.getAll().stream() + .map(Environment::getName) + .collect(Collectors.joining(",", "[", "]")) + ); + return null; + } + + if (LOG.isDebugEnabled()) + LOG.debug("createContextHandler {} in {}", app, environment); + + ClassLoader old = Thread.currentThread().getContextClassLoader(); + try + { + Thread.currentThread().setContextClassLoader(environment.getClassLoader()); + + // Create de-aliased file + Path path = app.getPath().toRealPath(); + if (!Files.exists(path)) + throw new IllegalStateException("App resource does not exist " + path); + + // prepare app attributes to use for app deployment + Attributes appAttributes = initAttributes(environment, app); + + Object context = null; + + // check if there is a specific ContextHandler type to create set in the + // properties associated with the webapp. If there is, we create it _before_ + // applying the environment xml file. + String contextHandlerClassName = (String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); + if (contextHandlerClassName != null) + context = Class.forName(contextHandlerClassName).getDeclaredConstructor().newInstance(); + + // Collect the optional environment context xml files. + // Order them according to the name of their property key names. + List sortedEnvXmlPaths = appAttributes.getAttributeNameSet() + .stream() + .filter(k -> k.startsWith(Deployable.ENVIRONMENT_XML)) + .map(k -> Path.of((String)appAttributes.getAttribute(k))) + .sorted() + .toList(); + + // apply each environment context xml file + for (Path envXmlPath : sortedEnvXmlPaths) + { + if (LOG.isDebugEnabled()) + LOG.debug("Applying environment specific context file {}", envXmlPath); + context = applyXml(context, envXmlPath, environment, appAttributes); + } + + // Handle a context XML file + if (FileID.isXml(path)) + { + context = applyXml(context, path, environment, appAttributes); + + // Look for the contextHandler itself + ContextHandler contextHandler = null; + if (context instanceof ContextHandler c) + contextHandler = c; + else if (context instanceof Supplier supplier) + { + Object nestedContext = supplier.get(); + if (nestedContext instanceof ContextHandler c) + contextHandler = c; + } + if (contextHandler == null) + throw new IllegalStateException("Unknown context type of " + context); + + return contextHandler; + } + // Otherwise it must be a directory or an archive + else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) + { + throw new IllegalStateException("unable to create ContextHandler for " + app); + } + + // Build the web application if necessary + if (context == null) + { + contextHandlerClassName = (String)environment.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); + if (StringUtil.isBlank(contextHandlerClassName)) + throw new IllegalStateException("No ContextHandler classname for " + app); + Class contextHandlerClass = Loader.loadClass(contextHandlerClassName); + if (contextHandlerClass == null) + throw new IllegalStateException("Unknown ContextHandler class " + contextHandlerClassName + " for " + app); + + context = contextHandlerClass.getDeclaredConstructor().newInstance(); + } + + //set a backup value for the path to the war in case it hasn't already been set + appAttributes.setAttribute(Deployable.WAR, path.toString()); + return initializeContextHandler(context, path, appAttributes); + } + finally + { + Thread.currentThread().setContextClassLoader(old); + } + } + + @Deprecated + public Map getProperties(Environment environment) + { + return asProperties(environment); + } + + public void loadProperties(Environment environment, Path path) throws IOException + { + try (InputStream inputStream = Files.newInputStream(path)) + { + loadProperties(environment, inputStream); + } + } + + public void loadProperties(Environment environment, Resource resource) throws IOException + { + try (InputStream inputStream = IOResources.asInputStream(resource)) + { + loadProperties(environment, inputStream); + } + } + + public void loadPropertiesFromString(Environment environment, String path) throws IOException + { + loadProperties(environment, Path.of(path)); + } + + protected Object applyXml(Object context, Path xml, Environment environment, Attributes attributes) throws Exception + { + if (!FileID.isXml(xml)) + return null; + + XmlConfiguration xmlc = new XmlConfiguration(ResourceFactory.of(this).newResource(xml), null, asProperties(attributes)) + { + @Override + public void initializeDefaults(Object context) + { + super.initializeDefaults(context); + EnvironmentContextProvider.this.initializeContextHandler(context, xml, attributes); + } + }; + + xmlc.getIdMap().put("Environment", environment.getName()); + xmlc.setJettyStandardIdsAndProperties(getDeploymentManager().getServer(), xml); + + // Put all Environment attributes into XMLC as properties that can be used. + attributes.getAttributeNameSet() + .stream() + .filter(k -> !k.startsWith("jetty.home") && + !k.startsWith("jetty.base") && + !k.startsWith("jetty.webapps")) + .forEach(k -> + { + String v = Objects.toString(attributes.getAttribute(k)); + xmlc.getProperties().put(k, v); + }); + + // Run configure against appropriate classloader. + ClassLoader xmlClassLoader = getXmlClassLoader(environment, xml); + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(xmlClassLoader); + + try + { + // Create or configure the context + if (context == null) + return xmlc.configure(); + + return xmlc.configure(context); + } + finally + { + Thread.currentThread().setContextClassLoader(oldClassLoader); + } + } + + /** + * Return a ClassLoader that can load a {@link Environment#CORE} based webapp + * that is entirely defined within the {@code webapps/} directory. + * + *

+ * The resulting ClassLoader consists of the following entries: + *

    + *
  1. The java archive {@code .jar}
  2. + *
  3. The java archives {@code .d/lib/*.jar}
  4. + *
  5. The directory {@code .d/classes/}
  6. + *
+ *

+ * + * @param path to XML defining this webapp, must be absolute, and cannot be in root directory of drive. + * filename of XML will be used to determine the {@code } of the other entries in this + * ClassLoader. + * @return the classloader for this CORE environment webapp. + * @throws IOException if unable to apply to create classloader. + */ + protected ClassLoader findCoreContextClassLoader(Path path) throws IOException + { + Path webapps = path.getParent(); + String basename = FileID.getBasename(path); + List urls = new ArrayList<>(); + + // Is there a matching jar file? + // TODO: both files can be there depending on FileSystem, is this still sane? + // TODO: what about other capitalization? eg: ".Jar" ? + Path contextJar = webapps.resolve(basename + ".jar"); + if (!Files.exists(contextJar)) + contextJar = webapps.resolve(basename + ".JAR"); + if (Files.exists(contextJar)) + urls.add(contextJar.toUri().toURL()); + + // Is there a matching lib directory? + Path libDir = webapps.resolve(basename + ".d/lib"); + if (Files.exists(libDir) && Files.isDirectory(libDir)) + { + try (Stream paths = Files.list(libDir)) + { + paths.filter(FileID::isJavaArchive) + .map(Path::toUri) + .forEach(uri -> + { + try + { + urls.add(uri.toURL()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + }); + } + } + + // Is there a matching lib directory? + Path classesDir = webapps.resolve(basename + ".d/classes"); + if (Files.exists(classesDir) && Files.isDirectory(libDir)) + urls.add(classesDir.toUri().toURL()); + + if (LOG.isDebugEnabled()) + LOG.debug("Core classloader for {}", urls); + + if (urls.isEmpty()) + return null; + return new URLClassLoader(urls.toArray(new URL[0]), Environment.CORE.getClassLoader()); + } + + protected ContextHandler initializeContextHandler(Object context, Path path, Attributes attributes) + { + if (LOG.isDebugEnabled()) + LOG.debug("initializeContextHandler {}", context); + // find the ContextHandler + ContextHandler contextHandler; + if (context instanceof ContextHandler handler) + contextHandler = handler; + else if (Supplier.class.isAssignableFrom(context.getClass())) + { + @SuppressWarnings("unchecked") + Supplier provider = (Supplier)context; + contextHandler = provider.get(); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("Not a context {}", context); + return null; + } + + assert contextHandler != null; + + initializeContextPath(contextHandler, path); + + if (Files.isDirectory(path)) + { + ResourceFactory resourceFactory = ResourceFactory.of(contextHandler); + contextHandler.setBaseResource(resourceFactory.newResource(path)); + } + + // pass through properties as attributes directly + attributes.getAttributeNameSet().stream() + .filter((name) -> name.startsWith(Deployable.ATTRIBUTE_PREFIX)) + .map((name) -> name.substring(Deployable.ATTRIBUTE_PREFIX.length())) + .forEach((name) -> contextHandler.setAttribute(name, attributes.getAttribute(name))); + + String contextPath = (String)attributes.getAttribute(Deployable.CONTEXT_PATH); + if (StringUtil.isNotBlank(contextPath)) + contextHandler.setContextPath(contextPath); + + if (context instanceof Deployable deployable) + deployable.initializeDefaults(attributes); + + return contextHandler; + } + + protected void initializeContextPath(ContextHandler context, Path path) + { + // Strip any 3 char extension from non directories + String basename = FileID.getBasename(path); + String contextPath = basename; + + // special case of archive (or dir) named "root" is / context + if (contextPath.equalsIgnoreCase("root")) + { + contextPath = "/"; + } + // handle root with virtual host form + else if (StringUtil.asciiStartsWithIgnoreCase(contextPath, "root-")) + { + int dash = contextPath.indexOf('-'); + String virtual = contextPath.substring(dash + 1); + context.setVirtualHosts(Arrays.asList(virtual.split(","))); + contextPath = "/"; + } + + // Ensure "/" is Prepended to all context paths. + if (contextPath.charAt(0) != '/') + contextPath = "/" + contextPath; + + // Set the display name and context Path + context.setDisplayName(basename); + context.setContextPath(contextPath); + } + + protected boolean isDeployable(Path path) + { + String basename = FileID.getBasename(path); + + //is the file that changed a directory? + if (Files.isDirectory(path)) + { + // deploy if there is not a .xml or .war file of the same basename? + return !Files.exists(path.getParent().resolve(basename + ".xml")) && + !Files.exists(path.getParent().resolve(basename + ".XML")) && + !Files.exists(path.getParent().resolve(basename + ".war")) && + !Files.exists(path.getParent().resolve(basename + ".WAR")); + } + + // deploy if it is a .war and there is not a .xml for of the same basename + if (FileID.isWebArchive(path)) + { + // if a .xml file exists for it + return !Files.exists(path.getParent().resolve(basename + ".xml")) && + !Files.exists(path.getParent().resolve(basename + ".XML")); + } + + // otherwise only deploy an XML + return FileID.isXml(path); + } + + @Override + protected void pathAdded(Path path) throws Exception + { + if (isDeployable(path)) + super.pathAdded(path); + } + + @Override + protected void pathChanged(Path path) throws Exception + { + if (isDeployable(path)) + super.pathChanged(path); + } + + /** + * Get the ClassLoader appropriate for applying Jetty XML. + * @param environment the environment to use + * @param xml the path to the XML + * @return the appropriate ClassLoader. + * @throws IOException if unable to create the ClassLoader + */ + private ClassLoader getXmlClassLoader(Environment environment, Path xml) throws IOException + { + if (Environment.CORE.equals(environment)) + { + // this XML belongs to a CORE deployment. + return findCoreContextClassLoader(xml); + } + else + { + return environment.getClassLoader(); + } + } + + private Attributes initAttributes(Environment environment, App app) throws IOException + { + Attributes attributes = new Attributes.Mapped(); + + // Start appAttributes with Attributes from Environment + environment.getAttributeNameSet().forEach((key) -> + attributes.setAttribute(key, environment.getAttribute(key))); + + // TODO: double check if an empty environment name makes sense. Will the default environment name? + String env = app.getEnvironmentName(); + + if (StringUtil.isNotBlank(env)) + { + // Load environment specific properties files + Path parent = app.getPath().getParent(); + Properties envProps = loadEnvironmentProperties(parent, env); + + envProps.stringPropertyNames().forEach( + k -> attributes.setAttribute(k, envProps.getProperty(k)) + ); + } + + // Overlay the app properties + app.getProperties().forEach(attributes::setAttribute); + + return attributes; + } + + /** + * Load all of the {@link Environment} specific {@code [-].properties} files + * found in the directory provided. + * + *

+ * All found properties files are first sorted by filename, then loaded one by one into + * a single {@link Properties} instance. + *

+ * + * @param directory the directory to load environment properties from. + * @param env the environment name + */ + private Properties loadEnvironmentProperties(Path directory, String env) throws IOException + { + Properties props = new Properties(); + List envPropertyFiles = new ArrayList<>(); + + // Get all environment specific properties files for this environment, + // order them according to the lexical ordering of the filenames + try (Stream paths = Files.list(directory)) + { + envPropertyFiles = paths.filter(Files::isRegularFile) + .map(directory::relativize) + .filter(p -> + { + String name = p.getName(0).toString(); + if (!name.endsWith(".properties")) + return false; + if (!name.startsWith(env)) + return false; + return true; + }).sorted().toList(); + } + + if (LOG.isDebugEnabled()) + LOG.debug("Environment property files {}", envPropertyFiles); + + // Load each *.properties file + for (Path file : envPropertyFiles) + { + Path resolvedFile = directory.resolve(file); + if (Files.exists(resolvedFile)) + { + Properties tmp = new Properties(); + try (InputStream stream = Files.newInputStream(resolvedFile)) + { + tmp.load(stream); + //put each property into our substitution pool + tmp.stringPropertyNames().forEach(k -> props.put(k, tmp.getProperty(k))); + } + } + } + + return props; + } + + private void loadProperties(Environment environment, InputStream inputStream) throws IOException + { + Properties props = new Properties(); + props.load(inputStream); + props.stringPropertyNames().forEach((name) -> + environment.setAttribute(name, props.getProperty(name))); + } + + /** + * Builder of a deployment configuration for a specific {@link Environment}. + * + *

+ * Results in {@link Attributes} for {@link Environment} containing the + * deployment configuration (as {@link Deployable} keys) that is applied to all deployable + * apps belonging to that {@link Environment}. + *

+ */ + public static class EnvBuilder + { + // Using setters in this class to allow jetty-xml + // syntax to skip setting of an environment attribute if property is unset, + // allowing the in code values to be same defaults as they are in embedded-jetty. + + private final Environment environment; + + public EnvBuilder(String name) + { + environment = Environment.ensure(name); + } + + /** + * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} attribute. + * + * @param configurations The configuration class names as a comma separated list + * @see Deployable#CONFIGURATION_CLASSES + */ + public void setConfigurationClasses(String configurations) + { + setConfigurationClasses(StringUtil.isBlank(configurations) ? null : configurations.split(",")); + } + + /** + * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} property. + * + * @param configurations The configuration class names. + * @see Deployable#CONFIGURATION_CLASSES + */ + public void setConfigurationClasses(String[] configurations) + { + if (configurations == null) + environment.removeAttribute(Deployable.CONFIGURATION_CLASSES); + else + environment.setAttribute(Deployable.CONFIGURATION_CLASSES, configurations); + } + + /** + * This is equivalent to setting the {@link Deployable#CONTAINER_SCAN_JARS} property. + * + * @param pattern The regex pattern to use when bytecode scanning container jars + * @see Deployable#CONTAINER_SCAN_JARS + */ + public void setContainerScanJarPattern(String pattern) + { + environment.setAttribute(Deployable.CONTAINER_SCAN_JARS, pattern); + } + + /** + * The name of the class that this environment uses to create {@link ContextHandler} + * instances (can be class that implements {@code java.util.function.Supplier} + * as well). + * + * @param classname the classname for this environment's context deployable. + * @see Deployable#CONTEXT_HANDLER_CLASS + */ + public void setContextHandlerClass(String classname) + { + environment.setAttribute(Deployable.CONTEXT_HANDLER_CLASS, classname); + } + + /** + * Set the defaultsDescriptor. + * This is equivalent to setting the {@link Deployable#DEFAULTS_DESCRIPTOR} attribute. + * + * @param defaultsDescriptor the defaultsDescriptor to set + * @see Deployable#DEFAULTS_DESCRIPTOR + */ + public void setDefaultsDescriptor(String defaultsDescriptor) + { + environment.setAttribute(Deployable.DEFAULTS_DESCRIPTOR, defaultsDescriptor); + } + + /** + * This is equivalent to setting the {@link Deployable#EXTRACT_WARS} attribute. + * + * @param extractWars the extractWars to set + * @see Deployable#EXTRACT_WARS + */ + public void setExtractWars(boolean extractWars) + { + environment.setAttribute(Deployable.EXTRACT_WARS, extractWars); + } + + /** + * This is equivalent to setting the {@link Deployable#PARENT_LOADER_PRIORITY} attribute. + * + * @param parentLoaderPriority the parentLoaderPriority to set + * @see Deployable#PARENT_LOADER_PRIORITY + */ + public void setParentLoaderPriority(boolean parentLoaderPriority) + { + environment.setAttribute(Deployable.PARENT_LOADER_PRIORITY, parentLoaderPriority); + } + + /** + * This is equivalent to setting the {@link Deployable#SCI_EXCLUSION_PATTERN} property. + * + * @param pattern The regex pattern to exclude ServletContainerInitializers from executing + * @see Deployable#SCI_EXCLUSION_PATTERN + */ + public void setServletContainerInitializerExclusionPattern(String pattern) + { + environment.setAttribute(Deployable.SCI_EXCLUSION_PATTERN, pattern); + } + + /** + * This is equivalent to setting the {@link Deployable#SCI_ORDER} property. + * + * @param order The ordered list of ServletContainerInitializer classes to run + * @see Deployable#SCI_ORDER + */ + public void setServletContainerInitializerOrder(String order) + { + environment.setAttribute(Deployable.SCI_ORDER, order); + } + + /** + * This is equivalent to setting the {@link Deployable#WEBINF_SCAN_JARS} property. + * + * @param pattern The regex pattern to use when bytecode scanning web-inf jars + * @see Deployable#WEBINF_SCAN_JARS + */ + public void setWebInfScanJarPattern(String pattern) + { + environment.setAttribute(Deployable.WEBINF_SCAN_JARS, pattern); + } + } + + public class Filter implements FilenameFilter + { + @Override + public boolean accept(File dir, String name) + { + if (dir == null || !dir.canRead()) + return false; + + // Accept XML files and WARs + if (FileID.isXml(name) || FileID.isWebArchive(name)) + return true; + + Path path = dir.toPath().resolve(name); + + // Ignore any other file that are not directories + if (!Files.isDirectory(path)) + return false; + + // Don't deploy monitored resources + if (getMonitoredResources().stream().map(Resource::getPath).anyMatch(path::equals)) + return false; + + // Ignore hidden directories + if (name.startsWith(".")) + return false; + + String lowerName = name.toLowerCase(Locale.ENGLISH); + + // is it a nominated config directory + if (lowerName.endsWith(".d")) + return false; + + // ignore source control directories + if ("cvs".equals(lowerName) || "cvsroot".equals(lowerName)) + return false; + + // ignore directories that have sibling war or XML file + if (Files.exists(dir.toPath().resolve(name + ".war")) || + Files.exists(dir.toPath().resolve(name + ".WAR")) || + Files.exists(dir.toPath().resolve(name + ".xml")) || + Files.exists(dir.toPath().resolve(name + ".XML"))) + return false; + + return true; + } + } +} diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java index 01d395d5699c..283190714090 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java @@ -29,15 +29,12 @@ import org.eclipse.jetty.deploy.App; import org.eclipse.jetty.deploy.AppProvider; import org.eclipse.jetty.deploy.DeploymentManager; -import org.eclipse.jetty.server.Deployable; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.Scanner; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedOperation; import org.eclipse.jetty.util.component.ContainerLifeCycle; -import org.eclipse.jetty.util.component.Environment; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; @@ -58,7 +55,6 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements private int _scanInterval = 10; private Scanner _scanner; private boolean _useRealPaths; - private String _environmentName; private boolean _deferInitialScan = false; private final Scanner.DiscreteListener _scannerListener = new Scanner.DiscreteListener() @@ -93,17 +89,6 @@ protected ScanningAppProvider(FilenameFilter filter) installBean(_appMap); } - @Override - public String getEnvironmentName() - { - return _environmentName; - } - - public void setEnvironmentName(String environmentName) - { - _environmentName = environmentName; - } - /** * @return True if the real path of the scanned files should be used for deployment. */ @@ -149,40 +134,7 @@ protected App createApp(Path path) App app = new App(_deploymentManager, this, path); if (LOG.isDebugEnabled()) LOG.debug("{} creating {}", this, app); - - String defaultEnvironmentName = _deploymentManager.getDefaultEnvironmentName(); - - String environmentName = app.getEnvironmentName(); - if (StringUtil.isBlank(environmentName) && StringUtil.isNotBlank(defaultEnvironmentName)) - { - environmentName = defaultEnvironmentName; - app.getProperties().put(Deployable.ENVIRONMENT, environmentName); - if (LOG.isDebugEnabled()) - LOG.debug("{} default environment for {}", this, app); - } - - if (StringUtil.isNotBlank(environmentName)) - { - // If the app specifies the environment for this provider, then this deployer will deploy it. - if (environmentName.equalsIgnoreCase(getEnvironmentName())) - { - if (LOG.isDebugEnabled()) - LOG.debug("{} created {}", this, app); - return app; - } - - // If we are the default provider then we may warn - if (getEnvironmentName().equalsIgnoreCase(defaultEnvironmentName)) - { - // if the app specified an environment name, then produce warning if there is no provider for it. - if (!_deploymentManager.hasAppProviderFor(environmentName)) - LOG.warn("No AppProvider with environment {} for {}", environmentName, app); - return null; - } - } - - LOG.warn("{} no environment for {}, ignoring", this, app); - return null; + return app; } @Override @@ -190,21 +142,10 @@ protected void doStart() throws Exception { if (LOG.isDebugEnabled()) LOG.debug("{}.doStart()", this.getClass().getSimpleName()); - if (_monitored.size() == 0) - throw new IllegalStateException("No configuration dir specified"); - if (_environmentName == null) - { - List nonCore = Environment.getAll().stream().filter(environment -> !environment.equals(Environment.CORE)).toList(); - if (nonCore.size() != 1) - throw new IllegalStateException("No environment configured"); - _environmentName = nonCore.get(0).getName(); - } - - Environment environment = Environment.get(_environmentName); - if (environment == null) - throw new IllegalStateException("Unknown environment " + _environmentName); + if (_monitored.isEmpty()) + throw new IllegalStateException("No monitored dir specified"); - LOG.info("Deployment monitor {} in {} at intervals {}s", getEnvironmentName(), _monitored, getScanInterval()); + LOG.info("Deployment monitor in {} at intervals {}s", _monitored, getScanInterval()); List files = new ArrayList<>(); for (Resource resource : _monitored) { diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java index d08275f8cccb..947b2668a74c 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java @@ -18,6 +18,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jetty.util.Attributes; + /** * Interface that can be implemented by ContextHandlers within Environments to allow configuration * to be passed from the DeploymentManager without dependencies on the Deployment module itself. @@ -67,5 +69,8 @@ public interface Deployable String WAR = "jetty.deploy.war"; String WEBINF_SCAN_JARS = "jetty.deploy.webInfScanJarPattern"; + // TODO: should we deprecate this one? void initializeDefaults(Map properties); + + void initializeDefaults(Attributes attributes); } diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml b/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml index 868a0e51462c..0c973bf2822e 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml +++ b/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml @@ -1,65 +1,31 @@ - + - - - org.eclipse.jetty.deploy.DeploymentManager - - - - contextHandlerClass - - - - - - - - ee10 - - - - - - - - - - - - - jetty.deploy.defaultsDescriptorPath - jetty.deploy.defaultsDescriptor - - /etc/webdefault-ee10.xml - - - - - - - - - - - - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - - - - - - - - - - - - + + ee10 + + + + + + + /etc/webdefault-ee10.xml + + jetty.deploy.defaultsDescriptor + jetty.deploy.defaultsDescriptorPath + + + + + + + + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + + + + + diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java index 0a4d5ab71f5e..3bb3e7fb406d 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java +++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java @@ -52,6 +52,7 @@ import org.eclipse.jetty.server.Deployable; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.ClassMatcher; import org.eclipse.jetty.util.ExceptionUtil; import org.eclipse.jetty.util.IO; @@ -243,6 +244,41 @@ public void initializeDefaults(Map properties) _defaultContextPath = true; } + @Override + public void initializeDefaults(Attributes attributes) + { + for (String keyName : attributes.getAttributeNameSet()) + { + Object value = attributes.getAttribute(keyName); + if (LOG.isDebugEnabled()) + LOG.debug("init {}: {}", keyName, value); + + switch (keyName) + { + case Deployable.WAR -> + { + if (getWar() == null) + setWar((String)value); + } + case Deployable.TEMP_DIR -> setTempDirectory(IO.asFile(value)); + case Deployable.CONFIGURATION_CLASSES -> setConfigurationClasses((String[])value); + case Deployable.CONTAINER_SCAN_JARS -> setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, value); + case Deployable.EXTRACT_WARS -> setExtractWAR((Boolean)value); + case Deployable.PARENT_LOADER_PRIORITY -> setParentLoaderPriority((Boolean)value); + case Deployable.WEBINF_SCAN_JARS -> setAttribute(MetaInfConfiguration.WEBINF_JAR_PATTERN, value); + case Deployable.DEFAULTS_DESCRIPTOR -> setDefaultsDescriptor((String)value); + case Deployable.SCI_EXCLUSION_PATTERN -> setAttribute("org.eclipse.jetty.containerInitializerExclusionPattern", value); + case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value); + default -> + { + if (LOG.isDebugEnabled() && value != null) + LOG.debug("unknown property {}={}", keyName, value); + } + } + } + _defaultContextPath = true; + } + public boolean isContextPathDefault() { return _defaultContextPath; diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml b/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml index 84e2ee4fdbd9..5ab08e473b7a 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml +++ b/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml @@ -1,68 +1,31 @@ - - - - - + - - - org.eclipse.jetty.deploy.DeploymentManager - - - - contextHandlerClass - - - - - - - - ee11 - - - - - - - - - - - - - jetty.deploy.defaultsDescriptorPath - jetty.deploy.defaultsDescriptor - - /etc/webdefault-ee11.xml - - - - - - - - - - - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - - - - - - - - - - - - + + ee11 + + + + + + + /etc/webdefault-ee11.xml + + jetty.deploy.defaultsDescriptor + jetty.deploy.defaultsDescriptorPath + + + + + + + + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + + + + + diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java index 73127bbc0389..98679e3bde27 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java +++ b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java @@ -52,6 +52,7 @@ import org.eclipse.jetty.server.Deployable; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.ClassMatcher; import org.eclipse.jetty.util.ExceptionUtil; import org.eclipse.jetty.util.IO; @@ -225,6 +226,41 @@ public void initializeDefaults(Map properties) _defaultContextPath = true; } + @Override + public void initializeDefaults(Attributes attributes) + { + for (String keyName : attributes.getAttributeNameSet()) + { + Object value = attributes.getAttribute(keyName); + if (LOG.isDebugEnabled()) + LOG.debug("init {}: {}", keyName, value); + + switch (keyName) + { + case Deployable.WAR -> + { + if (getWar() == null) + setWar((String)value); + } + case Deployable.TEMP_DIR -> setTempDirectory(IO.asFile(value)); + case Deployable.CONFIGURATION_CLASSES -> setConfigurationClasses((String[])value); + case Deployable.CONTAINER_SCAN_JARS -> setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, value); + case Deployable.EXTRACT_WARS -> setExtractWAR((Boolean)value); + case Deployable.PARENT_LOADER_PRIORITY -> setParentLoaderPriority((Boolean)value); + case Deployable.WEBINF_SCAN_JARS -> setAttribute(MetaInfConfiguration.WEBINF_JAR_PATTERN, value); + case Deployable.DEFAULTS_DESCRIPTOR -> setDefaultsDescriptor((String)value); + case Deployable.SCI_EXCLUSION_PATTERN -> setAttribute("org.eclipse.jetty.containerInitializerExclusionPattern", value); + case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value); + default -> + { + if (LOG.isDebugEnabled() && value != null) + LOG.debug("unknown property {}={}", keyName, value); + } + } + } + _defaultContextPath = true; + } + public boolean isContextPathDefault() { return _defaultContextPath; diff --git a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml index ef89f770e7b1..03ac84cca3cc 100644 --- a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml +++ b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml @@ -1,64 +1,31 @@ - + - - - org.eclipse.jetty.deploy.DeploymentManager - - - - contextHandlerClass - - - - - - - - ee8 - - - - - - - - - - - - - jetty.deploy.defaultsDescriptorPath - jetty.deploy.defaultsDescriptor - - /etc/webdefault-ee8.xml - - - - - - - - - - - .*/jetty-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-[^/]*\.jar|.*jsp.jstl-[^/]*\.jar - - - - - - - - - - - - - - - - + + ee8 + + + + + + + /etc/webdefault-ee8.xml + + jetty.deploy.defaultsDescriptor + jetty.deploy.defaultsDescriptorPath + + + + + + + + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + + + + + diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml index d4f60b484b11..adcf0e856a3c 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml +++ b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml @@ -1,65 +1,31 @@ - + - + - - - org.eclipse.jetty.deploy.DeploymentManager - - - - contextHandlerClass - - - - - - - - ee9 - - - - - - - - - - - - - jetty.deploy.defaultsDescriptorPath - jetty.deploy.defaultsDescriptor - - /etc/webdefault-ee9.xml - - - - - - - - - - - - .*/jetty-jakarta-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - - - - - - - - - - - - + + ee9 + + + + + + + /etc/webdefault-ee9.xml + + jetty.deploy.defaultsDescriptor + jetty.deploy.defaultsDescriptorPath + + + + + + + + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + + + + + diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java index 7ccf48b9e25d..89be9babf757 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java +++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java @@ -269,6 +269,41 @@ public void initializeDefaults(Map properties) _defaultContextPath = true; } + @Override + public void initializeDefaults(Attributes attributes) + { + for (String keyName : attributes.getAttributeNameSet()) + { + Object value = attributes.getAttribute(keyName); + if (LOG.isDebugEnabled()) + LOG.debug("init {}: {}", keyName, value); + + switch (keyName) + { + case Deployable.WAR -> + { + if (getWar() == null) + setWar((String)value); + } + case Deployable.TEMP_DIR -> setTempDirectory(IO.asFile(value)); + case Deployable.CONFIGURATION_CLASSES -> setConfigurationClasses((String[])value); + case Deployable.CONTAINER_SCAN_JARS -> setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, value); + case Deployable.EXTRACT_WARS -> setExtractWAR((Boolean)value); + case Deployable.PARENT_LOADER_PRIORITY -> setParentLoaderPriority((Boolean)value); + case Deployable.WEBINF_SCAN_JARS -> setAttribute(MetaInfConfiguration.WEBINF_JAR_PATTERN, value); + case Deployable.DEFAULTS_DESCRIPTOR -> setDefaultsDescriptor((String)value); + case Deployable.SCI_EXCLUSION_PATTERN -> setAttribute("org.eclipse.jetty.containerInitializerExclusionPattern", value); + case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value); + default -> + { + if (LOG.isDebugEnabled() && value != null) + LOG.debug("unknown property {}={}", keyName, value); + } + } + } + _defaultContextPath = true; + } + public boolean isContextPathDefault() { return _defaultContextPath; From 3a46e37e18a2070966c098eca4b99e005f59bdaa Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 22 Nov 2024 15:12:04 -0600 Subject: [PATCH 02/22] Fixing javadoc errors --- .../providers/EnvironmentContextProvider.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java index 4fa08049f033..f321803a8a96 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java @@ -51,17 +51,19 @@ /** * The webapps directory scanning provider. - *

- * This provider scans one or more directories (typically "webapps") for contexts to - * deploy, which may be:

    + *

    This provider scans one or more directories (typically "webapps") for contexts to + * deploy, which may be: + *

    + *
      *
    • A standard WAR file (must end in ".war")
    • *
    • A directory containing an expanded WAR file
    • *
    • A directory containing static content
    • *
    • An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance
    • *
    - *

    - * To avoid double deployments and allow flexibility of the content of the scanned directories, the provider - * implements some heuristics to ignore some files found in the scans:

      + *

      To avoid double deployments and allow flexibility of the content of the scanned directories, the provider + * implements some heuristics to ignore some files found in the scans: + *

      + *
        *
      • Hidden files (starting with {@code "."}) are ignored
      • *
      • Directories with names ending in {@code ".d"} are ignored
      • *
      • Property files with names ending in {@code ".properties"} are not deployed.
      • @@ -75,13 +77,13 @@ *

        For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". * The properties will be initialized with: + *

        *
          *
        • The properties set on the application via {@link App#getProperties()}
        • *
        • The app specific properties file {@code webapps/.properties}
        • *
        • The environment specific properties file {@code webapps/[-zzz].properties}
        • *
        • The {@link Attributes} from the {@link Environment}
        • *
        - *

        */ @ManagedObject("Provider for start-up deployment of webapps based on presence in directory") public class EnvironmentContextProvider extends ScanningAppProvider @@ -299,14 +301,12 @@ public void initializeDefaults(Object context) * Return a ClassLoader that can load a {@link Environment#CORE} based webapp * that is entirely defined within the {@code webapps/} directory. * - *

        - * The resulting ClassLoader consists of the following entries: + *

        The resulting ClassLoader consists of the following entries:

        *
          *
        1. The java archive {@code .jar}
        2. *
        3. The java archives {@code .d/lib/*.jar}
        4. *
        5. The directory {@code .d/classes/}
        6. *
        - *

        * * @param path to XML defining this webapp, must be absolute, and cannot be in root directory of drive. * filename of XML will be used to determine the {@code } of the other entries in this From c5c07c2fab4225344106e1be759d64b5c1c3df54 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 25 Nov 2024 13:04:28 -0600 Subject: [PATCH 03/22] Moving things around --- .../jetty/deploy/DeploymentManager.java | 6 ++++++ .../providers/EnvironmentContextProvider.java | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java index 5ce184b35e60..aa04ff8ea1b7 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java @@ -132,7 +132,9 @@ void setLifeCycleNode(Node node) * Get the default {@link Environment} name for deployed applications, which is * the maximal name when using the {@link Deployable#ENVIRONMENT_COMPARATOR}. * @return The default {@link Environment} name or null. + * @deprecated Moved to {@link org.eclipse.jetty.deploy.providers.EnvironmentContextProvider} */ + @Deprecated(since = "12.1.0", forRemoval = true) public String getDefaultEnvironmentName() { return _providers.stream() @@ -184,6 +186,10 @@ public void setAppProviders(Collection providers) } } + /** + * @deprecated No replacement. AppProvider interface no longer has getEnvironmentName. + */ + @Deprecated(since = "12.1.0", forRemoval = true) public boolean hasAppProviderFor(String environmentName) { return environmentName != null && getAppProviders().stream() diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java index f321803a8a96..3bd0039cd846 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java @@ -89,6 +89,7 @@ public class EnvironmentContextProvider extends ScanningAppProvider { private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(EnvironmentContextProvider.class); + private String defaultEnvironmentName; public EnvironmentContextProvider() { @@ -97,6 +98,23 @@ public EnvironmentContextProvider() setScanInterval(0); } + public String getDefaultEnvironmentName() + { + if (defaultEnvironmentName == null) + { + return Environment.getAll().stream() + .map(Environment::getName) + .max(Deployable.ENVIRONMENT_COMPARATOR) + .orElse(null); + } + return defaultEnvironmentName; + } + + public void setDefaultEnvironmentName(String name) + { + this.defaultEnvironmentName = name; + } + private static Map asProperties(Attributes attributes) { Map props = new HashMap<>(); From e0bdeb382149233edcecd46ffa076f6f773dbfe0 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 25 Nov 2024 13:10:50 -0600 Subject: [PATCH 04/22] Moving changes back into ContextProvider --- .../jetty/deploy/DeploymentManager.java | 3 +- .../deploy/providers/ContextProvider.java | 920 ++++++++++-------- .../providers/EnvironmentContextProvider.java | 798 --------------- .../src/main/config/etc/jetty-ee10-deploy.xml | 6 +- .../src/main/config/etc/jetty-ee11-deploy.xml | 6 +- .../src/main/config/etc/jetty-ee8-deploy.xml | 6 +- .../src/main/config/etc/jetty-ee9-deploy.xml | 6 +- 7 files changed, 550 insertions(+), 1195 deletions(-) delete mode 100644 jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java index aa04ff8ea1b7..dce0331884c4 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java @@ -32,6 +32,7 @@ import org.eclipse.jetty.deploy.graph.Edge; import org.eclipse.jetty.deploy.graph.Node; import org.eclipse.jetty.deploy.graph.Route; +import org.eclipse.jetty.deploy.providers.ContextProvider; import org.eclipse.jetty.server.Deployable; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; @@ -132,7 +133,7 @@ void setLifeCycleNode(Node node) * Get the default {@link Environment} name for deployed applications, which is * the maximal name when using the {@link Deployable#ENVIRONMENT_COMPARATOR}. * @return The default {@link Environment} name or null. - * @deprecated Moved to {@link org.eclipse.jetty.deploy.providers.EnvironmentContextProvider} + * @deprecated not used, replacement at {@link ContextProvider#getDefaultEnvironmentName()} */ @Deprecated(since = "12.1.0", forRemoval = true) public String getDefaultEnvironmentName() diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index b58a5c39a402..cfeaab2c55de 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -21,7 +21,6 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -39,10 +38,10 @@ import org.eclipse.jetty.server.Deployable; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.FileID; import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.Environment; import org.eclipse.jetty.util.resource.Resource; @@ -52,93 +51,45 @@ /** * The webapps directory scanning provider. - *

        - * This provider scans one or more directories (typically "webapps") for contexts to - * deploy, which may be:

          - *
        • A standard WAR file (must end in ".war")
        • - *
        • A directory containing an expanded WAR file
        • - *
        • A directory containing static content
        • - *
        • An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance
        • - *
        - *

        - * To avoid double deployments and allow flexibility of the content of the scanned directories, the provider - * implements some heuristics to ignore some files found in the scans:

          - *
        • Hidden files (starting with ".") are ignored
        • - *
        • Directories with names ending in ".d" are ignored
        • - *
        • Property files with names ending in ".properties" are not deployed.
        • - *
        • If a directory and a WAR file exist ( eg foo/ and foo.war) then the directory is assumed to be - * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)
        • - *
        • If a directory and a matching XML file exist ( eg foo/ and foo.xml) then the directory is assumed to be - * an unpacked WAR and only the XML is deployed (which may used the directory in it's configuration)
        • - *
        • If a WAR file and a matching XML exist (eg foo.war and foo.xml) then the WAR is assumed to - * be configured by the XML and only the XML is deployed. - *
        - *

        - * Only {@link App}s discovered that report {@link App#getEnvironmentName()} matching this providers - * {@link #getEnvironmentName()} will be deployed. - *

        - *

        For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and - * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". - * The properties will be initialized with:

          - *
        • The properties set on the application via {@link App#getProperties()}; otherwise:
        • - *
        • The properties set on this provider via {@link #getProperties()}
        • - *
        + * *

        This provider scans one or more directories (typically "webapps") for contexts to + * * deploy, which may be: + * *

        + * *
          + * *
        • A standard WAR file (must end in ".war")
        • + * *
        • A directory containing an expanded WAR file
        • + * *
        • A directory containing static content
        • + * *
        • An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance
        • + * *
        + * *

        To avoid double deployments and allow flexibility of the content of the scanned directories, the provider + * * implements some heuristics to ignore some files found in the scans: + * *

        + * *
          + * *
        • Hidden files (starting with {@code "."}) are ignored
        • + * *
        • Directories with names ending in {@code ".d"} are ignored
        • + * *
        • Property files with names ending in {@code ".properties"} are not deployed.
        • + * *
        • If a directory and a WAR file exist (eg: {@code foo/} and {@code foo.war}) then the directory is assumed to be + * * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)
        • + * *
        • If a directory and a matching XML file exist (eg: {@code foo/} and {@code foo.xml}) then the directory is assumed to be + * * an unpacked WAR and only the XML is deployed (which may used the directory in its configuration)
        • + * *
        • If a WAR file and a matching XML exist (eg: {@code foo.war} and {@code foo.xml}) then the WAR is assumed to + * * be configured by the XML and only the XML is deployed. + * *
        + * *

        For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and + * * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". + * * The properties will be initialized with: + * *

        + * *
          + * *
        • The properties set on the application via {@link App#getProperties()}
        • + * *
        • The app specific properties file {@code webapps/.properties}
        • + * *
        • The environment specific properties file {@code webapps/[-zzz].properties}
        • + * *
        • The {@link Attributes} from the {@link Environment}
        • + * *
        */ @ManagedObject("Provider for start-up deployment of webapps based on presence in directory") public class ContextProvider extends ScanningAppProvider { private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ContextProvider.class); - - private final Map _properties = new HashMap<>(); - - public class Filter implements FilenameFilter - { - @Override - public boolean accept(File dir, String name) - { - if (dir == null || !dir.canRead()) - return false; - - // Accept XML files and WARs - if (FileID.isXml(name) || FileID.isWebArchive(name)) - return true; - - Path path = dir.toPath().resolve(name); - - // Ignore any other file that are not directories - if (!Files.isDirectory(path)) - return false; - - // Don't deploy monitored resources - if (getMonitoredResources().stream().map(Resource::getPath).anyMatch(path::equals)) - return false; - - // Ignore hidden directories - if (name.startsWith(".")) - return false; - - String lowerName = name.toLowerCase(Locale.ENGLISH); - - // is it a nominated config directory - if (lowerName.endsWith(".d")) - return false; - - // ignore source control directories - if ("cvs".equals(lowerName) || "cvsroot".equals(lowerName)) - return false; - - // ignore directories that have sibling war or XML file - if (Files.exists(dir.toPath().resolve(name + ".war")) || - Files.exists(dir.toPath().resolve(name + ".WAR")) || - Files.exists(dir.toPath().resolve(name + ".xml")) || - Files.exists(dir.toPath().resolve(name + ".XML"))) - return false; - - return true; - } - } - - private String _environmentName; + private String defaultEnvironmentName; public ContextProvider() { @@ -147,203 +98,39 @@ public ContextProvider() setScanInterval(0); } - @SuppressWarnings("removal") // only to suppress deprecation in interface, still needed here - public String getEnvironmentName() - { - return _environmentName; - } - - public void setEnvironmentName(String environmentName) - { - _environmentName = environmentName; - } - - public Map getProperties() - { - return _properties; - } - - public void loadProperties(Resource resource) throws IOException - { - Properties props = new Properties(); - try (InputStream inputStream = IOResources.asInputStream(resource)) - { - props.load(inputStream); - props.forEach((key, value) -> _properties.put((String)key, (String)value)); - } - } - - public void loadPropertiesFromPath(Path path) throws IOException - { - Properties props = new Properties(); - try (InputStream inputStream = Files.newInputStream(path)) - { - props.load(inputStream); - props.forEach((key, value) -> _properties.put((String)key, (String)value)); - } - } - - public void loadPropertiesFromString(String path) throws IOException - { - loadPropertiesFromPath(Path.of(path)); - } - - /** - * Get the extractWars. - * This is equivalent to getting the {@link Deployable#EXTRACT_WARS} property. - * - * @return the extractWars - */ - @ManagedAttribute("extract war files") - public boolean isExtractWars() - { - return Boolean.parseBoolean(_properties.get(Deployable.EXTRACT_WARS)); - } - - /** - * Set the extractWars. - * This is equivalent to setting the {@link Deployable#EXTRACT_WARS} property. - * - * @param extractWars the extractWars to set - */ - public void setExtractWars(boolean extractWars) - { - _properties.put(Deployable.EXTRACT_WARS, Boolean.toString(extractWars)); - } - - /** - * Get the parentLoaderPriority. - * This is equivalent to getting the {@link Deployable#PARENT_LOADER_PRIORITY} property. - * - * @return the parentLoaderPriority - */ - @ManagedAttribute("parent classloader has priority") - public boolean isParentLoaderPriority() - { - return Boolean.parseBoolean(_properties.get(Deployable.PARENT_LOADER_PRIORITY)); - } - - /** - * Set the parentLoaderPriority. - * This is equivalent to setting the {@link Deployable#PARENT_LOADER_PRIORITY} property. - * - * @param parentLoaderPriority the parentLoaderPriority to set - */ - public void setParentLoaderPriority(boolean parentLoaderPriority) - { - _properties.put(Deployable.PARENT_LOADER_PRIORITY, Boolean.toString(parentLoaderPriority)); - } - - /** - * Get the defaultsDescriptor. - * This is equivalent to getting the {@link Deployable#DEFAULTS_DESCRIPTOR} property. - * - * @return the defaultsDescriptor - */ - @ManagedAttribute("default descriptor for webapps") - public String getDefaultsDescriptor() + private static Map asProperties(Attributes attributes) { - return _properties.get(Deployable.DEFAULTS_DESCRIPTOR); - } - - /** - * Set the defaultsDescriptor. - * This is equivalent to setting the {@link Deployable#DEFAULTS_DESCRIPTOR} property. - * - * @param defaultsDescriptor the defaultsDescriptor to set - */ - public void setDefaultsDescriptor(String defaultsDescriptor) - { - _properties.put(Deployable.DEFAULTS_DESCRIPTOR, defaultsDescriptor); - } - - /** - * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} property. - * - * @param configurations The configuration class names as a comma separated list - */ - public void setConfigurationClasses(String configurations) - { - setConfigurationClasses(StringUtil.isBlank(configurations) ? null : configurations.split(",")); - } - - /** - * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} property. - * - * @param configurations The configuration class names. - */ - public void setConfigurationClasses(String[] configurations) - { - _properties.put(Deployable.CONFIGURATION_CLASSES, (configurations == null) - ? null - : String.join(",", configurations)); + Map props = new HashMap<>(); + attributes.getAttributeNameSet().stream() + .map((name) -> + { + // undo old prefixed entries + if (name.startsWith(Deployable.ATTRIBUTE_PREFIX)) + return name.substring(Deployable.ATTRIBUTE_PREFIX.length()); + else + return name; + }) + .forEach((name) -> props.put(name, Objects.toString(attributes.getAttribute(name)))); + return props; } - /** - * This is equivalent to getting the {@link Deployable#CONFIGURATION_CLASSES} property. - * - * @return The configuration class names. - */ - @ManagedAttribute("configuration classes for webapps to be processed through") - public String[] getConfigurationClasses() + @Override + public ContextHandler createContextHandler(final App app) throws Exception { - String cc = _properties.get(Deployable.CONFIGURATION_CLASSES); - return cc == null ? new String[0] : cc.split(","); - } + Environment environment = Environment.get(app.getEnvironmentName()); - protected ContextHandler initializeContextHandler(Object context, Path path, Map properties) - { - if (LOG.isDebugEnabled()) - LOG.debug("initializeContextHandler {}", context); - // find the ContextHandler - ContextHandler contextHandler; - if (context instanceof ContextHandler handler) - contextHandler = handler; - else if (Supplier.class.isAssignableFrom(context.getClass())) + if (environment == null) { - @SuppressWarnings("unchecked") - Supplier provider = (Supplier)context; - contextHandler = provider.get(); - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Not a context {}", context); + LOG.warn("Environment [{}] is not available for app [{}]. The available environments are: {}", + app.getEnvironmentName(), + app, + Environment.getAll().stream() + .map(Environment::getName) + .collect(Collectors.joining(",", "[", "]")) + ); return null; } - assert contextHandler != null; - - initializeContextPath(contextHandler, path); - - if (Files.isDirectory(path)) - contextHandler.setBaseResource(ResourceFactory.of(this).newResource(path)); - - //TODO think of better way of doing this - //pass through properties as attributes directly - for (Map.Entry prop : properties.entrySet()) - { - String key = prop.getKey(); - String value = prop.getValue(); - if (key.startsWith(Deployable.ATTRIBUTE_PREFIX)) - contextHandler.setAttribute(key.substring(Deployable.ATTRIBUTE_PREFIX.length()), value); - } - - String contextPath = properties.get(Deployable.CONTEXT_PATH); - if (StringUtil.isNotBlank(contextPath)) - contextHandler.setContextPath(contextPath); - - if (context instanceof Deployable deployable) - deployable.initializeDefaults(properties); - - return contextHandler; - } - - @Override - public ContextHandler createContextHandler(final App app) throws Exception - { - Environment environment = Environment.get(app.getEnvironmentName()); - if (LOG.isDebugEnabled()) LOG.debug("createContextHandler {} in {}", app, environment); @@ -353,112 +140,43 @@ public ContextHandler createContextHandler(final App app) throws Exception Thread.currentThread().setContextClassLoader(environment.getClassLoader()); // Create de-aliased file - Path path = app.getPath().toRealPath().toAbsolutePath().toFile().getCanonicalFile().toPath(); + Path path = app.getPath().toRealPath(); if (!Files.exists(path)) throw new IllegalStateException("App resource does not exist " + path); - // prepare properties - Map properties = new HashMap<>(); - - //add in properties from start mechanism - properties.putAll(getProperties()); + // prepare app attributes to use for app deployment + Attributes appAttributes = initAttributes(environment, app); Object context = null; - //check if there is a specific ContextHandler type to create set in the - //properties associated with the webapp. If there is, we create it _before_ - //applying the environment xml file. - String contextHandlerClassName = app.getProperties().get(Deployable.CONTEXT_HANDLER_CLASS); + + // check if there is a specific ContextHandler type to create set in the + // properties associated with the webapp. If there is, we create it _before_ + // applying the environment xml file. + String contextHandlerClassName = (String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); if (contextHandlerClassName != null) context = Class.forName(contextHandlerClassName).getDeclaredConstructor().newInstance(); - //Add in environment-specific properties: - // allow multiple eeXX[-zzz].properties files, ordered lexically - // allow each to contain jetty.deploy.environmentXml[.zzzz] properties - // accumulate all properties for substitution purposes - // order all jetty.deploy.environmentXml[.zzzz] properties lexically - // apply the context xml files named by the ordered jetty.deploy.environmentXml[.zzzz] properties - String env = app.getEnvironmentName() == null ? "" : app.getEnvironmentName(); - - if (StringUtil.isNotBlank(env)) + // Collect the optional environment context xml files. + // Order them according to the name of their property key names. + List sortedEnvXmlPaths = appAttributes.getAttributeNameSet() + .stream() + .filter(k -> k.startsWith(Deployable.ENVIRONMENT_XML)) + .map(k -> Path.of((String)appAttributes.getAttribute(k))) + .sorted() + .toList(); + + // apply each environment context xml file + for (Path envXmlPath : sortedEnvXmlPaths) { - List envPropertyFiles = new ArrayList<>(); - Path parent = app.getPath().getParent(); - - //Get all environment specific properties files for this environment, - //order them according to the lexical ordering of the filenames - try (Stream paths = Files.list(parent)) - { - envPropertyFiles = paths.filter(Files::isRegularFile) - .map(p -> parent.relativize(p)) - .filter(p -> - { - String name = p.getName(0).toString(); - if (!name.endsWith(".properties")) - return false; - if (!name.startsWith(env)) - return false; - return true; - }).sorted().collect(Collectors.toList()); - } - if (LOG.isDebugEnabled()) - LOG.debug("Environment property files {}", envPropertyFiles); - - Map envXmlFilenameMap = new HashMap<>(); - for (Path file : envPropertyFiles) - { - Path resolvedFile = parent.resolve(file); - if (Files.exists(resolvedFile)) - { - Properties tmp = new Properties(); - try (InputStream stream = Files.newInputStream(resolvedFile)) - { - tmp.load(stream); - //put each property into our substitution pool - tmp.stringPropertyNames().forEach(k -> properties.put(k, tmp.getProperty(k))); - } - } - } - - //extract any properties that name environment context xml files - for (Map.Entry entry : properties.entrySet()) - { - String name = Objects.toString(entry.getKey(), ""); - if (name.startsWith(Deployable.ENVIRONMENT_XML)) - { - //ensure all environment context xml files are absolute paths - Path envXmlPath = Paths.get(entry.getValue().toString()); - if (!envXmlPath.isAbsolute()) - envXmlPath = getMonitoredDirResource().getPath().getParent().resolve(envXmlPath); - //accumulate all properties that name environment xml files so they can be ordered - envXmlFilenameMap.put(name, envXmlPath); - } - } - - //order the environment context xml files according to the name of their properties - List sortedEnvXmlProperties = envXmlFilenameMap.keySet().stream().sorted().toList(); - - //apply each environment context xml file - for (String property : sortedEnvXmlProperties) - { - Path envXmlPath = envXmlFilenameMap.get(property); - if (LOG.isDebugEnabled()) - LOG.debug("Applying environment specific context file {}", envXmlPath); - context = applyXml(context, envXmlPath, env, properties); - } + LOG.debug("Applying environment specific context file {}", envXmlPath); + context = applyXml(context, envXmlPath, environment, appAttributes); } - //add in properties specific to the deployable - properties.putAll(app.getProperties()); - // Handle a context XML file if (FileID.isXml(path)) { - ClassLoader coreContextClassLoader = Environment.CORE.equals(environment) ? findCoreContextClassLoader(path) : null; - if (coreContextClassLoader != null) - Thread.currentThread().setContextClassLoader(coreContextClassLoader); - - context = applyXml(context, path, env, properties); + context = applyXml(context, path, environment, appAttributes); // Look for the contextHandler itself ContextHandler contextHandler = null; @@ -473,10 +191,6 @@ else if (context instanceof Supplier supplier) if (contextHandler == null) throw new IllegalStateException("Unknown context type of " + context); - // Set the classloader if we have a coreContextClassLoader - if (coreContextClassLoader != null) - contextHandler.setClassLoader(coreContextClassLoader); - return contextHandler; } // Otherwise it must be a directory or an archive @@ -488,7 +202,7 @@ else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) // Build the web application if necessary if (context == null) { - contextHandlerClassName = (String)environment.getAttribute("contextHandlerClass"); + contextHandlerClassName = (String)environment.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); if (StringUtil.isBlank(contextHandlerClassName)) throw new IllegalStateException("No ContextHandler classname for " + app); Class contextHandlerClass = Loader.loadClass(contextHandlerClassName); @@ -499,8 +213,8 @@ else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) } //set a backup value for the path to the war in case it hasn't already been set - properties.put(Deployable.WAR, path.toString()); - return initializeContextHandler(context, path, properties); + appAttributes.setAttribute(Deployable.WAR, path.toString()); + return initializeContextHandler(context, path, appAttributes); } finally { @@ -508,36 +222,127 @@ else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) } } - protected Object applyXml(Object context, Path xml, String environment, Map properties) throws Exception + /** + * Get the default {@link Environment} name for discovered web applications that + * do not declare the {@link Environment} that they belong to. + * + *

        + * Falls back to {@link Environment#getAll()} list, and returns + * the first name returned after sorting with {@link Deployable#ENVIRONMENT_COMPARATOR} + *

        + * + * @return the default environment name. + */ + public String getDefaultEnvironmentName() + { + if (defaultEnvironmentName == null) + { + return Environment.getAll().stream() + .map(Environment::getName) + .max(Deployable.ENVIRONMENT_COMPARATOR) + .orElse(null); + } + return defaultEnvironmentName; + } + + public void setDefaultEnvironmentName(String name) + { + this.defaultEnvironmentName = name; + } + + @Deprecated + public Map getProperties(Environment environment) + { + return asProperties(environment); + } + + public void loadProperties(Environment environment, Path path) throws IOException + { + try (InputStream inputStream = Files.newInputStream(path)) + { + loadProperties(environment, inputStream); + } + } + + public void loadProperties(Environment environment, Resource resource) throws IOException + { + try (InputStream inputStream = IOResources.asInputStream(resource)) + { + loadProperties(environment, inputStream); + } + } + + public void loadPropertiesFromString(Environment environment, String path) throws IOException + { + loadProperties(environment, Path.of(path)); + } + + protected Object applyXml(Object context, Path xml, Environment environment, Attributes attributes) throws Exception { if (!FileID.isXml(xml)) return null; - XmlConfiguration xmlc = new XmlConfiguration(ResourceFactory.of(this).newResource(xml), null, properties) + XmlConfiguration xmlc = new XmlConfiguration(ResourceFactory.of(this).newResource(xml), null, asProperties(attributes)) { @Override public void initializeDefaults(Object context) { super.initializeDefaults(context); - ContextProvider.this.initializeContextHandler(context, xml, properties); + ContextProvider.this.initializeContextHandler(context, xml, attributes); } }; - xmlc.getIdMap().put("Environment", environment); + xmlc.getIdMap().put("Environment", environment.getName()); xmlc.setJettyStandardIdsAndProperties(getDeploymentManager().getServer(), xml); - // If it is a core context environment, then look for a classloader - ClassLoader coreContextClassLoader = Environment.CORE.equals(environment) ? findCoreContextClassLoader(xml) : null; - if (coreContextClassLoader != null) - Thread.currentThread().setContextClassLoader(coreContextClassLoader); + // Put all Environment attributes into XMLC as properties that can be used. + attributes.getAttributeNameSet() + .stream() + .filter(k -> !k.startsWith("jetty.home") && + !k.startsWith("jetty.base") && + !k.startsWith("jetty.webapps")) + .forEach(k -> + { + String v = Objects.toString(attributes.getAttribute(k)); + xmlc.getProperties().put(k, v); + }); - // Create or configure the context - if (context == null) - return xmlc.configure(); + // Run configure against appropriate classloader. + ClassLoader xmlClassLoader = getXmlClassLoader(environment, xml); + ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(xmlClassLoader); - return xmlc.configure(context); + try + { + // Create or configure the context + if (context == null) + return xmlc.configure(); + + return xmlc.configure(context); + } + finally + { + Thread.currentThread().setContextClassLoader(oldClassLoader); + } } + /** + * Return a ClassLoader that can load a {@link Environment#CORE} based webapp + * that is entirely defined within the {@code webapps/} directory. + * + *

        The resulting ClassLoader consists of the following entries:

        + *
          + *
        1. The java archive {@code .jar}
        2. + *
        3. The java archives {@code .d/lib/*.jar}
        4. + *
        5. The directory {@code .d/classes/}
        6. + *
        + * + * @param path to XML defining this webapp, must be absolute, and cannot be in root directory of drive. + * filename of XML will be used to determine the {@code } of the other entries in this + * ClassLoader. + * @return the classloader for this CORE environment webapp. + * @throws IOException if unable to apply to create classloader. + */ protected ClassLoader findCoreContextClassLoader(Path path) throws IOException { Path webapps = path.getParent(); @@ -545,6 +350,8 @@ protected ClassLoader findCoreContextClassLoader(Path path) throws IOException List urls = new ArrayList<>(); // Is there a matching jar file? + // TODO: both files can be there depending on FileSystem, is this still sane? + // TODO: what about other capitalization? eg: ".Jar" ? Path contextJar = webapps.resolve(basename + ".jar"); if (!Files.exists(contextJar)) contextJar = webapps.resolve(basename + ".JAR"); @@ -552,7 +359,7 @@ protected ClassLoader findCoreContextClassLoader(Path path) throws IOException urls.add(contextJar.toUri().toURL()); // Is there a matching lib directory? - Path libDir = webapps.resolve(basename + ".d" + path.getFileSystem().getSeparator() + "lib"); + Path libDir = webapps.resolve(basename + ".d/lib"); if (Files.exists(libDir) && Files.isDirectory(libDir)) { try (Stream paths = Files.list(libDir)) @@ -574,7 +381,7 @@ protected ClassLoader findCoreContextClassLoader(Path path) throws IOException } // Is there a matching lib directory? - Path classesDir = webapps.resolve(basename + ".d" + path.getFileSystem().getSeparator() + "classes"); + Path classesDir = webapps.resolve(basename + ".d/classes"); if (Files.exists(classesDir) && Files.isDirectory(libDir)) urls.add(classesDir.toUri().toURL()); @@ -586,6 +393,53 @@ protected ClassLoader findCoreContextClassLoader(Path path) throws IOException return new URLClassLoader(urls.toArray(new URL[0]), Environment.CORE.getClassLoader()); } + protected ContextHandler initializeContextHandler(Object context, Path path, Attributes attributes) + { + if (LOG.isDebugEnabled()) + LOG.debug("initializeContextHandler {}", context); + // find the ContextHandler + ContextHandler contextHandler; + if (context instanceof ContextHandler handler) + contextHandler = handler; + else if (Supplier.class.isAssignableFrom(context.getClass())) + { + @SuppressWarnings("unchecked") + Supplier provider = (Supplier)context; + contextHandler = provider.get(); + } + else + { + if (LOG.isDebugEnabled()) + LOG.debug("Not a context {}", context); + return null; + } + + assert contextHandler != null; + + initializeContextPath(contextHandler, path); + + if (Files.isDirectory(path)) + { + ResourceFactory resourceFactory = ResourceFactory.of(contextHandler); + contextHandler.setBaseResource(resourceFactory.newResource(path)); + } + + // pass through properties as attributes directly + attributes.getAttributeNameSet().stream() + .filter((name) -> name.startsWith(Deployable.ATTRIBUTE_PREFIX)) + .map((name) -> name.substring(Deployable.ATTRIBUTE_PREFIX.length())) + .forEach((name) -> contextHandler.setAttribute(name, attributes.getAttribute(name))); + + String contextPath = (String)attributes.getAttribute(Deployable.CONTEXT_PATH); + if (StringUtil.isNotBlank(contextPath)) + contextHandler.setContextPath(contextPath); + + if (context instanceof Deployable deployable) + deployable.initializeDefaults(attributes); + + return contextHandler; + } + protected void initializeContextPath(ContextHandler context, Path path) { // Strip any 3 char extension from non directories @@ -654,4 +508,302 @@ protected void pathChanged(Path path) throws Exception if (isDeployable(path)) super.pathChanged(path); } + + /** + * Get the ClassLoader appropriate for applying Jetty XML. + * @param environment the environment to use + * @param xml the path to the XML + * @return the appropriate ClassLoader. + * @throws IOException if unable to create the ClassLoader + */ + private ClassLoader getXmlClassLoader(Environment environment, Path xml) throws IOException + { + if (Environment.CORE.equals(environment)) + { + // this XML belongs to a CORE deployment. + return findCoreContextClassLoader(xml); + } + else + { + return environment.getClassLoader(); + } + } + + private Attributes initAttributes(Environment environment, App app) throws IOException + { + Attributes attributes = new Attributes.Mapped(); + + // Start appAttributes with Attributes from Environment + environment.getAttributeNameSet().forEach((key) -> + attributes.setAttribute(key, environment.getAttribute(key))); + + // TODO: double check if an empty environment name makes sense. Will the default environment name? + String env = app.getEnvironmentName(); + + if (StringUtil.isNotBlank(env)) + { + // Load environment specific properties files + Path parent = app.getPath().getParent(); + Properties envProps = loadEnvironmentProperties(parent, env); + + envProps.stringPropertyNames().forEach( + k -> attributes.setAttribute(k, envProps.getProperty(k)) + ); + } + + // Overlay the app properties + app.getProperties().forEach(attributes::setAttribute); + + return attributes; + } + + /** + * Load all of the {@link Environment} specific {@code [-].properties} files + * found in the directory provided. + * + *

        + * All found properties files are first sorted by filename, then loaded one by one into + * a single {@link Properties} instance. + *

        + * + * @param directory the directory to load environment properties from. + * @param env the environment name + */ + private Properties loadEnvironmentProperties(Path directory, String env) throws IOException + { + Properties props = new Properties(); + List envPropertyFiles = new ArrayList<>(); + + // Get all environment specific properties files for this environment, + // order them according to the lexical ordering of the filenames + try (Stream paths = Files.list(directory)) + { + envPropertyFiles = paths.filter(Files::isRegularFile) + .map(directory::relativize) + .filter(p -> + { + String name = p.getName(0).toString(); + if (!name.endsWith(".properties")) + return false; + if (!name.startsWith(env)) + return false; + return true; + }).sorted().toList(); + } + + if (LOG.isDebugEnabled()) + LOG.debug("Environment property files {}", envPropertyFiles); + + // Load each *.properties file + for (Path file : envPropertyFiles) + { + Path resolvedFile = directory.resolve(file); + if (Files.exists(resolvedFile)) + { + Properties tmp = new Properties(); + try (InputStream stream = Files.newInputStream(resolvedFile)) + { + tmp.load(stream); + //put each property into our substitution pool + tmp.stringPropertyNames().forEach(k -> props.put(k, tmp.getProperty(k))); + } + } + } + + return props; + } + + private void loadProperties(Environment environment, InputStream inputStream) throws IOException + { + Properties props = new Properties(); + props.load(inputStream); + props.stringPropertyNames().forEach((name) -> + environment.setAttribute(name, props.getProperty(name))); + } + + /** + * Builder of a deployment configuration for a specific {@link Environment}. + * + *

        + * Results in {@link Attributes} for {@link Environment} containing the + * deployment configuration (as {@link Deployable} keys) that is applied to all deployable + * apps belonging to that {@link Environment}. + *

        + */ + public static class EnvBuilder + { + // Using setters in this class to allow jetty-xml + // syntax to skip setting of an environment attribute if property is unset, + // allowing the in code values to be same defaults as they are in embedded-jetty. + + private final Environment environment; + + public EnvBuilder(String name) + { + environment = Environment.ensure(name); + } + + /** + * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} attribute. + * + * @param configurations The configuration class names as a comma separated list + * @see Deployable#CONFIGURATION_CLASSES + */ + public void setConfigurationClasses(String configurations) + { + setConfigurationClasses(StringUtil.isBlank(configurations) ? null : configurations.split(",")); + } + + /** + * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} property. + * + * @param configurations The configuration class names. + * @see Deployable#CONFIGURATION_CLASSES + */ + public void setConfigurationClasses(String[] configurations) + { + if (configurations == null) + environment.removeAttribute(Deployable.CONFIGURATION_CLASSES); + else + environment.setAttribute(Deployable.CONFIGURATION_CLASSES, configurations); + } + + /** + * This is equivalent to setting the {@link Deployable#CONTAINER_SCAN_JARS} property. + * + * @param pattern The regex pattern to use when bytecode scanning container jars + * @see Deployable#CONTAINER_SCAN_JARS + */ + public void setContainerScanJarPattern(String pattern) + { + environment.setAttribute(Deployable.CONTAINER_SCAN_JARS, pattern); + } + + /** + * The name of the class that this environment uses to create {@link ContextHandler} + * instances (can be class that implements {@code java.util.function.Supplier} + * as well). + * + * @param classname the classname for this environment's context deployable. + * @see Deployable#CONTEXT_HANDLER_CLASS + */ + public void setContextHandlerClass(String classname) + { + environment.setAttribute(Deployable.CONTEXT_HANDLER_CLASS, classname); + } + + /** + * Set the defaultsDescriptor. + * This is equivalent to setting the {@link Deployable#DEFAULTS_DESCRIPTOR} attribute. + * + * @param defaultsDescriptor the defaultsDescriptor to set + * @see Deployable#DEFAULTS_DESCRIPTOR + */ + public void setDefaultsDescriptor(String defaultsDescriptor) + { + environment.setAttribute(Deployable.DEFAULTS_DESCRIPTOR, defaultsDescriptor); + } + + /** + * This is equivalent to setting the {@link Deployable#EXTRACT_WARS} attribute. + * + * @param extractWars the extractWars to set + * @see Deployable#EXTRACT_WARS + */ + public void setExtractWars(boolean extractWars) + { + environment.setAttribute(Deployable.EXTRACT_WARS, extractWars); + } + + /** + * This is equivalent to setting the {@link Deployable#PARENT_LOADER_PRIORITY} attribute. + * + * @param parentLoaderPriority the parentLoaderPriority to set + * @see Deployable#PARENT_LOADER_PRIORITY + */ + public void setParentLoaderPriority(boolean parentLoaderPriority) + { + environment.setAttribute(Deployable.PARENT_LOADER_PRIORITY, parentLoaderPriority); + } + + /** + * This is equivalent to setting the {@link Deployable#SCI_EXCLUSION_PATTERN} property. + * + * @param pattern The regex pattern to exclude ServletContainerInitializers from executing + * @see Deployable#SCI_EXCLUSION_PATTERN + */ + public void setServletContainerInitializerExclusionPattern(String pattern) + { + environment.setAttribute(Deployable.SCI_EXCLUSION_PATTERN, pattern); + } + + /** + * This is equivalent to setting the {@link Deployable#SCI_ORDER} property. + * + * @param order The ordered list of ServletContainerInitializer classes to run + * @see Deployable#SCI_ORDER + */ + public void setServletContainerInitializerOrder(String order) + { + environment.setAttribute(Deployable.SCI_ORDER, order); + } + + /** + * This is equivalent to setting the {@link Deployable#WEBINF_SCAN_JARS} property. + * + * @param pattern The regex pattern to use when bytecode scanning web-inf jars + * @see Deployable#WEBINF_SCAN_JARS + */ + public void setWebInfScanJarPattern(String pattern) + { + environment.setAttribute(Deployable.WEBINF_SCAN_JARS, pattern); + } + } + + public class Filter implements FilenameFilter + { + @Override + public boolean accept(File dir, String name) + { + if (dir == null || !dir.canRead()) + return false; + + // Accept XML files and WARs + if (FileID.isXml(name) || FileID.isWebArchive(name)) + return true; + + Path path = dir.toPath().resolve(name); + + // Ignore any other file that are not directories + if (!Files.isDirectory(path)) + return false; + + // Don't deploy monitored resources + if (getMonitoredResources().stream().map(Resource::getPath).anyMatch(path::equals)) + return false; + + // Ignore hidden directories + if (name.startsWith(".")) + return false; + + String lowerName = name.toLowerCase(Locale.ENGLISH); + + // is it a nominated config directory + if (lowerName.endsWith(".d")) + return false; + + // ignore source control directories + if ("cvs".equals(lowerName) || "cvsroot".equals(lowerName)) + return false; + + // ignore directories that have sibling war or XML file + if (Files.exists(dir.toPath().resolve(name + ".war")) || + Files.exists(dir.toPath().resolve(name + ".WAR")) || + Files.exists(dir.toPath().resolve(name + ".xml")) || + Files.exists(dir.toPath().resolve(name + ".XML"))) + return false; + + return true; + } + } } diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java deleted file mode 100644 index 3bd0039cd846..000000000000 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/EnvironmentContextProvider.java +++ /dev/null @@ -1,798 +0,0 @@ -// -// ======================================================================== -// 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.deploy.providers; - -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.eclipse.jetty.deploy.App; -import org.eclipse.jetty.io.IOResources; -import org.eclipse.jetty.server.Deployable; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.handler.ContextHandler; -import org.eclipse.jetty.util.Attributes; -import org.eclipse.jetty.util.FileID; -import org.eclipse.jetty.util.Loader; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.annotation.ManagedObject; -import org.eclipse.jetty.util.component.Environment; -import org.eclipse.jetty.util.resource.Resource; -import org.eclipse.jetty.util.resource.ResourceFactory; -import org.eclipse.jetty.xml.XmlConfiguration; -import org.slf4j.LoggerFactory; - -/** - * The webapps directory scanning provider. - *

        This provider scans one or more directories (typically "webapps") for contexts to - * deploy, which may be: - *

        - *
          - *
        • A standard WAR file (must end in ".war")
        • - *
        • A directory containing an expanded WAR file
        • - *
        • A directory containing static content
        • - *
        • An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance
        • - *
        - *

        To avoid double deployments and allow flexibility of the content of the scanned directories, the provider - * implements some heuristics to ignore some files found in the scans: - *

        - *
          - *
        • Hidden files (starting with {@code "."}) are ignored
        • - *
        • Directories with names ending in {@code ".d"} are ignored
        • - *
        • Property files with names ending in {@code ".properties"} are not deployed.
        • - *
        • If a directory and a WAR file exist (eg: {@code foo/} and {@code foo.war}) then the directory is assumed to be - * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)
        • - *
        • If a directory and a matching XML file exist (eg: {@code foo/} and {@code foo.xml}) then the directory is assumed to be - * an unpacked WAR and only the XML is deployed (which may used the directory in its configuration)
        • - *
        • If a WAR file and a matching XML exist (eg: {@code foo.war} and {@code foo.xml}) then the WAR is assumed to - * be configured by the XML and only the XML is deployed. - *
        - *

        For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and - * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". - * The properties will be initialized with: - *

        - *
          - *
        • The properties set on the application via {@link App#getProperties()}
        • - *
        • The app specific properties file {@code webapps/.properties}
        • - *
        • The environment specific properties file {@code webapps/[-zzz].properties}
        • - *
        • The {@link Attributes} from the {@link Environment}
        • - *
        - */ -@ManagedObject("Provider for start-up deployment of webapps based on presence in directory") -public class EnvironmentContextProvider extends ScanningAppProvider -{ - private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(EnvironmentContextProvider.class); - private String defaultEnvironmentName; - - public EnvironmentContextProvider() - { - super(); - setFilenameFilter(new Filter()); - setScanInterval(0); - } - - public String getDefaultEnvironmentName() - { - if (defaultEnvironmentName == null) - { - return Environment.getAll().stream() - .map(Environment::getName) - .max(Deployable.ENVIRONMENT_COMPARATOR) - .orElse(null); - } - return defaultEnvironmentName; - } - - public void setDefaultEnvironmentName(String name) - { - this.defaultEnvironmentName = name; - } - - private static Map asProperties(Attributes attributes) - { - Map props = new HashMap<>(); - attributes.getAttributeNameSet().stream() - .map((name) -> - { - // undo old prefixed entries - if (name.startsWith(Deployable.ATTRIBUTE_PREFIX)) - return name.substring(Deployable.ATTRIBUTE_PREFIX.length()); - else - return name; - }) - .forEach((name) -> props.put(name, Objects.toString(attributes.getAttribute(name)))); - return props; - } - - @Override - public ContextHandler createContextHandler(final App app) throws Exception - { - Environment environment = Environment.get(app.getEnvironmentName()); - - if (environment == null) - { - LOG.warn("Environment [{}] is not available for app [{}]. The available environments are: {}", - app.getEnvironmentName(), - app, - Environment.getAll().stream() - .map(Environment::getName) - .collect(Collectors.joining(",", "[", "]")) - ); - return null; - } - - if (LOG.isDebugEnabled()) - LOG.debug("createContextHandler {} in {}", app, environment); - - ClassLoader old = Thread.currentThread().getContextClassLoader(); - try - { - Thread.currentThread().setContextClassLoader(environment.getClassLoader()); - - // Create de-aliased file - Path path = app.getPath().toRealPath(); - if (!Files.exists(path)) - throw new IllegalStateException("App resource does not exist " + path); - - // prepare app attributes to use for app deployment - Attributes appAttributes = initAttributes(environment, app); - - Object context = null; - - // check if there is a specific ContextHandler type to create set in the - // properties associated with the webapp. If there is, we create it _before_ - // applying the environment xml file. - String contextHandlerClassName = (String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); - if (contextHandlerClassName != null) - context = Class.forName(contextHandlerClassName).getDeclaredConstructor().newInstance(); - - // Collect the optional environment context xml files. - // Order them according to the name of their property key names. - List sortedEnvXmlPaths = appAttributes.getAttributeNameSet() - .stream() - .filter(k -> k.startsWith(Deployable.ENVIRONMENT_XML)) - .map(k -> Path.of((String)appAttributes.getAttribute(k))) - .sorted() - .toList(); - - // apply each environment context xml file - for (Path envXmlPath : sortedEnvXmlPaths) - { - if (LOG.isDebugEnabled()) - LOG.debug("Applying environment specific context file {}", envXmlPath); - context = applyXml(context, envXmlPath, environment, appAttributes); - } - - // Handle a context XML file - if (FileID.isXml(path)) - { - context = applyXml(context, path, environment, appAttributes); - - // Look for the contextHandler itself - ContextHandler contextHandler = null; - if (context instanceof ContextHandler c) - contextHandler = c; - else if (context instanceof Supplier supplier) - { - Object nestedContext = supplier.get(); - if (nestedContext instanceof ContextHandler c) - contextHandler = c; - } - if (contextHandler == null) - throw new IllegalStateException("Unknown context type of " + context); - - return contextHandler; - } - // Otherwise it must be a directory or an archive - else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) - { - throw new IllegalStateException("unable to create ContextHandler for " + app); - } - - // Build the web application if necessary - if (context == null) - { - contextHandlerClassName = (String)environment.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); - if (StringUtil.isBlank(contextHandlerClassName)) - throw new IllegalStateException("No ContextHandler classname for " + app); - Class contextHandlerClass = Loader.loadClass(contextHandlerClassName); - if (contextHandlerClass == null) - throw new IllegalStateException("Unknown ContextHandler class " + contextHandlerClassName + " for " + app); - - context = contextHandlerClass.getDeclaredConstructor().newInstance(); - } - - //set a backup value for the path to the war in case it hasn't already been set - appAttributes.setAttribute(Deployable.WAR, path.toString()); - return initializeContextHandler(context, path, appAttributes); - } - finally - { - Thread.currentThread().setContextClassLoader(old); - } - } - - @Deprecated - public Map getProperties(Environment environment) - { - return asProperties(environment); - } - - public void loadProperties(Environment environment, Path path) throws IOException - { - try (InputStream inputStream = Files.newInputStream(path)) - { - loadProperties(environment, inputStream); - } - } - - public void loadProperties(Environment environment, Resource resource) throws IOException - { - try (InputStream inputStream = IOResources.asInputStream(resource)) - { - loadProperties(environment, inputStream); - } - } - - public void loadPropertiesFromString(Environment environment, String path) throws IOException - { - loadProperties(environment, Path.of(path)); - } - - protected Object applyXml(Object context, Path xml, Environment environment, Attributes attributes) throws Exception - { - if (!FileID.isXml(xml)) - return null; - - XmlConfiguration xmlc = new XmlConfiguration(ResourceFactory.of(this).newResource(xml), null, asProperties(attributes)) - { - @Override - public void initializeDefaults(Object context) - { - super.initializeDefaults(context); - EnvironmentContextProvider.this.initializeContextHandler(context, xml, attributes); - } - }; - - xmlc.getIdMap().put("Environment", environment.getName()); - xmlc.setJettyStandardIdsAndProperties(getDeploymentManager().getServer(), xml); - - // Put all Environment attributes into XMLC as properties that can be used. - attributes.getAttributeNameSet() - .stream() - .filter(k -> !k.startsWith("jetty.home") && - !k.startsWith("jetty.base") && - !k.startsWith("jetty.webapps")) - .forEach(k -> - { - String v = Objects.toString(attributes.getAttribute(k)); - xmlc.getProperties().put(k, v); - }); - - // Run configure against appropriate classloader. - ClassLoader xmlClassLoader = getXmlClassLoader(environment, xml); - ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(xmlClassLoader); - - try - { - // Create or configure the context - if (context == null) - return xmlc.configure(); - - return xmlc.configure(context); - } - finally - { - Thread.currentThread().setContextClassLoader(oldClassLoader); - } - } - - /** - * Return a ClassLoader that can load a {@link Environment#CORE} based webapp - * that is entirely defined within the {@code webapps/} directory. - * - *

        The resulting ClassLoader consists of the following entries:

        - *
          - *
        1. The java archive {@code .jar}
        2. - *
        3. The java archives {@code .d/lib/*.jar}
        4. - *
        5. The directory {@code .d/classes/}
        6. - *
        - * - * @param path to XML defining this webapp, must be absolute, and cannot be in root directory of drive. - * filename of XML will be used to determine the {@code } of the other entries in this - * ClassLoader. - * @return the classloader for this CORE environment webapp. - * @throws IOException if unable to apply to create classloader. - */ - protected ClassLoader findCoreContextClassLoader(Path path) throws IOException - { - Path webapps = path.getParent(); - String basename = FileID.getBasename(path); - List urls = new ArrayList<>(); - - // Is there a matching jar file? - // TODO: both files can be there depending on FileSystem, is this still sane? - // TODO: what about other capitalization? eg: ".Jar" ? - Path contextJar = webapps.resolve(basename + ".jar"); - if (!Files.exists(contextJar)) - contextJar = webapps.resolve(basename + ".JAR"); - if (Files.exists(contextJar)) - urls.add(contextJar.toUri().toURL()); - - // Is there a matching lib directory? - Path libDir = webapps.resolve(basename + ".d/lib"); - if (Files.exists(libDir) && Files.isDirectory(libDir)) - { - try (Stream paths = Files.list(libDir)) - { - paths.filter(FileID::isJavaArchive) - .map(Path::toUri) - .forEach(uri -> - { - try - { - urls.add(uri.toURL()); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - }); - } - } - - // Is there a matching lib directory? - Path classesDir = webapps.resolve(basename + ".d/classes"); - if (Files.exists(classesDir) && Files.isDirectory(libDir)) - urls.add(classesDir.toUri().toURL()); - - if (LOG.isDebugEnabled()) - LOG.debug("Core classloader for {}", urls); - - if (urls.isEmpty()) - return null; - return new URLClassLoader(urls.toArray(new URL[0]), Environment.CORE.getClassLoader()); - } - - protected ContextHandler initializeContextHandler(Object context, Path path, Attributes attributes) - { - if (LOG.isDebugEnabled()) - LOG.debug("initializeContextHandler {}", context); - // find the ContextHandler - ContextHandler contextHandler; - if (context instanceof ContextHandler handler) - contextHandler = handler; - else if (Supplier.class.isAssignableFrom(context.getClass())) - { - @SuppressWarnings("unchecked") - Supplier provider = (Supplier)context; - contextHandler = provider.get(); - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Not a context {}", context); - return null; - } - - assert contextHandler != null; - - initializeContextPath(contextHandler, path); - - if (Files.isDirectory(path)) - { - ResourceFactory resourceFactory = ResourceFactory.of(contextHandler); - contextHandler.setBaseResource(resourceFactory.newResource(path)); - } - - // pass through properties as attributes directly - attributes.getAttributeNameSet().stream() - .filter((name) -> name.startsWith(Deployable.ATTRIBUTE_PREFIX)) - .map((name) -> name.substring(Deployable.ATTRIBUTE_PREFIX.length())) - .forEach((name) -> contextHandler.setAttribute(name, attributes.getAttribute(name))); - - String contextPath = (String)attributes.getAttribute(Deployable.CONTEXT_PATH); - if (StringUtil.isNotBlank(contextPath)) - contextHandler.setContextPath(contextPath); - - if (context instanceof Deployable deployable) - deployable.initializeDefaults(attributes); - - return contextHandler; - } - - protected void initializeContextPath(ContextHandler context, Path path) - { - // Strip any 3 char extension from non directories - String basename = FileID.getBasename(path); - String contextPath = basename; - - // special case of archive (or dir) named "root" is / context - if (contextPath.equalsIgnoreCase("root")) - { - contextPath = "/"; - } - // handle root with virtual host form - else if (StringUtil.asciiStartsWithIgnoreCase(contextPath, "root-")) - { - int dash = contextPath.indexOf('-'); - String virtual = contextPath.substring(dash + 1); - context.setVirtualHosts(Arrays.asList(virtual.split(","))); - contextPath = "/"; - } - - // Ensure "/" is Prepended to all context paths. - if (contextPath.charAt(0) != '/') - contextPath = "/" + contextPath; - - // Set the display name and context Path - context.setDisplayName(basename); - context.setContextPath(contextPath); - } - - protected boolean isDeployable(Path path) - { - String basename = FileID.getBasename(path); - - //is the file that changed a directory? - if (Files.isDirectory(path)) - { - // deploy if there is not a .xml or .war file of the same basename? - return !Files.exists(path.getParent().resolve(basename + ".xml")) && - !Files.exists(path.getParent().resolve(basename + ".XML")) && - !Files.exists(path.getParent().resolve(basename + ".war")) && - !Files.exists(path.getParent().resolve(basename + ".WAR")); - } - - // deploy if it is a .war and there is not a .xml for of the same basename - if (FileID.isWebArchive(path)) - { - // if a .xml file exists for it - return !Files.exists(path.getParent().resolve(basename + ".xml")) && - !Files.exists(path.getParent().resolve(basename + ".XML")); - } - - // otherwise only deploy an XML - return FileID.isXml(path); - } - - @Override - protected void pathAdded(Path path) throws Exception - { - if (isDeployable(path)) - super.pathAdded(path); - } - - @Override - protected void pathChanged(Path path) throws Exception - { - if (isDeployable(path)) - super.pathChanged(path); - } - - /** - * Get the ClassLoader appropriate for applying Jetty XML. - * @param environment the environment to use - * @param xml the path to the XML - * @return the appropriate ClassLoader. - * @throws IOException if unable to create the ClassLoader - */ - private ClassLoader getXmlClassLoader(Environment environment, Path xml) throws IOException - { - if (Environment.CORE.equals(environment)) - { - // this XML belongs to a CORE deployment. - return findCoreContextClassLoader(xml); - } - else - { - return environment.getClassLoader(); - } - } - - private Attributes initAttributes(Environment environment, App app) throws IOException - { - Attributes attributes = new Attributes.Mapped(); - - // Start appAttributes with Attributes from Environment - environment.getAttributeNameSet().forEach((key) -> - attributes.setAttribute(key, environment.getAttribute(key))); - - // TODO: double check if an empty environment name makes sense. Will the default environment name? - String env = app.getEnvironmentName(); - - if (StringUtil.isNotBlank(env)) - { - // Load environment specific properties files - Path parent = app.getPath().getParent(); - Properties envProps = loadEnvironmentProperties(parent, env); - - envProps.stringPropertyNames().forEach( - k -> attributes.setAttribute(k, envProps.getProperty(k)) - ); - } - - // Overlay the app properties - app.getProperties().forEach(attributes::setAttribute); - - return attributes; - } - - /** - * Load all of the {@link Environment} specific {@code [-].properties} files - * found in the directory provided. - * - *

        - * All found properties files are first sorted by filename, then loaded one by one into - * a single {@link Properties} instance. - *

        - * - * @param directory the directory to load environment properties from. - * @param env the environment name - */ - private Properties loadEnvironmentProperties(Path directory, String env) throws IOException - { - Properties props = new Properties(); - List envPropertyFiles = new ArrayList<>(); - - // Get all environment specific properties files for this environment, - // order them according to the lexical ordering of the filenames - try (Stream paths = Files.list(directory)) - { - envPropertyFiles = paths.filter(Files::isRegularFile) - .map(directory::relativize) - .filter(p -> - { - String name = p.getName(0).toString(); - if (!name.endsWith(".properties")) - return false; - if (!name.startsWith(env)) - return false; - return true; - }).sorted().toList(); - } - - if (LOG.isDebugEnabled()) - LOG.debug("Environment property files {}", envPropertyFiles); - - // Load each *.properties file - for (Path file : envPropertyFiles) - { - Path resolvedFile = directory.resolve(file); - if (Files.exists(resolvedFile)) - { - Properties tmp = new Properties(); - try (InputStream stream = Files.newInputStream(resolvedFile)) - { - tmp.load(stream); - //put each property into our substitution pool - tmp.stringPropertyNames().forEach(k -> props.put(k, tmp.getProperty(k))); - } - } - } - - return props; - } - - private void loadProperties(Environment environment, InputStream inputStream) throws IOException - { - Properties props = new Properties(); - props.load(inputStream); - props.stringPropertyNames().forEach((name) -> - environment.setAttribute(name, props.getProperty(name))); - } - - /** - * Builder of a deployment configuration for a specific {@link Environment}. - * - *

        - * Results in {@link Attributes} for {@link Environment} containing the - * deployment configuration (as {@link Deployable} keys) that is applied to all deployable - * apps belonging to that {@link Environment}. - *

        - */ - public static class EnvBuilder - { - // Using setters in this class to allow jetty-xml - // syntax to skip setting of an environment attribute if property is unset, - // allowing the in code values to be same defaults as they are in embedded-jetty. - - private final Environment environment; - - public EnvBuilder(String name) - { - environment = Environment.ensure(name); - } - - /** - * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} attribute. - * - * @param configurations The configuration class names as a comma separated list - * @see Deployable#CONFIGURATION_CLASSES - */ - public void setConfigurationClasses(String configurations) - { - setConfigurationClasses(StringUtil.isBlank(configurations) ? null : configurations.split(",")); - } - - /** - * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} property. - * - * @param configurations The configuration class names. - * @see Deployable#CONFIGURATION_CLASSES - */ - public void setConfigurationClasses(String[] configurations) - { - if (configurations == null) - environment.removeAttribute(Deployable.CONFIGURATION_CLASSES); - else - environment.setAttribute(Deployable.CONFIGURATION_CLASSES, configurations); - } - - /** - * This is equivalent to setting the {@link Deployable#CONTAINER_SCAN_JARS} property. - * - * @param pattern The regex pattern to use when bytecode scanning container jars - * @see Deployable#CONTAINER_SCAN_JARS - */ - public void setContainerScanJarPattern(String pattern) - { - environment.setAttribute(Deployable.CONTAINER_SCAN_JARS, pattern); - } - - /** - * The name of the class that this environment uses to create {@link ContextHandler} - * instances (can be class that implements {@code java.util.function.Supplier} - * as well). - * - * @param classname the classname for this environment's context deployable. - * @see Deployable#CONTEXT_HANDLER_CLASS - */ - public void setContextHandlerClass(String classname) - { - environment.setAttribute(Deployable.CONTEXT_HANDLER_CLASS, classname); - } - - /** - * Set the defaultsDescriptor. - * This is equivalent to setting the {@link Deployable#DEFAULTS_DESCRIPTOR} attribute. - * - * @param defaultsDescriptor the defaultsDescriptor to set - * @see Deployable#DEFAULTS_DESCRIPTOR - */ - public void setDefaultsDescriptor(String defaultsDescriptor) - { - environment.setAttribute(Deployable.DEFAULTS_DESCRIPTOR, defaultsDescriptor); - } - - /** - * This is equivalent to setting the {@link Deployable#EXTRACT_WARS} attribute. - * - * @param extractWars the extractWars to set - * @see Deployable#EXTRACT_WARS - */ - public void setExtractWars(boolean extractWars) - { - environment.setAttribute(Deployable.EXTRACT_WARS, extractWars); - } - - /** - * This is equivalent to setting the {@link Deployable#PARENT_LOADER_PRIORITY} attribute. - * - * @param parentLoaderPriority the parentLoaderPriority to set - * @see Deployable#PARENT_LOADER_PRIORITY - */ - public void setParentLoaderPriority(boolean parentLoaderPriority) - { - environment.setAttribute(Deployable.PARENT_LOADER_PRIORITY, parentLoaderPriority); - } - - /** - * This is equivalent to setting the {@link Deployable#SCI_EXCLUSION_PATTERN} property. - * - * @param pattern The regex pattern to exclude ServletContainerInitializers from executing - * @see Deployable#SCI_EXCLUSION_PATTERN - */ - public void setServletContainerInitializerExclusionPattern(String pattern) - { - environment.setAttribute(Deployable.SCI_EXCLUSION_PATTERN, pattern); - } - - /** - * This is equivalent to setting the {@link Deployable#SCI_ORDER} property. - * - * @param order The ordered list of ServletContainerInitializer classes to run - * @see Deployable#SCI_ORDER - */ - public void setServletContainerInitializerOrder(String order) - { - environment.setAttribute(Deployable.SCI_ORDER, order); - } - - /** - * This is equivalent to setting the {@link Deployable#WEBINF_SCAN_JARS} property. - * - * @param pattern The regex pattern to use when bytecode scanning web-inf jars - * @see Deployable#WEBINF_SCAN_JARS - */ - public void setWebInfScanJarPattern(String pattern) - { - environment.setAttribute(Deployable.WEBINF_SCAN_JARS, pattern); - } - } - - public class Filter implements FilenameFilter - { - @Override - public boolean accept(File dir, String name) - { - if (dir == null || !dir.canRead()) - return false; - - // Accept XML files and WARs - if (FileID.isXml(name) || FileID.isWebArchive(name)) - return true; - - Path path = dir.toPath().resolve(name); - - // Ignore any other file that are not directories - if (!Files.isDirectory(path)) - return false; - - // Don't deploy monitored resources - if (getMonitoredResources().stream().map(Resource::getPath).anyMatch(path::equals)) - return false; - - // Ignore hidden directories - if (name.startsWith(".")) - return false; - - String lowerName = name.toLowerCase(Locale.ENGLISH); - - // is it a nominated config directory - if (lowerName.endsWith(".d")) - return false; - - // ignore source control directories - if ("cvs".equals(lowerName) || "cvsroot".equals(lowerName)) - return false; - - // ignore directories that have sibling war or XML file - if (Files.exists(dir.toPath().resolve(name + ".war")) || - Files.exists(dir.toPath().resolve(name + ".WAR")) || - Files.exists(dir.toPath().resolve(name + ".xml")) || - Files.exists(dir.toPath().resolve(name + ".XML"))) - return false; - - return true; - } - } -} diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml b/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml index 0c973bf2822e..3f5a24819b34 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml +++ b/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml @@ -3,18 +3,18 @@ - + ee10 + jetty.deploy.defaultsDescriptor + jetty.deploy.defaultsDescriptorPath /etc/webdefault-ee10.xml - jetty.deploy.defaultsDescriptor - jetty.deploy.defaultsDescriptorPath diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml b/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml index 5ab08e473b7a..415591463449 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml +++ b/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml @@ -3,18 +3,18 @@ - + ee11 + jetty.deploy.defaultsDescriptor + jetty.deploy.defaultsDescriptorPath /etc/webdefault-ee11.xml - jetty.deploy.defaultsDescriptor - jetty.deploy.defaultsDescriptorPath diff --git a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml index 03ac84cca3cc..f75a07217839 100644 --- a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml +++ b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml @@ -3,18 +3,18 @@ - + ee8 + jetty.deploy.defaultsDescriptor + jetty.deploy.defaultsDescriptorPath /etc/webdefault-ee8.xml - jetty.deploy.defaultsDescriptor - jetty.deploy.defaultsDescriptorPath diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml index adcf0e856a3c..9c7fc4abdb4d 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml +++ b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml @@ -3,18 +3,18 @@ - + ee9 + jetty.deploy.defaultsDescriptor + jetty.deploy.defaultsDescriptorPath /etc/webdefault-ee9.xml - jetty.deploy.defaultsDescriptor - jetty.deploy.defaultsDescriptorPath From 7588d5fd47dc63a75aee2124ce3cee944b0b05f4 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 25 Nov 2024 14:48:03 -0600 Subject: [PATCH 05/22] Introducing ContextProvider.configureEnvironment(String name) --- .../src/main/config/etc/jetty-core-deploy.xml | 38 +---- .../src/main/config/etc/jetty-deploy.xml | 49 +++--- .../config/etc/jetty-deployment-manager.xml | 29 ++++ .../src/main/config/modules/core-deploy.mod | 17 +-- .../src/main/config/modules/deploy.mod | 15 +- .../config/modules/deployment-manager.mod | 12 ++ .../deploy/providers/ContextProvider.java | 140 ++++++++++++------ .../jetty/deploy/DeploymentManagerTest.java | 2 +- .../ContextProviderDeferredStartupTest.java | 7 +- .../ContextProviderRuntimeUpdatesTest.java | 5 +- .../providers/ContextProviderStartupTest.java | 6 +- .../resources/jetty-core-deploy-custom.xml | 21 +++ .../resources/jetty-deploymgr-contexts.xml | 40 ----- .../jetty/ee10/test/DeploymentErrorTest.java | 2 +- .../src/main/config/etc/jetty-ee10-deploy.xml | 52 +++---- .../src/main/config/modules/ee10-deploy.mod | 9 -- .../src/main/config/etc/jetty-ee11-deploy.xml | 52 +++---- .../src/main/config/modules/ee11-deploy.mod | 6 - .../src/main/config/etc/jetty-ee8-deploy.xml | 52 +++---- .../src/main/config/modules/ee8-deploy.mod | 6 - .../jetty/ee9/test/DeploymentErrorTest.java | 2 +- .../src/main/config/etc/jetty-ee9-deploy.xml | 52 +++---- .../src/main/config/modules/ee9-deploy.mod | 6 - 23 files changed, 326 insertions(+), 294 deletions(-) create mode 100644 jetty-core/jetty-deploy/src/main/config/etc/jetty-deployment-manager.xml create mode 100644 jetty-core/jetty-deploy/src/main/config/modules/deployment-manager.mod create mode 100644 jetty-core/jetty-deploy/src/test/resources/jetty-core-deploy-custom.xml delete mode 100644 jetty-core/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml diff --git a/jetty-core/jetty-deploy/src/main/config/etc/jetty-core-deploy.xml b/jetty-core/jetty-deploy/src/main/config/etc/jetty-core-deploy.xml index e4d6b0c4acad..7adf72376086 100644 --- a/jetty-core/jetty-deploy/src/main/config/etc/jetty-core-deploy.xml +++ b/jetty-core/jetty-deploy/src/main/config/etc/jetty-core-deploy.xml @@ -1,37 +1,13 @@ - + - - - org.eclipse.jetty.deploy.DeploymentManager + + + core + + + - - - contextHandlerClass - - - - - - - - core - - - - - - - - - - - - - - - - diff --git a/jetty-core/jetty-deploy/src/main/config/etc/jetty-deploy.xml b/jetty-core/jetty-deploy/src/main/config/etc/jetty-deploy.xml index 02d9dc902096..afc5c4e338f7 100644 --- a/jetty-core/jetty-deploy/src/main/config/etc/jetty-deploy.xml +++ b/jetty-core/jetty-deploy/src/main/config/etc/jetty-deploy.xml @@ -2,30 +2,31 @@ - - - - - + + + + + - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + + + + diff --git a/jetty-core/jetty-deploy/src/main/config/etc/jetty-deployment-manager.xml b/jetty-core/jetty-deploy/src/main/config/etc/jetty-deployment-manager.xml new file mode 100644 index 000000000000..6d7a6b7dd238 --- /dev/null +++ b/jetty-core/jetty-deploy/src/main/config/etc/jetty-deployment-manager.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jetty-core/jetty-deploy/src/main/config/modules/core-deploy.mod b/jetty-core/jetty-deploy/src/main/config/modules/core-deploy.mod index 8de1a2de16e4..bf893a6f3cf8 100644 --- a/jetty-core/jetty-deploy/src/main/config/modules/core-deploy.mod +++ b/jetty-core/jetty-deploy/src/main/config/modules/core-deploy.mod @@ -15,21 +15,6 @@ webapps/ [xml] etc/jetty-core-deploy.xml -[ini] -contextHandlerClass?=org.eclipse.jetty.server.handler.ResourceHandler$ResourceContext - [ini-template] -## Monitored directory name (relative to $jetty.base) -# jetty.deploy.monitoredDir=webapps - -# Defer Initial Scan -# true to have the initial scan deferred until the Server component is started. -# Note: deploy failures do not fail server startup in a deferred initial scan mode. -# false (default) to have initial scan occur as normal. -# jetty.deploy.deferInitialScan=false - -## Monitored directory scan period (seconds) -# jetty.deploy.scanInterval=0 - -## Default ContextHandler class for core deployments +## Default ContextHandler class for "core" environment deployments # contextHandlerClass=org.eclipse.jetty.server.handler.ResourceHandler$ResourceContext diff --git a/jetty-core/jetty-deploy/src/main/config/modules/deploy.mod b/jetty-core/jetty-deploy/src/main/config/modules/deploy.mod index a4db8cd3efa5..1dff1db7ef9e 100644 --- a/jetty-core/jetty-deploy/src/main/config/modules/deploy.mod +++ b/jetty-core/jetty-deploy/src/main/config/modules/deploy.mod @@ -1,8 +1,9 @@ [description] -This module enables web application deployment from the `$JETTY_BASE/webapps` directory. +This module enables web application context deployment from the `$JETTY_BASE/webapps` directory. [depend] server +deployment-manager [lib] lib/jetty-deploy-${jetty.version}.jar @@ -13,3 +14,15 @@ webapps/ [xml] etc/jetty-deploy.xml +[ini-template] +## Monitored directory name (relative to $jetty.base) +# jetty.deploy.monitoredDir=webapps + +# Defer Initial Scan +# true to have the initial scan deferred until the Server component is started. +# Note: deploy failures do not fail server startup in a deferred initial scan mode. +# false (default) to have initial scan occur as normal. +# jetty.deploy.deferInitialScan=false + +## Monitored directory scan period (seconds) +# jetty.deploy.scanInterval=0 diff --git a/jetty-core/jetty-deploy/src/main/config/modules/deployment-manager.mod b/jetty-core/jetty-deploy/src/main/config/modules/deployment-manager.mod new file mode 100644 index 000000000000..d9e73c73b41e --- /dev/null +++ b/jetty-core/jetty-deploy/src/main/config/modules/deployment-manager.mod @@ -0,0 +1,12 @@ +[description] +This module enables the DeploymentManager + +[depend] +server + +[lib] +lib/jetty-deploy-${jetty.version}.jar + +[xml] +etc/jetty-deployment-manager.xml + diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index cfeaab2c55de..ee46a83037ec 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -21,6 +21,7 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -51,39 +52,39 @@ /** * The webapps directory scanning provider. - * *

        This provider scans one or more directories (typically "webapps") for contexts to - * * deploy, which may be: - * *

        - * *
          - * *
        • A standard WAR file (must end in ".war")
        • - * *
        • A directory containing an expanded WAR file
        • - * *
        • A directory containing static content
        • - * *
        • An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance
        • - * *
        - * *

        To avoid double deployments and allow flexibility of the content of the scanned directories, the provider - * * implements some heuristics to ignore some files found in the scans: - * *

        - * *
          - * *
        • Hidden files (starting with {@code "."}) are ignored
        • - * *
        • Directories with names ending in {@code ".d"} are ignored
        • - * *
        • Property files with names ending in {@code ".properties"} are not deployed.
        • - * *
        • If a directory and a WAR file exist (eg: {@code foo/} and {@code foo.war}) then the directory is assumed to be - * * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)
        • - * *
        • If a directory and a matching XML file exist (eg: {@code foo/} and {@code foo.xml}) then the directory is assumed to be - * * an unpacked WAR and only the XML is deployed (which may used the directory in its configuration)
        • - * *
        • If a WAR file and a matching XML exist (eg: {@code foo.war} and {@code foo.xml}) then the WAR is assumed to - * * be configured by the XML and only the XML is deployed. - * *
        - * *

        For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and - * * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". - * * The properties will be initialized with: - * *

        - * *
          - * *
        • The properties set on the application via {@link App#getProperties()}
        • - * *
        • The app specific properties file {@code webapps/.properties}
        • - * *
        • The environment specific properties file {@code webapps/[-zzz].properties}
        • - * *
        • The {@link Attributes} from the {@link Environment}
        • - * *
        + * *

        This provider scans one or more directories (typically "webapps") for contexts to + * * deploy, which may be: + * *

        + * *
          + * *
        • A standard WAR file (must end in ".war")
        • + * *
        • A directory containing an expanded WAR file
        • + * *
        • A directory containing static content
        • + * *
        • An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance
        • + * *
        + * *

        To avoid double deployments and allow flexibility of the content of the scanned directories, the provider + * * implements some heuristics to ignore some files found in the scans: + * *

        + * *
          + * *
        • Hidden files (starting with {@code "."}) are ignored
        • + * *
        • Directories with names ending in {@code ".d"} are ignored
        • + * *
        • Property files with names ending in {@code ".properties"} are not deployed.
        • + * *
        • If a directory and a WAR file exist (eg: {@code foo/} and {@code foo.war}) then the directory is assumed to be + * * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)
        • + * *
        • If a directory and a matching XML file exist (eg: {@code foo/} and {@code foo.xml}) then the directory is assumed to be + * * an unpacked WAR and only the XML is deployed (which may used the directory in its configuration)
        • + * *
        • If a WAR file and a matching XML exist (eg: {@code foo.war} and {@code foo.xml}) then the WAR is assumed to + * * be configured by the XML and only the XML is deployed. + * *
        + * *

        For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and + * * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". + * * The properties will be initialized with: + * *

        + * *
          + * *
        • The properties set on the application via {@link App#getProperties()}
        • + * *
        • The app specific properties file {@code webapps/.properties}
        • + * *
        • The environment specific properties file {@code webapps/[-zzz].properties}
        • + * *
        • The {@link Attributes} from the {@link Environment}
        • + * *
        */ @ManagedObject("Provider for start-up deployment of webapps based on presence in directory") public class ContextProvider extends ScanningAppProvider @@ -117,7 +118,8 @@ private static Map asProperties(Attributes attributes) @Override public ContextHandler createContextHandler(final App app) throws Exception { - Environment environment = Environment.get(app.getEnvironmentName()); + String envName = app.getEnvironmentName(); + Environment environment = Environment.get(StringUtil.isNotBlank(envName) ? envName : getDefaultEnvironmentName()); if (environment == null) { @@ -161,7 +163,14 @@ public ContextHandler createContextHandler(final App app) throws Exception List sortedEnvXmlPaths = appAttributes.getAttributeNameSet() .stream() .filter(k -> k.startsWith(Deployable.ENVIRONMENT_XML)) - .map(k -> Path.of((String)appAttributes.getAttribute(k))) + .map(k -> + { + Path envXmlPath = Paths.get((String)appAttributes.getAttribute(k)); + if (!envXmlPath.isAbsolute()) + envXmlPath = getMonitoredDirResource().getPath().getParent().resolve(envXmlPath); + return envXmlPath; + }) + .filter(Files::isRegularFile) .sorted() .toList(); @@ -227,8 +236,8 @@ else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) * do not declare the {@link Environment} that they belong to. * *

        - * Falls back to {@link Environment#getAll()} list, and returns - * the first name returned after sorting with {@link Deployable#ENVIRONMENT_COMPARATOR} + * Falls back to {@link Environment#getAll()} list, and returns + * the first name returned after sorting with {@link Deployable#ENVIRONMENT_COMPARATOR} *

        * * @return the default environment name. @@ -277,6 +286,46 @@ public void loadPropertiesFromString(Environment environment, String path) throw loadProperties(environment, Path.of(path)); } + /** + * Configure the Environment specific Deploy settings. + * + * @param name the name of the environment. + * @return the deployment configuration for the {@link Environment}. + */ + public EnvironmentConfig configureEnvironment(String name) + { + return new EnvironmentConfig(Environment.ensure(name)); + } + + /** + * To enable support for an {@link Environment}, just ensure it exists. + * + *

        + * Eg: {@code Environment.ensure("ee11");} + *

        + * + *

        + * To configure Environment specific deployment {@link Attributes}, + * either set the appropriate {@link Deployable} attribute via {@link Attributes#setAttribute(String, Object)}, + * or use the convenience class {@link EnvironmentConfig}. + *

        + * + *
        {@code
        +     * ContextProvider provider = new ContextProvider();
        +     * ContextProvider.EnvironmentConfig envbuilder = provider.configureEnvironment("ee10");
        +     * envbuilder.setExtractWars(true);
        +     * envbuilder.setParentLoaderPriority(false);
        +     * }
        + * + * @see #configureEnvironment(String) instead + * @deprecated not used anymore. + */ + @Deprecated(since = "12.1.0", forRemoval = true) + public void setEnvironmentName(String name) + { + Environment.ensure(name); + } + protected Object applyXml(Object context, Path xml, Environment environment, Attributes attributes) throws Exception { if (!FileID.isXml(xml)) @@ -511,6 +560,7 @@ protected void pathChanged(Path path) throws Exception /** * Get the ClassLoader appropriate for applying Jetty XML. + * * @param environment the environment to use * @param xml the path to the XML * @return the appropriate ClassLoader. @@ -562,8 +612,8 @@ private Attributes initAttributes(Environment environment, App app) throws IOExc * found in the directory provided. * *

        - * All found properties files are first sorted by filename, then loaded one by one into - * a single {@link Properties} instance. + * All found properties files are first sorted by filename, then loaded one by one into + * a single {@link Properties} instance. *

        * * @param directory the directory to load environment properties from. @@ -625,12 +675,12 @@ private void loadProperties(Environment environment, InputStream inputStream) th * Builder of a deployment configuration for a specific {@link Environment}. * *

        - * Results in {@link Attributes} for {@link Environment} containing the - * deployment configuration (as {@link Deployable} keys) that is applied to all deployable - * apps belonging to that {@link Environment}. + * Results in {@link Attributes} for {@link Environment} containing the + * deployment configuration (as {@link Deployable} keys) that is applied to all deployable + * apps belonging to that {@link Environment}. *

        */ - public static class EnvBuilder + public static class EnvironmentConfig { // Using setters in this class to allow jetty-xml // syntax to skip setting of an environment attribute if property is unset, @@ -638,9 +688,9 @@ public static class EnvBuilder private final Environment environment; - public EnvBuilder(String name) + private EnvironmentConfig(Environment environment) { - environment = Environment.ensure(name); + this.environment = environment; } /** diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java index 2bd5d43f3646..432501a67e62 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java @@ -165,7 +165,7 @@ public void testXmlConfigured(WorkDir workDir) throws Exception jetty = new XmlConfiguredJetty(testdir); jetty.addConfiguration("jetty.xml"); jetty.addConfiguration("jetty-http.xml"); - jetty.addConfiguration("jetty-deploymgr-contexts.xml"); + jetty.addConfiguration("jetty-core-deploy-custom.xml"); // Should not throw an Exception jetty.load(); diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderDeferredStartupTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderDeferredStartupTest.java index cda077a79211..c85948ce1788 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderDeferredStartupTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderDeferredStartupTest.java @@ -27,14 +27,13 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenPaths; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.eclipse.jetty.util.Scanner; import org.eclipse.jetty.util.component.Container; import org.eclipse.jetty.util.component.LifeCycle; -import org.eclipse.jetty.util.resource.Resource; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -68,7 +67,9 @@ public void testDelayedDeploy() throws Exception jetty = new XmlConfiguredJetty(realBase); jetty.addConfiguration("jetty.xml"); jetty.addConfiguration("jetty-http.xml"); - jetty.addConfiguration("jetty-deploymgr-contexts.xml"); + jetty.addConfiguration(MavenPaths.projectBase().resolve("src/main/config/etc/jetty-deployment-manager.xml")); + jetty.addConfiguration(MavenPaths.projectBase().resolve("src/main/config/etc/jetty-deploy.xml")); + jetty.addConfiguration("jetty-core-deploy-custom.xml"); // Put a context into the base jetty.copyWebapp("bar-core-context.xml", "bar.xml"); diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderRuntimeUpdatesTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderRuntimeUpdatesTest.java index 977db71608ea..a600972b985c 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderRuntimeUpdatesTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderRuntimeUpdatesTest.java @@ -25,6 +25,7 @@ import org.eclipse.jetty.deploy.test.XmlConfiguredJetty; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenPaths; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.eclipse.jetty.util.Scanner; @@ -80,7 +81,9 @@ public void startJetty() throws Exception { jetty.addConfiguration("jetty.xml"); jetty.addConfiguration("jetty-http.xml"); - jetty.addConfiguration("jetty-deploymgr-contexts.xml"); + jetty.addConfiguration(MavenPaths.projectBase().resolve("src/main/config/etc/jetty-deployment-manager.xml")); + jetty.addConfiguration(MavenPaths.projectBase().resolve("src/main/config/etc/jetty-deploy.xml")); + jetty.addConfiguration("jetty-core-deploy-custom.xml"); // Should not throw an Exception jetty.load(); diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java index a68db97d0e66..9fc7c0a25e0b 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java @@ -37,7 +37,6 @@ import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -63,7 +62,9 @@ public void setupEnvironment() throws Exception jetty.addConfiguration("jetty.xml"); jetty.addConfiguration("jetty-http.xml"); - jetty.addConfiguration("jetty-deploymgr-contexts.xml"); + jetty.addConfiguration(MavenPaths.projectBase().resolve("src/main/config/etc/jetty-deployment-manager.xml")); + jetty.addConfiguration(MavenPaths.projectBase().resolve("src/main/config/etc/jetty-deploy.xml")); + jetty.addConfiguration("jetty-core-deploy-custom.xml"); // Setup initial context jetty.copyWebapp("bar-core-context.xml", "bar.xml"); @@ -167,7 +168,6 @@ public void testNonEnvironmentPropertyFileNotApplied() throws Exception /** * Test that properties of the same name will be overridden, in the order of the name of the .properties file - * @throws Exception */ @Test public void testPropertyOverriding() throws Exception diff --git a/jetty-core/jetty-deploy/src/test/resources/jetty-core-deploy-custom.xml b/jetty-core/jetty-deploy/src/test/resources/jetty-core-deploy-custom.xml new file mode 100644 index 000000000000..748ab6ecbda8 --- /dev/null +++ b/jetty-core/jetty-deploy/src/test/resources/jetty-core-deploy-custom.xml @@ -0,0 +1,21 @@ + + + + + core + + + + + + core + + + + + + + + + + diff --git a/jetty-core/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml b/jetty-core/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml deleted file mode 100644 index 0b3431bd2225..000000000000 --- a/jetty-core/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - core - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/DeploymentErrorTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/DeploymentErrorTest.java index 89877be22cba..b7daf2bb4952 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/DeploymentErrorTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/java/org/eclipse/jetty/ee10/test/DeploymentErrorTest.java @@ -107,7 +107,7 @@ public Path startServer(Consumer docrootSetupConsumer) throws Exception System.setProperty("test.docroots", docroots.toAbsolutePath().toString()); ContextProvider appProvider = new ContextProvider(); - appProvider.setEnvironmentName("ee10"); + appProvider.configureEnvironment("ee10"); appProvider.setScanInterval(1); appProvider.setMonitoredDirResource(resourceFactory.newResource(docroots)); diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml b/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml index 3f5a24819b34..019157c06ce5 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml +++ b/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml @@ -3,29 +3,31 @@ - - ee10 - - - - - - jetty.deploy.defaultsDescriptor - jetty.deploy.defaultsDescriptorPath - - /etc/webdefault-ee10.xml - - - - - - - - - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - + + + ee10 + + + + + + jetty.deploy.defaultsDescriptorPath + jetty.deploy.defaultsDescriptor + + /etc/webdefault-ee10.xml + + + + + + + + + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + + + + + + diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/config/modules/ee10-deploy.mod b/jetty-ee10/jetty-ee10-webapp/src/main/config/modules/ee10-deploy.mod index a3e44ecc5c78..30f04501fb69 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/config/modules/ee10-deploy.mod +++ b/jetty-ee10/jetty-ee10-webapp/src/main/config/modules/ee10-deploy.mod @@ -15,15 +15,6 @@ etc/jetty-ee10-deploy.xml [ini-template] # tag::ini-template[] -## Monitored directory name (relative to $jetty.base) -# jetty.deploy.monitoredDir=webapps - -## Defaults Descriptor for all deployed webapps -# jetty.deploy.defaultsDescriptorPath=${jetty.base}/etc/webdefault-ee10.xml - -## Monitored directory scan period (seconds) -# jetty.deploy.scanInterval=0 - ## Whether to extract *.war files # jetty.deploy.extractWars=true diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml b/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml index 415591463449..eeccefecff4a 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml +++ b/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml @@ -3,29 +3,31 @@ - - ee11 - - - - - - jetty.deploy.defaultsDescriptor - jetty.deploy.defaultsDescriptorPath - - /etc/webdefault-ee11.xml - - - - - - - - - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - + + + ee11 + + + + + + jetty.deploy.defaultsDescriptorPath + jetty.deploy.defaultsDescriptor + + /etc/webdefault-ee11.xml + + + + + + + + + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + + + + + + diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/config/modules/ee11-deploy.mod b/jetty-ee11/jetty-ee11-webapp/src/main/config/modules/ee11-deploy.mod index f36f320f7992..27a2ed0974c6 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/config/modules/ee11-deploy.mod +++ b/jetty-ee11/jetty-ee11-webapp/src/main/config/modules/ee11-deploy.mod @@ -15,15 +15,9 @@ etc/jetty-ee11-deploy.xml [ini-template] # tag::ini-template[] -## Monitored directory name (relative to $jetty.base) -# jetty.deploy.monitoredDir=webapps - ## Defaults Descriptor for all deployed webapps # jetty.deploy.defaultsDescriptorPath=${jetty.base}/etc/webdefault-ee11.xml -## Monitored directory scan period (seconds) -# jetty.deploy.scanInterval=0 - ## Whether to extract *.war files # jetty.deploy.extractWars=true diff --git a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml index f75a07217839..fdb720ccb634 100644 --- a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml +++ b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml @@ -3,29 +3,31 @@ - - ee8 - - - - - - jetty.deploy.defaultsDescriptor - jetty.deploy.defaultsDescriptorPath - - /etc/webdefault-ee8.xml - - - - - - - - - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - + + + ee8 + + + + + + jetty.deploy.defaultsDescriptorPath + jetty.deploy.defaultsDescriptor + + /etc/webdefault-ee8.xml + + + + + + + + + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + + + + + + diff --git a/jetty-ee8/jetty-ee8-webapp/src/main/config/modules/ee8-deploy.mod b/jetty-ee8/jetty-ee8-webapp/src/main/config/modules/ee8-deploy.mod index 3d44d7d5bedd..6897cd9484a2 100644 --- a/jetty-ee8/jetty-ee8-webapp/src/main/config/modules/ee8-deploy.mod +++ b/jetty-ee8/jetty-ee8-webapp/src/main/config/modules/ee8-deploy.mod @@ -15,15 +15,9 @@ etc/jetty-ee8-deploy.xml [ini-template] # tag::ini-template[] -## Monitored directory name (relative to $jetty.base) -# jetty.deploy.monitoredDir=webapps - ## Defaults Descriptor for all deployed webapps # jetty.deploy.defaultsDescriptorPath=${jetty.base}/etc/webdefault-ee8.xml -## Monitored directory scan period (seconds) -# jetty.deploy.scanInterval=0 - ## Whether to extract *.war files # jetty.deploy.extractWars=true diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/DeploymentErrorTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/DeploymentErrorTest.java index d9e73b607cbb..34fb6841dfe9 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/DeploymentErrorTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/DeploymentErrorTest.java @@ -102,7 +102,7 @@ public Path startServer(Consumer docrootSetupConsumer, Path docroots) thro System.setProperty("test.docroots", docroots.toAbsolutePath().toString()); ContextProvider appProvider = new ContextProvider(); - appProvider.setEnvironmentName("ee9"); + Environment.ensure("ee9"); appProvider.setMonitoredDirResource(resourceFactory.newResource(docroots)); appProvider.setScanInterval(1); deploymentManager.addAppProvider(appProvider); diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml index 9c7fc4abdb4d..4ff9703d3569 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml +++ b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml @@ -3,29 +3,31 @@ - - ee9 - - - - - - jetty.deploy.defaultsDescriptor - jetty.deploy.defaultsDescriptorPath - - /etc/webdefault-ee9.xml - - - - - - - - - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - + + + ee9 + + + + + + jetty.deploy.defaultsDescriptorPath + jetty.deploy.defaultsDescriptor + + /etc/webdefault-ee9.xml + + + + + + + + + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + + + + + + diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/config/modules/ee9-deploy.mod b/jetty-ee9/jetty-ee9-webapp/src/main/config/modules/ee9-deploy.mod index 742f44c76019..75b121460f2d 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/config/modules/ee9-deploy.mod +++ b/jetty-ee9/jetty-ee9-webapp/src/main/config/modules/ee9-deploy.mod @@ -15,15 +15,9 @@ etc/jetty-ee9-deploy.xml [ini-template] # tag::ini-template[] -## Monitored directory name (relative to $jetty.base) -# jetty.deploy.monitoredDir=webapps - ## Defaults Descriptor for all deployed webapps # jetty.deploy.defaultsDescriptorPath=${jetty.base}/etc/webdefault-ee9.xml -## Monitored directory scan period (seconds) -# jetty.deploy.scanInterval=0 - ## Whether to extract *.war files # jetty.deploy.extractWars=true From 42015b3fbd76242d59f72050b158a106cacf30fc Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 26 Nov 2024 15:22:19 -0600 Subject: [PATCH 06/22] Fixing [-zzz].properties sorting --- .../jetty/deploy/providers/ContextProvider.java | 14 +++++++++++--- .../providers/ContextProviderStartupTest.java | 13 +++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index ee46a83037ec..7028e45ad550 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -45,6 +45,7 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.Environment; +import org.eclipse.jetty.util.resource.PathCollators; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.xml.XmlConfiguration; @@ -167,11 +168,18 @@ public ContextHandler createContextHandler(final App app) throws Exception { Path envXmlPath = Paths.get((String)appAttributes.getAttribute(k)); if (!envXmlPath.isAbsolute()) - envXmlPath = getMonitoredDirResource().getPath().getParent().resolve(envXmlPath); + { + Path monitoredPath = getMonitoredDirResource().getPath(); + // not all Resource implementations support java.nio.file.Path. + if (monitoredPath != null) + { + envXmlPath = monitoredPath.getParent().resolve(envXmlPath); + } + } return envXmlPath; }) .filter(Files::isRegularFile) - .sorted() + .sorted(PathCollators.byName(true)) .toList(); // apply each environment context xml file @@ -211,7 +219,7 @@ else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) // Build the web application if necessary if (context == null) { - contextHandlerClassName = (String)environment.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); + contextHandlerClassName = (String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); if (StringUtil.isBlank(contextHandlerClassName)) throw new IllegalStateException("No ContextHandler classname for " + app); Class contextHandlerClass = Loader.loadClass(contextHandlerClassName); diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java index 9fc7c0a25e0b..563c50b87f9a 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java @@ -123,17 +123,18 @@ public void testStartupWithRelativeEnvironmentContext() throws Exception public void testStartupWithAbsoluteEnvironmentContext() throws Exception { Path jettyBase = jetty.getJettyBasePath(); - Path propsFile = Files.writeString(jettyBase.resolve("webapps/core.properties"), Deployable.ENVIRONMENT_XML + " = " + - MavenPaths.findTestResourceFile("etc/core-context.xml"), StandardOpenOption.CREATE_NEW); + Path propsFile = Files.writeString(jettyBase.resolve("webapps/core.properties"), + String.format("%s = %s%n", Deployable.ENVIRONMENT_XML, MavenPaths.findTestResourceFile("etc/core-context.xml")), + StandardOpenOption.CREATE_NEW); assertTrue(Files.exists(propsFile)); - Path props2File = Files.writeString(jettyBase.resolve("webapps/core-other.properties"), Deployable.ENVIRONMENT_XML + ".other = " + MavenPaths.findTestResourceFile("etc/core-context-other.xml"), StandardOpenOption.CREATE_NEW); + Path props2File = Files.writeString(jettyBase.resolve("webapps/core-other.properties"), + String.format("%s = %s%n", (Deployable.ENVIRONMENT_XML + ".other"), MavenPaths.findTestResourceFile("etc/core-context-other.xml")), + StandardOpenOption.CREATE_NEW); assertTrue(Files.exists(props2File)); - Files.copy(MavenPaths.findTestResourceFile("etc/core-context.xml"), jettyBase.resolve("etc/core-context.xml"), StandardCopyOption.REPLACE_EXISTING); - Files.copy(MavenPaths.findTestResourceFile("etc/core-context-other.xml"), jettyBase.resolve("etc/core-context-other.xml"), StandardCopyOption.REPLACE_EXISTING); jetty.copyWebapp("bar-core-context.properties", "bar.properties"); startJetty(); - //check core environment context xml was applied to the produced context + // check core environment context xml was applied to the produced context ContextHandler context = jetty.getContextHandler("/bar"); assertNotNull(context); assertThat(context.getAttribute("core-context-0"), equalTo("core-context-0")); From f9c90cec7d662497a015f6456c33cb42aecd8707 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 26 Nov 2024 15:45:01 -0600 Subject: [PATCH 07/22] Allow an Environment to be removed (needed for testing with Environment) --- .../java/org/eclipse/jetty/util/component/Environment.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/Environment.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/Environment.java index b2d7cca0bdcd..4338c07b9471 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/Environment.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/Environment.java @@ -50,6 +50,11 @@ static Environment set(Environment environment) return Named.__environments.put(environment.getName(), environment); } + static Environment remove(String name) + { + return Named.__environments.remove(name); + } + /** * @return The case-insensitive name of the environment. */ From 83fc5f101143ecc8b17bf3010d02517b409faa2b Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 26 Nov 2024 15:45:17 -0600 Subject: [PATCH 08/22] Fixing test cases --- .../jetty/deploy/DeploymentManagerTest.java | 62 ++++++++++++------- .../ContextProviderDeferredStartupTest.java | 2 +- .../ContextProviderRuntimeUpdatesTest.java | 3 +- .../providers/ContextProviderStartupTest.java | 3 +- .../jetty/deploy/test/XmlConfiguredJetty.java | 61 ++---------------- 5 files changed, 47 insertions(+), 84 deletions(-) diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java index 432501a67e62..f3e8b85d4a12 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java @@ -15,6 +15,7 @@ import java.nio.file.Path; import java.util.Collection; +import java.util.List; import java.util.Set; import org.eclipse.jetty.deploy.test.XmlConfiguredJetty; @@ -23,7 +24,9 @@ import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.eclipse.jetty.util.component.Environment; +import org.eclipse.jetty.util.component.LifeCycle; import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -37,6 +40,21 @@ @ExtendWith(WorkDirExtension.class) public class DeploymentManagerTest { + /** + * Cleanup after any tests that modify the Environments singleton + */ + @AfterEach + public void clearEnvironments() + { + List envnames = Environment.getAll().stream() + .map(Environment::getName) + .toList(); + for (String envname : envnames) + { + Environment.remove(envname); + } + assertEquals(0, Environment.getAll().size()); + } @Test public void testReceiveApp() throws Exception @@ -53,20 +71,27 @@ public void testReceiveApp() throws Exception // Start DepMan depman.start(); - // Trigger new App - mockProvider.createWebapp("foo-webapp-1.war"); - - // Test app tracking - Collection apps = depman.getApps(); - assertNotNull(apps, "Should never be null"); - assertEquals(1, apps.size(), "Expected App Count"); - - // Test app get - App app = apps.stream().findFirst().orElse(null); - assertNotNull(app); - App actual = depman.getApp(app.getPath()); - assertNotNull(actual, "Should have gotten app (by id)"); - assertThat(actual.getPath().toString(), endsWith("mock-foo-webapp-1.war")); + try + { + // Trigger new App + mockProvider.createWebapp("foo-webapp-1.war"); + + // Test app tracking + Collection apps = depman.getApps(); + assertNotNull(apps, "Should never be null"); + assertEquals(1, apps.size(), "Expected App Count"); + + // Test app get + App app = apps.stream().findFirst().orElse(null); + assertNotNull(app); + App actual = depman.getApp(app.getPath()); + assertNotNull(actual, "Should have gotten app (by id)"); + assertThat(actual.getPath().toString(), endsWith("mock-foo-webapp-1.war")); + } + finally + { + LifeCycle.stop(depman); + } } @Test @@ -177,14 +202,7 @@ public void testXmlConfigured(WorkDir workDir) throws Exception { if (jetty != null) { - try - { - jetty.stop(); - } - catch (Exception ignore) - { - // ignore - } + jetty.stop(); } } } diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderDeferredStartupTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderDeferredStartupTest.java index c85948ce1788..620223a1e319 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderDeferredStartupTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderDeferredStartupTest.java @@ -55,7 +55,7 @@ public class ContextProviderDeferredStartupTest @AfterEach public void teardownEnvironment() throws Exception { - LifeCycle.stop(jetty); + jetty.stop(); } @Test diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderRuntimeUpdatesTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderRuntimeUpdatesTest.java index a600972b985c..653c1a5a12a3 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderRuntimeUpdatesTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderRuntimeUpdatesTest.java @@ -29,7 +29,6 @@ import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; import org.eclipse.jetty.util.Scanner; -import org.eclipse.jetty.util.component.LifeCycle; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -115,7 +114,7 @@ public void scanEnded(int cycle) @AfterEach public void teardownEnvironment() throws Exception { - LifeCycle.stop(jetty); + jetty.stop(); } public void waitForDirectoryScan() diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java index 563c50b87f9a..dceaeb90eea5 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java @@ -27,7 +27,6 @@ import org.eclipse.jetty.toolchain.test.MavenPaths; import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; -import org.eclipse.jetty.util.component.LifeCycle; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -82,7 +81,7 @@ public void startJetty() throws Exception @AfterEach public void teardownEnvironment() throws Exception { - LifeCycle.stop(jetty); + jetty.stop(); } @Test diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java index 2ee6ce882eda..9822961c6657 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java @@ -36,9 +36,7 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpURI; -import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; @@ -47,6 +45,7 @@ import org.eclipse.jetty.toolchain.test.PathMatchers; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.URIUtil; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.xml.XmlConfiguration; @@ -290,47 +289,6 @@ public Server load() throws Exception return this._server; } - /* - Path testConfig = _jettyBase.resolve("xml-configured-jetty.properties"); - setProperty("jetty.deploy.common.properties", testConfig.toString()); - - // Write out configuration for use by ConfigurationManager. - Properties properties = new Properties(); - properties.putAll(_properties); - try (OutputStream out = Files.newOutputStream(testConfig)) - { - properties.store(out, "Generated by " + XmlConfiguredJetty.class.getName()); - } - - XmlConfiguration last = null; - Object[] obj = new Object[_xmlConfigurations.size()]; - - // Configure everything - for (int i = 0; i < _xmlConfigurations.size(); i++) - { - Resource config = _xmlConfigurations.get(i); - XmlConfiguration configuration = new XmlConfiguration(config); - if (last != null) - configuration.getIdMap().putAll(last.getIdMap()); - configuration.getProperties().putAll(_properties); - obj[i] = configuration.configure(); - last = configuration; - } - - Map ids = last.getIdMap(); - - // Test for Server Instance. - Server server = (Server)ids.get("Server"); - if (server == null) - { - throw new Exception("Load failed to configure a " + Server.class.getName()); - } - - this._server = server; - this._server.setStopTimeout(10000); - this._contexts = (ContextHandlerCollection)ids.get("Contexts"); - } */ - public void removeWebapp(String name) throws IOException { Path webappFile = _jettyBase.resolve("webapps/" + name); @@ -358,19 +316,7 @@ public void start() throws Exception _server.start(); // Find the active server port. - _serverPort = -1; - Connector[] connectors = _server.getConnectors(); - for (int i = 0; _serverPort < 0 && i < connectors.length; i++) - { - if (connectors[i] instanceof NetworkConnector) - { - int port = ((NetworkConnector)connectors[i]).getLocalPort(); - if (port > 0) - _serverPort = port; - } - } - - assertTrue((1 <= this._serverPort) && (this._serverPort <= 65535), "Server Port is between 1 and 65535. Was actually <" + _serverPort + ">"); + _serverPort = _server.getURI().getPort(); // Uncomment to have server start and continue to run (without exiting) // System.err.printf("Listening to port %d%n",this.serverPort); @@ -379,6 +325,7 @@ public void start() throws Exception public void stop() throws Exception { - _server.stop(); + LifeCycle.stop(_server); + _properties.clear(); } } From 249679f9b5bb6247bc722d5ab862b29cd778dc6d Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 27 Nov 2024 08:55:16 -0600 Subject: [PATCH 09/22] Fix javadoc --- .../deploy/providers/ContextProvider.java | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index 7028e45ad550..34140a910165 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -52,40 +52,40 @@ import org.slf4j.LoggerFactory; /** - * The webapps directory scanning provider. - * *

        This provider scans one or more directories (typically "webapps") for contexts to - * * deploy, which may be: - * *

        - * *
          - * *
        • A standard WAR file (must end in ".war")
        • - * *
        • A directory containing an expanded WAR file
        • - * *
        • A directory containing static content
        • - * *
        • An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance
        • - * *
        - * *

        To avoid double deployments and allow flexibility of the content of the scanned directories, the provider - * * implements some heuristics to ignore some files found in the scans: - * *

        - * *
          - * *
        • Hidden files (starting with {@code "."}) are ignored
        • - * *
        • Directories with names ending in {@code ".d"} are ignored
        • - * *
        • Property files with names ending in {@code ".properties"} are not deployed.
        • - * *
        • If a directory and a WAR file exist (eg: {@code foo/} and {@code foo.war}) then the directory is assumed to be - * * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)
        • - * *
        • If a directory and a matching XML file exist (eg: {@code foo/} and {@code foo.xml}) then the directory is assumed to be - * * an unpacked WAR and only the XML is deployed (which may used the directory in its configuration)
        • - * *
        • If a WAR file and a matching XML exist (eg: {@code foo.war} and {@code foo.xml}) then the WAR is assumed to - * * be configured by the XML and only the XML is deployed. - * *
        - * *

        For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and - * * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". - * * The properties will be initialized with: - * *

        - * *
          - * *
        • The properties set on the application via {@link App#getProperties()}
        • - * *
        • The app specific properties file {@code webapps/.properties}
        • - * *
        • The environment specific properties file {@code webapps/[-zzz].properties}
        • - * *
        • The {@link Attributes} from the {@link Environment}
        • - * *
        + *

        Jetty Environment WebApp Hot Deployment Provider.

        + * + *

        This provider scans one or more directories (typically "webapps") for contexts to + * deploy, which may be:

        + *
          + *
        • A standard WAR file (must end in ".war")
        • + *
        • A directory containing an expanded WAR file
        • + *
        • A directory containing static content
        • + *
        • An XML descriptor in {@link XmlConfiguration} format that configures a {@link ContextHandler} instance
        • + *
        + *

        To avoid double deployments and allow flexibility of the content of the scanned directories, the provider + * implements some heuristics to ignore some files found in the scans: + *

        + *
          + *
        • Hidden files (starting with {@code "."}) are ignored
        • + *
        • Directories with names ending in {@code ".d"} are ignored
        • + *
        • Property files with names ending in {@code ".properties"} are not deployed.
        • + *
        • If a directory and a WAR file exist (eg: {@code foo/} and {@code foo.war}) then the directory is assumed to be + * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)
        • + *
        • If a directory and a matching XML file exist (eg: {@code foo/} and {@code foo.xml}) then the directory is assumed to be + * an unpacked WAR and only the XML is deployed (which may used the directory in its configuration)
        • + *
        • If a WAR file and a matching XML exist (eg: {@code foo.war} and {@code foo.xml}) then the WAR is assumed to + * be configured by the XML and only the XML is deployed. + *
        + *

        For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and + * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". + * The properties will be initialized with: + *

        + *
          + *
        • The properties set on the application via {@link App#getProperties()}
        • + *
        • The app specific properties file {@code webapps/.properties}
        • + *
        • The environment specific properties file {@code webapps/[-zzz].properties}
        • + *
        • The {@link Attributes} from the {@link Environment}
        • + *
        */ @ManagedObject("Provider for start-up deployment of webapps based on presence in directory") public class ContextProvider extends ScanningAppProvider From bef04d9888b0fd7ea72a5a49bd5566196d769d42 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 27 Nov 2024 08:55:25 -0600 Subject: [PATCH 10/22] Fix method names --- .../src/main/config/etc/jetty-ee10-deploy.xml | 6 +++--- .../src/main/config/etc/jetty-ee11-deploy.xml | 6 +++--- .../src/main/config/etc/jetty-ee8-deploy.xml | 6 +++--- .../src/main/config/etc/jetty-ee9-deploy.xml | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml b/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml index 019157c06ce5..19c0107e843a 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml +++ b/jetty-ee10/jetty-ee10-webapp/src/main/config/etc/jetty-ee10-deploy.xml @@ -26,8 +26,8 @@ .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$
        - - - + + +
        diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml b/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml index eeccefecff4a..876c9407dfaa 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml +++ b/jetty-ee11/jetty-ee11-webapp/src/main/config/etc/jetty-ee11-deploy.xml @@ -26,8 +26,8 @@ .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$
        - - - + + +
        diff --git a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml index fdb720ccb634..d54f7394043c 100644 --- a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml +++ b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml @@ -26,8 +26,8 @@ .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$
        - - - + + +
        diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml index 4ff9703d3569..506b7f427b69 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml +++ b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml @@ -26,8 +26,8 @@ .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$
        - - - + + +
        From add337ae1fb7d43503dd20a9aa9d760472d04351 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 27 Nov 2024 10:15:29 -0600 Subject: [PATCH 11/22] Fixing Environment contextClass loading --- .../jetty/deploy/providers/ContextProvider.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index 34140a910165..7a43df7fb15f 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -135,7 +135,7 @@ public ContextHandler createContextHandler(final App app) throws Exception } if (LOG.isDebugEnabled()) - LOG.debug("createContextHandler {} in {}", app, environment); + LOG.debug("createContextHandler {} in {}", app, environment.getName()); ClassLoader old = Thread.currentThread().getContextClassLoader(); try @@ -157,7 +157,17 @@ public ContextHandler createContextHandler(final App app) throws Exception // applying the environment xml file. String contextHandlerClassName = (String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); if (contextHandlerClassName != null) - context = Class.forName(contextHandlerClassName).getDeclaredConstructor().newInstance(); + { + Class contextClass = Loader.loadClass(contextHandlerClassName); + if (contextClass == null) + { + throw new IllegalStateException("Unknown ContextHandler class " + contextHandlerClassName + " for " + app + " in environment " + environment.getName()); + } + else + { + context = contextClass.getDeclaredConstructor().newInstance(); + } + } // Collect the optional environment context xml files. // Order them according to the name of their property key names. From 2e9660d233d060d65f7e897dfb141c64b92f6832 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 27 Nov 2024 15:09:35 -0600 Subject: [PATCH 12/22] Fixing test cases --- .../deploy/providers/ContextProvider.java | 12 +- .../org/eclipse/jetty/server/Deployable.java | 1 + .../src/test/resources/RFC2616Base.xml | 191 +++++++++--------- .../src/test/resources/deploy.xml | 70 +++---- .../src/test/resources/RFC2616Base.xml | 36 ++-- .../src/test/resources/deploy.xml | 37 +--- .../src/test/resources/RFC2616Base.xml | 36 ++-- .../src/test/resources/deploy.xml | 35 +--- 8 files changed, 177 insertions(+), 241 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index 7a43df7fb15f..04555a07f61a 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -229,7 +229,8 @@ else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) // Build the web application if necessary if (context == null) { - contextHandlerClassName = (String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); + // Create the webapp from the default context class + contextHandlerClassName = (String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT); if (StringUtil.isBlank(contextHandlerClassName)) throw new IllegalStateException("No ContextHandler classname for " + app); Class contextHandlerClass = Loader.loadClass(contextHandlerClassName); @@ -752,12 +753,17 @@ public void setContainerScanJarPattern(String pattern) * instances (can be class that implements {@code java.util.function.Supplier} * as well). * + *

        + * This is the fallback class used, if the context class itself isn't defined by + * the web application being deployed. + *

        + * * @param classname the classname for this environment's context deployable. - * @see Deployable#CONTEXT_HANDLER_CLASS + * @see Deployable#CONTEXT_HANDLER_CLASS_DEFAULT */ public void setContextHandlerClass(String classname) { - environment.setAttribute(Deployable.CONTEXT_HANDLER_CLASS, classname); + environment.setAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT, classname); } /** diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java index 947b2668a74c..b42ff8ecbf3d 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java @@ -59,6 +59,7 @@ public interface Deployable String CONTAINER_SCAN_JARS = "jetty.deploy.containerScanJarPattern"; String CONTEXT_PATH = "jetty.deploy.contextPath"; String CONTEXT_HANDLER_CLASS = "jetty.deploy.contextHandlerClass"; + String CONTEXT_HANDLER_CLASS_DEFAULT = "jetty.deploy.default.contextHandlerClass"; String DEFAULTS_DESCRIPTOR = "jetty.deploy.defaultsDescriptor"; String ENVIRONMENT = "environment"; String ENVIRONMENT_XML = "jetty.deploy.environmentXml"; diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/RFC2616Base.xml b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/RFC2616Base.xml index 12e88f004ae1..379bd1560e3f 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/RFC2616Base.xml +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/RFC2616Base.xml @@ -1,5 +1,4 @@ - - + @@ -9,124 +8,122 @@ - - - https - - 32768 - 8192 - 8192 - true - true - 1024 - - - - RFC2616 - - + + https + + + + 32768 + 8192 + 8192 + true + true + 1024 - - - - - - - - - - - - - /tests - - - VirtualHost - - - /virtualhost - - virtual - - - - - /tests - /default - - default - - - - - /echo - - echo - - - - - - - ee10 + + - + + + + + - - - - - - - + + + + /tests + + + VirtualHost + + + /virtualhost + + + + + virtual + + + + + /tests + /default + + + + + default + + + + + /echo + + + + echo + + + + + + + + + ee10 + + + + + + + + + + + - ee10 src/test/resources/ webapp-contexts/RFC2616/ - 1 - true - - + + + ee10 + true + org.eclipse.jetty.ee10.webapp.WebAppContext + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - - - - - - - +
        + + + + - - - + + + diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/deploy.xml b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/deploy.xml index 23fe67b511e6..efb34d961c13 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/deploy.xml +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-integration/src/test/resources/deploy.xml @@ -1,16 +1,9 @@ - - + ee10 - - - contextHandlerClass - org.eclipse.jetty.ee10.webapp.WebAppContext - - @@ -18,43 +11,34 @@ - - - - - ee10 - - - target - webapps - - - 1 - true + + + + + + target + webapps + + + 1 - - - - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - - - - - - - - - - - - - - + + ee10 + org.eclipse.jetty.ee10.webapp.WebAppContext + true + + + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + + + + + + + + + diff --git a/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/resources/RFC2616Base.xml b/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/resources/RFC2616Base.xml index 757a2ca049db..62951771d39f 100644 --- a/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/resources/RFC2616Base.xml +++ b/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/resources/RFC2616Base.xml @@ -1,5 +1,4 @@ - - + @@ -78,12 +77,6 @@ ee11 - - - contextHandlerClass - org.eclipse.jetty.ee11.webapp.WebAppContext - - @@ -95,32 +88,27 @@ - ee11 src/test/resources/ webapp-contexts/RFC2616/ - 1 - true - - + + + ee11 + org.eclipse.jetty.ee11.webapp.WebAppContext + true + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - - - - - - - + + + + + diff --git a/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/resources/deploy.xml b/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/resources/deploy.xml index 3cec2d0d357b..dd84a5fa4912 100644 --- a/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/resources/deploy.xml +++ b/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/resources/deploy.xml @@ -1,16 +1,9 @@ - - + ee11 - - - contextHandlerClass - org.eclipse.jetty.ee11.webapp.WebAppContext - - @@ -22,7 +15,6 @@ - ee11 target @@ -31,30 +23,23 @@ 1 - true - - + + ee11 + org.eclipse.jetty.ee11.webapp.WebAppContext + true + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - - - - - - - + + + + + - - - diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/RFC2616Base.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/RFC2616Base.xml index 127923777ca8..51235bd714a7 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/RFC2616Base.xml +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/RFC2616Base.xml @@ -1,5 +1,4 @@ - - + @@ -71,12 +70,6 @@ ee9 - - - contextHandlerClass - org.eclipse.jetty.ee9.webapp.WebAppContext - - @@ -88,32 +81,27 @@ - ee9 src/test/resources/ webapp-contexts/RFC2616/ - 1 - true - - + + + ee9 + org.eclipse.jetty.ee9.webapp.WebAppContext + true + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - - - - - - - + + + + + diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/deploy.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/deploy.xml index 1656b8d75ed0..3e19a33ba574 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/deploy.xml +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/deploy.xml @@ -1,16 +1,9 @@ - - + ee9 - - - contextHandlerClass - org.eclipse.jetty.ee9.webapp.WebAppContext - - @@ -22,33 +15,27 @@ - ee9 target webapps - 1 - true - - + + ee9 + org.eclipse.jetty.ee9.webapp.WebAppContext + true + .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ - - - - - - - - - - - + + + + + From 9bfab80f1290d76f476a1d01a499948cf86db1cf Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 27 Nov 2024 17:05:50 -0600 Subject: [PATCH 13/22] Fixing quickstart XML references to provider --- .../main/config/etc/jetty-ee10-quickstart.xml | 25 +++++++++++++----- .../main/config/etc/jetty-ee11-quickstart.xml | 26 +++++++++++++------ .../main/config/etc/jetty-ee8-quickstart.xml | 25 +++++++++++++----- .../main/config/etc/jetty-ee9-quickstart.xml | 25 +++++++++++++----- 4 files changed, 72 insertions(+), 29 deletions(-) diff --git a/jetty-ee10/jetty-ee10-quickstart/src/main/config/etc/jetty-ee10-quickstart.xml b/jetty-ee10/jetty-ee10-quickstart/src/main/config/etc/jetty-ee10-quickstart.xml index a08f0c808c3a..0acd3b375cff 100644 --- a/jetty-ee10/jetty-ee10-quickstart/src/main/config/etc/jetty-ee10-quickstart.xml +++ b/jetty-ee10/jetty-ee10-quickstart/src/main/config/etc/jetty-ee10-quickstart.xml @@ -7,12 +7,23 @@ - - - - - - - + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml + + + +
        diff --git a/jetty-ee11/jetty-ee11-quickstart/src/main/config/etc/jetty-ee11-quickstart.xml b/jetty-ee11/jetty-ee11-quickstart/src/main/config/etc/jetty-ee11-quickstart.xml index 3ff08b63d59e..bfe26866106e 100644 --- a/jetty-ee11/jetty-ee11-quickstart/src/main/config/etc/jetty-ee11-quickstart.xml +++ b/jetty-ee11/jetty-ee11-quickstart/src/main/config/etc/jetty-ee11-quickstart.xml @@ -7,12 +7,22 @@ - - - - - - - - + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml + + + +
        diff --git a/jetty-ee8/jetty-ee8-quickstart/src/main/config/etc/jetty-ee8-quickstart.xml b/jetty-ee8/jetty-ee8-quickstart/src/main/config/etc/jetty-ee8-quickstart.xml index 4a36d28e5564..c1f1a38fd5e4 100644 --- a/jetty-ee8/jetty-ee8-quickstart/src/main/config/etc/jetty-ee8-quickstart.xml +++ b/jetty-ee8/jetty-ee8-quickstart/src/main/config/etc/jetty-ee8-quickstart.xml @@ -7,12 +7,23 @@ - - - - - - - + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml + + + + diff --git a/jetty-ee9/jetty-ee9-quickstart/src/main/config/etc/jetty-ee9-quickstart.xml b/jetty-ee9/jetty-ee9-quickstart/src/main/config/etc/jetty-ee9-quickstart.xml index e47a22440b1a..33421816d96d 100644 --- a/jetty-ee9/jetty-ee9-quickstart/src/main/config/etc/jetty-ee9-quickstart.xml +++ b/jetty-ee9/jetty-ee9-quickstart/src/main/config/etc/jetty-ee9-quickstart.xml @@ -7,12 +7,23 @@ - - - - - - - + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml + + + + From 0e534f73d24c541ab193c6a9ec8c0d30bc86a58a Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 2 Dec 2024 12:26:27 -0600 Subject: [PATCH 14/22] Fixing quickstart XML --- .../deploy/providers/ContextProvider.java | 26 +++++++------ .../main/config/etc/jetty-ee10-quickstart.xml | 38 ++++++++++--------- .../main/config/etc/jetty-ee11-quickstart.xml | 37 +++++++++--------- .../main/config/etc/jetty-ee8-quickstart.xml | 38 ++++++++++--------- .../main/config/etc/jetty-ee9-quickstart.xml | 38 ++++++++++--------- .../quickstart/QuickStartConfiguration.java | 1 - 6 files changed, 95 insertions(+), 83 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index 04555a07f61a..8097ccab1bd8 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -103,16 +103,14 @@ public ContextProvider() private static Map asProperties(Attributes attributes) { Map props = new HashMap<>(); - attributes.getAttributeNameSet().stream() - .map((name) -> + attributes.getAttributeNameSet().forEach((name) -> { - // undo old prefixed entries - if (name.startsWith(Deployable.ATTRIBUTE_PREFIX)) - return name.substring(Deployable.ATTRIBUTE_PREFIX.length()); - else - return name; - }) - .forEach((name) -> props.put(name, Objects.toString(attributes.getAttribute(name)))); + Object value = attributes.getAttribute(name); + String key = name.startsWith(Deployable.ATTRIBUTE_PREFIX) + ? name.substring(Deployable.ATTRIBUTE_PREFIX.length()) + : name; + props.put(key, Objects.toString(value)); + }); return props; } @@ -495,8 +493,14 @@ else if (Supplier.class.isAssignableFrom(context.getClass())) // pass through properties as attributes directly attributes.getAttributeNameSet().stream() .filter((name) -> name.startsWith(Deployable.ATTRIBUTE_PREFIX)) - .map((name) -> name.substring(Deployable.ATTRIBUTE_PREFIX.length())) - .forEach((name) -> contextHandler.setAttribute(name, attributes.getAttribute(name))); + .forEach((name) -> + { + Object value = attributes.getAttribute(name); + String key = name.substring(Deployable.ATTRIBUTE_PREFIX.length()); + if (LOG.isDebugEnabled()) + LOG.debug("Setting attribute [{}] to [{}] in context {}", key, value, contextHandler); + contextHandler.setAttribute(key, value); + }); String contextPath = (String)attributes.getAttribute(Deployable.CONTEXT_PATH); if (StringUtil.isNotBlank(contextPath)) diff --git a/jetty-ee10/jetty-ee10-quickstart/src/main/config/etc/jetty-ee10-quickstart.xml b/jetty-ee10/jetty-ee10-quickstart/src/main/config/etc/jetty-ee10-quickstart.xml index 0acd3b375cff..c92702f7a51c 100644 --- a/jetty-ee10/jetty-ee10-quickstart/src/main/config/etc/jetty-ee10-quickstart.xml +++ b/jetty-ee10/jetty-ee10-quickstart/src/main/config/etc/jetty-ee10-quickstart.xml @@ -7,23 +7,25 @@ - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode - - - + + ee10 + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml + + + + - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin - - - - - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml - - - - - diff --git a/jetty-ee11/jetty-ee11-quickstart/src/main/config/etc/jetty-ee11-quickstart.xml b/jetty-ee11/jetty-ee11-quickstart/src/main/config/etc/jetty-ee11-quickstart.xml index bfe26866106e..555a385fc059 100644 --- a/jetty-ee11/jetty-ee11-quickstart/src/main/config/etc/jetty-ee11-quickstart.xml +++ b/jetty-ee11/jetty-ee11-quickstart/src/main/config/etc/jetty-ee11-quickstart.xml @@ -7,22 +7,25 @@ - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode - - - - - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin - - - - - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml - - - + + ee11 + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml + + + + diff --git a/jetty-ee8/jetty-ee8-quickstart/src/main/config/etc/jetty-ee8-quickstart.xml b/jetty-ee8/jetty-ee8-quickstart/src/main/config/etc/jetty-ee8-quickstart.xml index c1f1a38fd5e4..8eacb3b00bce 100644 --- a/jetty-ee8/jetty-ee8-quickstart/src/main/config/etc/jetty-ee8-quickstart.xml +++ b/jetty-ee8/jetty-ee8-quickstart/src/main/config/etc/jetty-ee8-quickstart.xml @@ -7,23 +7,25 @@ - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode - - - + + ee9 + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml + + + + - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin - - - - - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml - - - - - diff --git a/jetty-ee9/jetty-ee9-quickstart/src/main/config/etc/jetty-ee9-quickstart.xml b/jetty-ee9/jetty-ee9-quickstart/src/main/config/etc/jetty-ee9-quickstart.xml index 33421816d96d..3215a9ca1241 100644 --- a/jetty-ee9/jetty-ee9-quickstart/src/main/config/etc/jetty-ee9-quickstart.xml +++ b/jetty-ee9/jetty-ee9-quickstart/src/main/config/etc/jetty-ee9-quickstart.xml @@ -7,23 +7,25 @@ - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode - - - + + ee9 + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.mode + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin + + + + + + jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml + + + + - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.origin - - - - - - jetty.deploy.attribute.org.eclipse.jetty.quickstart.xml - - - - - diff --git a/jetty-ee9/jetty-ee9-quickstart/src/main/java/org/eclipse/jetty/ee9/quickstart/QuickStartConfiguration.java b/jetty-ee9/jetty-ee9-quickstart/src/main/java/org/eclipse/jetty/ee9/quickstart/QuickStartConfiguration.java index 01fcf87f4035..dc3d0a9d709b 100644 --- a/jetty-ee9/jetty-ee9-quickstart/src/main/java/org/eclipse/jetty/ee9/quickstart/QuickStartConfiguration.java +++ b/jetty-ee9/jetty-ee9-quickstart/src/main/java/org/eclipse/jetty/ee9/quickstart/QuickStartConfiguration.java @@ -32,7 +32,6 @@ import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; -import org.eclipse.jetty.util.resource.Resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 43eb6798fbf7780f23e2160c99303f59022e7524 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 2 Dec 2024 16:22:30 -0600 Subject: [PATCH 15/22] Fixing deploy containerScanJarPattern for ee9/ee8 --- .../jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml | 2 +- .../jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml index d54f7394043c..f1b3b32fa3cd 100644 --- a/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml +++ b/jetty-ee8/jetty-ee8-webapp/src/main/config/etc/jetty-ee8-deploy.xml @@ -23,7 +23,7 @@ - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + .*/jetty-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-[^/]*\.jar|.*jsp.jstl-[^/]*\.jar diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml index 506b7f427b69..0de7b4693173 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml +++ b/jetty-ee9/jetty-ee9-webapp/src/main/config/etc/jetty-ee9-deploy.xml @@ -23,7 +23,7 @@ - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + .*/jetty-jakarta-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ From a363189b34102ed2016d97d1f24e6a82b82077f8 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 18 Dec 2024 13:38:38 -0600 Subject: [PATCH 16/22] Changes from reviews. Preparing for BulkListener impl. --- .../deploy/providers/ContextProvider.java | 121 ++++++++++-------- .../deploy/providers/ScanningAppProvider.java | 5 +- .../providers/ContextProviderStartupTest.java | 16 ++- 3 files changed, 77 insertions(+), 65 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index 8097ccab1bd8..bd4645d3aa02 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -49,6 +49,7 @@ import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.xml.XmlConfiguration; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** @@ -72,16 +73,21 @@ *
      • If a directory and a WAR file exist (eg: {@code foo/} and {@code foo.war}) then the directory is assumed to be * the unpacked WAR and only the WAR is deployed (which may reused the unpacked directory)
      • *
      • If a directory and a matching XML file exist (eg: {@code foo/} and {@code foo.xml}) then the directory is assumed to be - * an unpacked WAR and only the XML is deployed (which may used the directory in its configuration)
      • + * an unpacked WAR and only the XML is deployed (which may use the directory in its configuration) *
      • If a WAR file and a matching XML exist (eg: {@code foo.war} and {@code foo.xml}) then the WAR is assumed to * be configured by the XML and only the XML is deployed. *
      - *

      For XML configured contexts, the ID map will contain a reference to the {@link Server} instance called "Server" and - * properties for the webapp file such as "jetty.webapp" and directory as "jetty.webapps". - * The properties will be initialized with: + *

      For XML configured contexts, the following is available.

      + *
        + *
      • The XML Object ID Map will have a reference to the {@link Server} instance via the ID name {@code "Server"}
      • + *
      • The Default XML Properties are populated from a call to {@link XmlConfiguration#setJettyStandardIdsAndProperties(Object, Path)} (for things like {@code jetty.home} and {@code jetty.base})
      • + *
      • An extra XML Property named {@code "jetty.webapps"} is available, and points to the monitored path.
      • + *
      + *

      + * Context Deployment properties will be initialized with: *

      *
        - *
      • The properties set on the application via {@link App#getProperties()}
      • + *
      • The properties set on the application via embedded calls modifying {@link App#getProperties()}
      • *
      • The app specific properties file {@code webapps/.properties}
      • *
      • The environment specific properties file {@code webapps/[-zzz].properties}
      • *
      • The {@link Attributes} from the {@link Environment}
      • @@ -90,8 +96,8 @@ @ManagedObject("Provider for start-up deployment of webapps based on presence in directory") public class ContextProvider extends ScanningAppProvider { - private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ContextProvider.class); - private String defaultEnvironmentName; + private static final Logger LOG = LoggerFactory.getLogger(ContextProvider.class); + private String _defaultEnvironmentName; public ContextProvider() { @@ -202,45 +208,33 @@ public ContextHandler createContextHandler(final App app) throws Exception if (FileID.isXml(path)) { context = applyXml(context, path, environment, appAttributes); - - // Look for the contextHandler itself - ContextHandler contextHandler = null; - if (context instanceof ContextHandler c) - contextHandler = c; - else if (context instanceof Supplier supplier) - { - Object nestedContext = supplier.get(); - if (nestedContext instanceof ContextHandler c) - contextHandler = c; - } - if (contextHandler == null) - throw new IllegalStateException("Unknown context type of " + context); - - return contextHandler; } // Otherwise it must be a directory or an archive else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) { throw new IllegalStateException("unable to create ContextHandler for " + app); } - - // Build the web application if necessary - if (context == null) + else { - // Create the webapp from the default context class - contextHandlerClassName = (String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT); - if (StringUtil.isBlank(contextHandlerClassName)) - throw new IllegalStateException("No ContextHandler classname for " + app); - Class contextHandlerClass = Loader.loadClass(contextHandlerClassName); - if (contextHandlerClass == null) - throw new IllegalStateException("Unknown ContextHandler class " + contextHandlerClassName + " for " + app); - - context = contextHandlerClass.getDeclaredConstructor().newInstance(); + // If we reach this point, we don't have a Context XML Deployable. + // We are either a directory or a web archive (war) + // Set the WAR attribute to point to this directory or web-archive. + appAttributes.setAttribute(Deployable.WAR, path.toString()); } - //set a backup value for the path to the war in case it hasn't already been set - appAttributes.setAttribute(Deployable.WAR, path.toString()); - return initializeContextHandler(context, path, appAttributes); + // Look for the contextHandler itself + ContextHandler contextHandler = getContextHandler(context); + if (contextHandler == null) + throw new IllegalStateException("Unknown context type of " + context); + + // Allow raw ContextHandler to be initialized properly + initializeContextHandler(contextHandler, path, appAttributes); + + // Initialize deployable + if (contextHandler instanceof Deployable deployable) + deployable.initializeDefaults(appAttributes); + + return contextHandler; } finally { @@ -261,19 +255,19 @@ else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) */ public String getDefaultEnvironmentName() { - if (defaultEnvironmentName == null) + if (_defaultEnvironmentName == null) { return Environment.getAll().stream() .map(Environment::getName) .max(Deployable.ENVIRONMENT_COMPARATOR) .orElse(null); } - return defaultEnvironmentName; + return _defaultEnvironmentName; } public void setDefaultEnvironmentName(String name) { - this.defaultEnvironmentName = name; + this._defaultEnvironmentName = name; } @Deprecated @@ -354,14 +348,23 @@ protected Object applyXml(Object context, Path xml, Environment environment, Att public void initializeDefaults(Object context) { super.initializeDefaults(context); - ContextProvider.this.initializeContextHandler(context, xml, attributes); + ContextHandler contextHandler = getContextHandler(context); + if (contextHandler == null) + { + if (LOG.isDebugEnabled()) + LOG.debug("Not a ContextHandler: Not initializing Context {}", context); + } + else + { + ContextProvider.this.initializeContextHandler(contextHandler, xml, attributes); + } } }; xmlc.getIdMap().put("Environment", environment.getName()); xmlc.setJettyStandardIdsAndProperties(getDeploymentManager().getServer(), xml); - // Put all Environment attributes into XMLC as properties that can be used. + // Put all Environment attributes into XmlConfiguration as properties that can be used. attributes.getAttributeNameSet() .stream() .filter(k -> !k.startsWith("jetty.home") && @@ -459,10 +462,8 @@ protected ClassLoader findCoreContextClassLoader(Path path) throws IOException return new URLClassLoader(urls.toArray(new URL[0]), Environment.CORE.getClassLoader()); } - protected ContextHandler initializeContextHandler(Object context, Path path, Attributes attributes) + private ContextHandler getContextHandler(Object context) { - if (LOG.isDebugEnabled()) - LOG.debug("initializeContextHandler {}", context); // find the ContextHandler ContextHandler contextHandler; if (context instanceof ContextHandler handler) @@ -480,14 +481,25 @@ else if (Supplier.class.isAssignableFrom(context.getClass())) return null; } + return contextHandler; + } + + protected void initializeContextHandler(ContextHandler contextHandler, Path path, Attributes attributes) + { + if (LOG.isDebugEnabled()) + LOG.debug("initializeContextHandler {}", contextHandler); + assert contextHandler != null; initializeContextPath(contextHandler, path); - if (Files.isDirectory(path)) + if (contextHandler.getBaseResource() == null) { - ResourceFactory resourceFactory = ResourceFactory.of(contextHandler); - contextHandler.setBaseResource(resourceFactory.newResource(path)); + if (Files.isDirectory(path)) + { + ResourceFactory resourceFactory = ResourceFactory.of(contextHandler); + contextHandler.setBaseResource(resourceFactory.newResource(path)); + } } // pass through properties as attributes directly @@ -505,11 +517,6 @@ else if (Supplier.class.isAssignableFrom(context.getClass())) String contextPath = (String)attributes.getAttribute(Deployable.CONTEXT_PATH); if (StringUtil.isNotBlank(contextPath)) contextHandler.setContextPath(contextPath); - - if (context instanceof Deployable deployable) - deployable.initializeDefaults(attributes); - - return contextHandler; } protected void initializeContextPath(ContextHandler context, Path path) @@ -537,15 +544,17 @@ else if (StringUtil.asciiStartsWithIgnoreCase(contextPath, "root-")) contextPath = "/" + contextPath; // Set the display name and context Path - context.setDisplayName(basename); - context.setContextPath(contextPath); + if (StringUtil.isBlank(context.getDisplayName())) + context.setDisplayName(basename); + if (StringUtil.isBlank(context.getContextPath())) + context.setContextPath(contextPath); } protected boolean isDeployable(Path path) { String basename = FileID.getBasename(path); - //is the file that changed a directory? + // is the file that changed a directory? if (Files.isDirectory(path)) { // deploy if there is not a .xml or .war file of the same basename? diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java index 283190714090..a79a13610e42 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java @@ -125,9 +125,8 @@ protected Map getDeployedApps() * Isolated in a method so that it is possible to override the default App * object for specialized implementations of the AppProvider. * - * @param path The file that is the context.xml. It is resolved by - * {@link org.eclipse.jetty.util.resource.ResourceFactory#newResource(String)} - * @return The App object for this particular context definition file. + * @param path The file that the main point of deployment (eg: a context XML, a WAR file, a directory, etc) + * @return The App object for this particular context. */ protected App createApp(Path path) { diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java index dceaeb90eea5..57c55f1c7643 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.deploy.providers; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -212,7 +213,7 @@ public void testPropertyOverriding() throws Exception public void testPropertySubstitution() throws Exception { Path jettyBase = jetty.getJettyBasePath(); - Path propsCoreAFile = Files.writeString(jettyBase.resolve("webapps/core.properties"), Deployable.ENVIRONMENT_XML + " = etc/core-context-sub.xml\ntest.displayName=DisplayName Set By Property", StandardOpenOption.CREATE_NEW); + Files.writeString(jettyBase.resolve("webapps/core.properties"), Deployable.ENVIRONMENT_XML + " = etc/core-context-sub.xml\ntest.displayName=DisplayName Set By Property", StandardOpenOption.CREATE_NEW); Files.copy(MavenPaths.findTestResourceFile("etc/core-context-sub.xml"), jettyBase.resolve("etc/core-context-sub.xml"), StandardCopyOption.REPLACE_EXISTING); jetty.copyWebapp("bar-core-context.properties", "bar.properties"); startJetty(); @@ -223,10 +224,13 @@ public void testPropertySubstitution() throws Exception private static void writeXmlDisplayName(Path filePath, String displayName) throws IOException { - String content = "\n" + - "\n" + - " "; - - Files.writeString(filePath, content + displayName + "\n", StandardOpenOption.CREATE_NEW); + String content = """ + + + @NAME@ + + """.replace("@NAME@", displayName); + + Files.writeString(filePath, content, StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW); } } From 5f6d504e485cc3f1bd64f5c5ffdfbe83d9c41be6 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 18 Dec 2024 13:49:53 -0600 Subject: [PATCH 17/22] Using BulkListener with sorted paths for alphabetical deployment. --- .../deploy/providers/ScanningAppProvider.java | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java index a79a13610e42..fbe309035b28 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; @@ -36,6 +37,7 @@ import org.eclipse.jetty.util.annotation.ManagedOperation; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.resource.PathCollators; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.resource.Resources; @@ -57,24 +59,28 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements private boolean _useRealPaths; private boolean _deferInitialScan = false; - private final Scanner.DiscreteListener _scannerListener = new Scanner.DiscreteListener() + private final Scanner.BulkListener _scannerBulkListener = new Scanner.BulkListener() { @Override - public void pathAdded(Path path) throws Exception + public void pathsChanged(Set paths) throws Exception { - ScanningAppProvider.this.pathAdded(path); - } + List sortedPaths = paths.stream() + .sorted(PathCollators.byName(true)) + .toList(); - @Override - public void pathChanged(Path path) throws Exception - { - ScanningAppProvider.this.pathChanged(path); + for (Path path : sortedPaths) + { + if (Files.exists(path)) + ScanningAppProvider.this.pathChanged(path); + else + ScanningAppProvider.this.pathRemoved(path); + } } @Override - public void pathRemoved(Path path) throws Exception + public void filesChanged(Set filenames) throws Exception { - ScanningAppProvider.this.pathRemoved(path); + // ignore, as we are using the pathsChanged() technique only. } }; @@ -176,7 +182,7 @@ protected void doStart() throws Exception _scanner.setFilenameFilter(_filenameFilter); _scanner.setReportDirs(true); _scanner.setScanDepth(1); //consider direct dir children of monitored dir - _scanner.addListener(_scannerListener); + _scanner.addListener(_scannerBulkListener); _scanner.setReportExistingFilesOnStartup(true); _scanner.setAutoStartScanning(!_deferInitialScan); addBean(_scanner); @@ -211,7 +217,7 @@ protected void doStop() throws Exception if (_scanner != null) { removeBean(_scanner); - _scanner.removeListener(_scannerListener); + _scanner.removeListener(_scannerBulkListener); _scanner = null; } } From 77c34c2a0d4a909f5884ab811ba77acf09abb92a Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 18 Dec 2024 13:54:40 -0600 Subject: [PATCH 18/22] Changes from review --- .../deploy/providers/ContextProvider.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index bd4645d3aa02..42f00819677f 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -718,11 +718,11 @@ public static class EnvironmentConfig // syntax to skip setting of an environment attribute if property is unset, // allowing the in code values to be same defaults as they are in embedded-jetty. - private final Environment environment; + private final Environment _environment; private EnvironmentConfig(Environment environment) { - this.environment = environment; + this._environment = environment; } /** @@ -745,9 +745,9 @@ public void setConfigurationClasses(String configurations) public void setConfigurationClasses(String[] configurations) { if (configurations == null) - environment.removeAttribute(Deployable.CONFIGURATION_CLASSES); + _environment.removeAttribute(Deployable.CONFIGURATION_CLASSES); else - environment.setAttribute(Deployable.CONFIGURATION_CLASSES, configurations); + _environment.setAttribute(Deployable.CONFIGURATION_CLASSES, configurations); } /** @@ -758,7 +758,7 @@ public void setConfigurationClasses(String[] configurations) */ public void setContainerScanJarPattern(String pattern) { - environment.setAttribute(Deployable.CONTAINER_SCAN_JARS, pattern); + _environment.setAttribute(Deployable.CONTAINER_SCAN_JARS, pattern); } /** @@ -776,7 +776,7 @@ public void setContainerScanJarPattern(String pattern) */ public void setContextHandlerClass(String classname) { - environment.setAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT, classname); + _environment.setAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT, classname); } /** @@ -788,7 +788,7 @@ public void setContextHandlerClass(String classname) */ public void setDefaultsDescriptor(String defaultsDescriptor) { - environment.setAttribute(Deployable.DEFAULTS_DESCRIPTOR, defaultsDescriptor); + _environment.setAttribute(Deployable.DEFAULTS_DESCRIPTOR, defaultsDescriptor); } /** @@ -799,7 +799,7 @@ public void setDefaultsDescriptor(String defaultsDescriptor) */ public void setExtractWars(boolean extractWars) { - environment.setAttribute(Deployable.EXTRACT_WARS, extractWars); + _environment.setAttribute(Deployable.EXTRACT_WARS, extractWars); } /** @@ -810,7 +810,7 @@ public void setExtractWars(boolean extractWars) */ public void setParentLoaderPriority(boolean parentLoaderPriority) { - environment.setAttribute(Deployable.PARENT_LOADER_PRIORITY, parentLoaderPriority); + _environment.setAttribute(Deployable.PARENT_LOADER_PRIORITY, parentLoaderPriority); } /** @@ -821,7 +821,7 @@ public void setParentLoaderPriority(boolean parentLoaderPriority) */ public void setServletContainerInitializerExclusionPattern(String pattern) { - environment.setAttribute(Deployable.SCI_EXCLUSION_PATTERN, pattern); + _environment.setAttribute(Deployable.SCI_EXCLUSION_PATTERN, pattern); } /** @@ -832,7 +832,7 @@ public void setServletContainerInitializerExclusionPattern(String pattern) */ public void setServletContainerInitializerOrder(String order) { - environment.setAttribute(Deployable.SCI_ORDER, order); + _environment.setAttribute(Deployable.SCI_ORDER, order); } /** @@ -843,7 +843,7 @@ public void setServletContainerInitializerOrder(String order) */ public void setWebInfScanJarPattern(String pattern) { - environment.setAttribute(Deployable.WEBINF_SCAN_JARS, pattern); + _environment.setAttribute(Deployable.WEBINF_SCAN_JARS, pattern); } } From 5306c3d612b469b2313f3814189828a3c4e4e566 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 18 Dec 2024 17:11:14 -0600 Subject: [PATCH 19/22] More changes from review --- .../deploy/providers/ContextProvider.java | 60 +++++---- .../deploy/providers/ScanningAppProvider.java | 15 ++- .../jetty/osgi/AbstractContextProvider.java | 119 ++---------------- .../org/eclipse/jetty/server/Deployable.java | 4 - .../jetty/ee10/osgi/boot/EE10Activator.java | 8 +- .../jetty/ee10/webapp/WebAppContext.java | 38 +----- .../jetty/ee11/osgi/boot/EE11Activator.java | 8 +- .../test/websocket/JakartaWebSocketTest.java | 1 - .../jetty/ee11/webapp/WebAppContext.java | 34 +---- .../jetty/ee9/osgi/boot/EE9Activator.java | 8 +- .../jetty/ee9/webapp/WebAppContext.java | 33 +---- 11 files changed, 77 insertions(+), 251 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index 42f00819677f..89dd8f52db29 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -154,24 +154,11 @@ public ContextHandler createContextHandler(final App app) throws Exception // prepare app attributes to use for app deployment Attributes appAttributes = initAttributes(environment, app); - Object context = null; - // check if there is a specific ContextHandler type to create set in the // properties associated with the webapp. If there is, we create it _before_ // applying the environment xml file. - String contextHandlerClassName = (String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS); - if (contextHandlerClassName != null) - { - Class contextClass = Loader.loadClass(contextHandlerClassName); - if (contextClass == null) - { - throw new IllegalStateException("Unknown ContextHandler class " + contextHandlerClassName + " for " + app + " in environment " + environment.getName()); - } - else - { - context = contextClass.getDeclaredConstructor().newInstance(); - } - } + Object context = newContextInstance((String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS), app); + initializeContextPath(getContextHandler(context), path); // Collect the optional environment context xml files. // Order them according to the name of their property key names. @@ -222,6 +209,17 @@ else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) appAttributes.setAttribute(Deployable.WAR, path.toString()); } + if (context == null) + { + context = newContextInstance((String)environment.getAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT), app); + initializeContextPath(getContextHandler(context), path); + } + + if (context == null) + { + throw new IllegalStateException("ContextHandler class is null for app " + app); + } + // Look for the contextHandler itself ContextHandler contextHandler = getContextHandler(context); if (contextHandler == null) @@ -242,6 +240,22 @@ else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) } } + private Object newContextInstance(String contextHandlerClassName, App app) throws Exception + { + if (StringUtil.isBlank(contextHandlerClassName)) + return null; + + Class contextClass = Loader.loadClass(contextHandlerClassName); + if (contextClass == null) + { + throw new IllegalStateException("Unknown ContextHandler class " + contextHandlerClassName + " for " + app + " in environment " + app.getEnvironmentName()); + } + else + { + return contextClass.getConstructor().newInstance(); + } + } + /** * Get the default {@link Environment} name for discovered web applications that * do not declare the {@link Environment} that they belong to. @@ -356,6 +370,7 @@ public void initializeDefaults(Object context) } else { + ContextProvider.this.initializeContextPath(contextHandler, xml); ContextProvider.this.initializeContextHandler(contextHandler, xml, attributes); } } @@ -464,6 +479,9 @@ protected ClassLoader findCoreContextClassLoader(Path path) throws IOException private ContextHandler getContextHandler(Object context) { + if (context == null) + return null; + // find the ContextHandler ContextHandler contextHandler; if (context instanceof ContextHandler handler) @@ -491,8 +509,6 @@ protected void initializeContextHandler(ContextHandler contextHandler, Path path assert contextHandler != null; - initializeContextPath(contextHandler, path); - if (contextHandler.getBaseResource() == null) { if (Files.isDirectory(path)) @@ -521,6 +537,9 @@ protected void initializeContextHandler(ContextHandler contextHandler, Path path protected void initializeContextPath(ContextHandler context, Path path) { + if (context == null) + return; + // Strip any 3 char extension from non directories String basename = FileID.getBasename(path); String contextPath = basename; @@ -543,11 +562,8 @@ else if (StringUtil.asciiStartsWithIgnoreCase(contextPath, "root-")) if (contextPath.charAt(0) != '/') contextPath = "/" + contextPath; - // Set the display name and context Path - if (StringUtil.isBlank(context.getDisplayName())) - context.setDisplayName(basename); - if (StringUtil.isBlank(context.getContextPath())) - context.setContextPath(contextPath); + context.setDisplayName(basename); + context.setContextPath(contextPath); } protected boolean isDeployable(Path path) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java index fbe309035b28..3ae35d5c4b39 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -61,6 +62,8 @@ public abstract class ScanningAppProvider extends ContainerLifeCycle implements private final Scanner.BulkListener _scannerBulkListener = new Scanner.BulkListener() { + private final Set _addedPaths = new HashSet<>(); + @Override public void pathsChanged(Set paths) throws Exception { @@ -71,14 +74,22 @@ public void pathsChanged(Set paths) throws Exception for (Path path : sortedPaths) { if (Files.exists(path)) - ScanningAppProvider.this.pathChanged(path); + { + if (_addedPaths.add(path)) + ScanningAppProvider.this.pathAdded(path); + else + ScanningAppProvider.this.pathChanged(path); + } else + { + _addedPaths.remove(path); ScanningAppProvider.this.pathRemoved(path); + } } } @Override - public void filesChanged(Set filenames) throws Exception + public void filesChanged(Set filenames) { // ignore, as we are using the pathsChanged() technique only. } diff --git a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/AbstractContextProvider.java b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/AbstractContextProvider.java index 1cdb626997ad..1e1e2ad7148f 100644 --- a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/AbstractContextProvider.java +++ b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/AbstractContextProvider.java @@ -13,16 +13,14 @@ package org.eclipse.jetty.osgi; -import java.util.HashMap; -import java.util.Map; import java.util.Objects; import org.eclipse.jetty.deploy.App; import org.eclipse.jetty.deploy.AppProvider; import org.eclipse.jetty.deploy.DeploymentManager; -import org.eclipse.jetty.server.Deployable; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.osgi.framework.Bundle; @@ -46,7 +44,7 @@ public abstract class AbstractContextProvider extends AbstractLifeCycle implemen private Server _server; private ContextFactory _contextFactory; private String _environment; - private final Map _properties = new HashMap<>(); + private final Attributes _attributes = new Attributes.Mapped(); public AbstractContextProvider(String environment, Server server, ContextFactory contextFactory) { @@ -60,9 +58,9 @@ public Server getServer() return _server; } - public Map getProperties() + public Attributes getAttributes() { - return _properties; + return _attributes; } @Override @@ -82,10 +80,12 @@ public void setDeploymentManager(DeploymentManager deploymentManager) { _deploymentManager = deploymentManager; } - + @Override public String getEnvironmentName() { + // TODO: when AppProvider.getEnvironmentName is eventually removed, leave this method here for + // these OSGI based AppProviders to use. return _environment; } @@ -94,110 +94,13 @@ public DeploymentManager getDeploymentManager() return _deploymentManager; } - /** - * Get the extractWars. - * This is equivalent to getting the {@link Deployable#EXTRACT_WARS} property. - * - * @return the extractWars - */ - public boolean isExtractWars() - { - return Boolean.parseBoolean(_properties.get(Deployable.EXTRACT_WARS)); - } - - /** - * Set the extractWars. - * This is equivalent to setting the {@link Deployable#EXTRACT_WARS} property. - * - * @param extractWars the extractWars to set - */ - public void setExtractWars(boolean extractWars) - { - _properties.put(Deployable.EXTRACT_WARS, Boolean.toString(extractWars)); - } - - /** - * Get the parentLoaderPriority. - * This is equivalent to getting the {@link Deployable#PARENT_LOADER_PRIORITY} property. - * - * @return the parentLoaderPriority - */ - public boolean isParentLoaderPriority() - { - return Boolean.parseBoolean(_properties.get(Deployable.PARENT_LOADER_PRIORITY)); - } - - /** - * Set the parentLoaderPriority. - * This is equivalent to setting the {@link Deployable#PARENT_LOADER_PRIORITY} property. - * - * @param parentLoaderPriority the parentLoaderPriority to set - */ - public void setParentLoaderPriority(boolean parentLoaderPriority) - { - _properties.put(Deployable.PARENT_LOADER_PRIORITY, Boolean.toString(parentLoaderPriority)); - } - - /** - * Get the defaultsDescriptor. - * This is equivalent to getting the {@link Deployable#DEFAULTS_DESCRIPTOR} property. - * - * @return the defaultsDescriptor - */ - public String getDefaultsDescriptor() - { - return _properties.get(Deployable.DEFAULTS_DESCRIPTOR); - } - - /** - * Set the defaultsDescriptor. - * This is equivalent to setting the {@link Deployable#DEFAULTS_DESCRIPTOR} property. - * - * @param defaultsDescriptor the defaultsDescriptor to set - */ - public void setDefaultsDescriptor(String defaultsDescriptor) - { - _properties.put(Deployable.DEFAULTS_DESCRIPTOR, defaultsDescriptor); - } - - /** - * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} property. - * @param configurations The configuration class names as a comma separated list - */ - public void setConfigurationClasses(String configurations) - { - setConfigurationClasses(StringUtil.isBlank(configurations) ? null : configurations.split(",")); - } - - /** - * This is equivalent to setting the {@link Deployable#CONFIGURATION_CLASSES} property. - * @param configurations The configuration class names. - */ - public void setConfigurationClasses(String[] configurations) - { - _properties.put(Deployable.CONFIGURATION_CLASSES, (configurations == null) - ? null - : String.join(",", configurations)); - } - - /** - * - * This is equivalent to getting the {@link Deployable#CONFIGURATION_CLASSES} property. - * @return The configuration class names. - */ - public String[] getConfigurationClasses() - { - String cc = _properties.get(Deployable.CONFIGURATION_CLASSES); - return cc == null ? new String[0] : cc.split(","); - } - /** * @param tldBundles Comma separated list of bundles that contain tld jars * that should be setup on the context instances created here. */ public void setTldBundles(String tldBundles) { - _properties.put(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, tldBundles); + _attributes.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, tldBundles); } /** @@ -206,7 +109,7 @@ public void setTldBundles(String tldBundles) */ public String getTldBundles() { - return _properties.get(OSGiWebappConstants.REQUIRE_TLD_BUNDLE); + return (String)_attributes.getAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE); } public boolean isDeployable(Bundle bundle) @@ -220,8 +123,8 @@ public boolean isDeployable(Bundle bundle) return false; } - - public boolean isDeployable(ServiceReference service) + + public boolean isDeployable(ServiceReference service) { if (service == null) return false; diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java index b42ff8ecbf3d..7cd65f5621b2 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Deployable.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.server; import java.util.Comparator; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -70,8 +69,5 @@ public interface Deployable String WAR = "jetty.deploy.war"; String WEBINF_SCAN_JARS = "jetty.deploy.webInfScanJarPattern"; - // TODO: should we deprecate this one? - void initializeDefaults(Map properties); - void initializeDefaults(Attributes attributes); } diff --git a/jetty-ee10/jetty-ee10-osgi/jetty-ee10-osgi-boot/src/main/java/org/eclipse/jetty/ee10/osgi/boot/EE10Activator.java b/jetty-ee10/jetty-ee10-osgi/jetty-ee10-osgi-boot/src/main/java/org/eclipse/jetty/ee10/osgi/boot/EE10Activator.java index bb1a880a02b6..232c43599a9a 100644 --- a/jetty-ee10/jetty-ee10-osgi/jetty-ee10-osgi-boot/src/main/java/org/eclipse/jetty/ee10/osgi/boot/EE10Activator.java +++ b/jetty-ee10/jetty-ee10-osgi/jetty-ee10-osgi-boot/src/main/java/org/eclipse/jetty/ee10/osgi/boot/EE10Activator.java @@ -170,8 +170,8 @@ public Object addingService(ServiceReference sr) //ensure the providers are configured with the extra bundles that must be scanned from the container classpath if (containerScanBundlePattern != null) { - contextProvider.getProperties().put(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); - webAppProvider.getProperties().put(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); + contextProvider.getAttributes().setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); + webAppProvider.getAttributes().setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); } } else @@ -364,7 +364,7 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App WebAppContext webApp = new WebAppContext(); //Apply defaults from the deployer providers - webApp.initializeDefaults(provider.getProperties()); + webApp.initializeDefaults(provider.getAttributes()); // provides access to core classes ClassLoader coreLoader = (ClassLoader)osgiApp.getDeploymentManager().getServer().getAttribute(OSGiServerConstants.SERVER_CLASSLOADER); @@ -418,7 +418,7 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App webApp.setClassLoader(webAppLoader); //Take care of extra provider properties - webApp.setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, provider.getProperties().get(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN)); + webApp.setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, provider.getAttributes().getAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN)); //TODO needed? webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles); diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java index 3bb3e7fb406d..727592c6d6ba 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java +++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java @@ -56,7 +56,6 @@ import org.eclipse.jetty.util.ClassMatcher; import org.eclipse.jetty.util.ExceptionUtil; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; @@ -209,41 +208,6 @@ public WebAppContext(String contextPath, SessionHandler sessionHandler, Security setProtectedTargets(__dftProtectedTargets); } - @Override - public void initializeDefaults(Map properties) - { - for (String property : properties.keySet()) - { - String value = properties.get(property); - if (LOG.isDebugEnabled()) - LOG.debug("init {}: {}", property, value); - - switch (property) - { - case Deployable.WAR -> - { - if (getWar() == null) - setWar(value); - } - case Deployable.TEMP_DIR -> setTempDirectory(IO.asFile(value)); - case Deployable.CONFIGURATION_CLASSES -> setConfigurationClasses(value == null ? null : value.split(",")); - case Deployable.CONTAINER_SCAN_JARS -> setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, value); - case Deployable.EXTRACT_WARS -> setExtractWAR(Boolean.parseBoolean(value)); - case Deployable.PARENT_LOADER_PRIORITY -> setParentLoaderPriority(Boolean.parseBoolean(value)); - case Deployable.WEBINF_SCAN_JARS -> setAttribute(MetaInfConfiguration.WEBINF_JAR_PATTERN, value); - case Deployable.DEFAULTS_DESCRIPTOR -> setDefaultsDescriptor(value); - case Deployable.SCI_EXCLUSION_PATTERN -> setAttribute("org.eclipse.jetty.containerInitializerExclusionPattern", value); - case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value); - default -> - { - if (LOG.isDebugEnabled() && StringUtil.isNotBlank(value)) - LOG.debug("unknown property {}={}", property, value); - } - } - } - _defaultContextPath = true; - } - @Override public void initializeDefaults(Attributes attributes) { @@ -271,7 +235,7 @@ public void initializeDefaults(Attributes attributes) case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value); default -> { - if (LOG.isDebugEnabled() && value != null) + if (LOG.isDebugEnabled()) LOG.debug("unknown property {}={}", keyName, value); } } diff --git a/jetty-ee11/jetty-ee11-osgi/jetty-ee11-osgi-boot/src/main/java/org/eclipse/jetty/ee11/osgi/boot/EE11Activator.java b/jetty-ee11/jetty-ee11-osgi/jetty-ee11-osgi-boot/src/main/java/org/eclipse/jetty/ee11/osgi/boot/EE11Activator.java index 2782247b908b..0982628430f4 100644 --- a/jetty-ee11/jetty-ee11-osgi/jetty-ee11-osgi-boot/src/main/java/org/eclipse/jetty/ee11/osgi/boot/EE11Activator.java +++ b/jetty-ee11/jetty-ee11-osgi/jetty-ee11-osgi-boot/src/main/java/org/eclipse/jetty/ee11/osgi/boot/EE11Activator.java @@ -170,8 +170,8 @@ public Object addingService(ServiceReference sr) //ensure the providers are configured with the extra bundles that must be scanned from the container classpath if (containerScanBundlePattern != null) { - contextProvider.getProperties().put(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); - webAppProvider.getProperties().put(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); + contextProvider.getAttributes().setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); + webAppProvider.getAttributes().setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); } } else @@ -364,7 +364,7 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App WebAppContext webApp = new WebAppContext(); //Apply defaults from the deployer providers - webApp.initializeDefaults(provider.getProperties()); + webApp.initializeDefaults(provider.getAttributes()); // provides access to core classes ClassLoader coreLoader = (ClassLoader)osgiApp.getDeploymentManager().getServer().getAttribute(OSGiServerConstants.SERVER_CLASSLOADER); @@ -418,7 +418,7 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App webApp.setClassLoader(webAppLoader); //Take care of extra provider properties - webApp.setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, provider.getProperties().get(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN)); + webApp.setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, provider.getAttributes().getAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN)); //TODO needed? webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles); diff --git a/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/java/org/eclipse/jetty/ee11/test/websocket/JakartaWebSocketTest.java b/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/java/org/eclipse/jetty/ee11/test/websocket/JakartaWebSocketTest.java index fb03595abf44..8b3d0b684e99 100644 --- a/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/java/org/eclipse/jetty/ee11/test/websocket/JakartaWebSocketTest.java +++ b/jetty-ee11/jetty-ee11-tests/jetty-ee11-test-integration/src/test/java/org/eclipse/jetty/ee11/test/websocket/JakartaWebSocketTest.java @@ -45,7 +45,6 @@ public static void setUpServer() throws Exception server.addXmlConfiguration("NIOHttp.xml"); server.load(); - // server.getServer().setDumpAfterStart(true); server.start(); } diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java index 98679e3bde27..c208582faa22 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java +++ b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java @@ -56,7 +56,6 @@ import org.eclipse.jetty.util.ClassMatcher; import org.eclipse.jetty.util.ExceptionUtil; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; @@ -195,37 +194,6 @@ public WebAppContext(String contextPath, SessionHandler sessionHandler, Security setProtectedTargets(__dftProtectedTargets); } - @Override - public void initializeDefaults(Map properties) - { - for (String property : properties.keySet()) - { - String value = properties.get(property); - if (LOG.isDebugEnabled()) - LOG.debug("init {}: {}", property, value); - - switch (property) - { - case Deployable.WAR -> setWar(value); - case Deployable.TEMP_DIR -> setTempDirectory(IO.asFile(value)); - case Deployable.CONFIGURATION_CLASSES -> setConfigurationClasses(value == null ? null : value.split(",")); - case Deployable.CONTAINER_SCAN_JARS -> setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, value); - case Deployable.EXTRACT_WARS -> setExtractWAR(Boolean.parseBoolean(value)); - case Deployable.PARENT_LOADER_PRIORITY -> setParentLoaderPriority(Boolean.parseBoolean(value)); - case Deployable.WEBINF_SCAN_JARS -> setAttribute(MetaInfConfiguration.WEBINF_JAR_PATTERN, value); - case Deployable.DEFAULTS_DESCRIPTOR -> setDefaultsDescriptor(value); - case Deployable.SCI_EXCLUSION_PATTERN -> setAttribute("org.eclipse.jetty.containerInitializerExclusionPattern", value); - case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value); - default -> - { - if (LOG.isDebugEnabled() && StringUtil.isNotBlank(value)) - LOG.debug("unknown property {}={}", property, value); - } - } - } - _defaultContextPath = true; - } - @Override public void initializeDefaults(Attributes attributes) { @@ -253,7 +221,7 @@ public void initializeDefaults(Attributes attributes) case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value); default -> { - if (LOG.isDebugEnabled() && value != null) + if (LOG.isDebugEnabled()) LOG.debug("unknown property {}={}", keyName, value); } } diff --git a/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/EE9Activator.java b/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/EE9Activator.java index af543c6dcdfe..cb28ef215bb8 100644 --- a/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/EE9Activator.java +++ b/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/EE9Activator.java @@ -169,8 +169,8 @@ public Object addingService(ServiceReference sr) //ensure the providers are configured with the extra bundles that must be scanned from the container classpath if (containerScanBundlePattern != null) { - contextProvider.getProperties().put(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); - webAppProvider.getProperties().put(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); + contextProvider.getAttributes().setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); + webAppProvider.getAttributes().setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, containerScanBundlePattern); } } else @@ -362,7 +362,7 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App WebAppContext webApp = new WebAppContext(); //Apply defaults from the deployer providers - webApp.initializeDefaults(provider.getProperties()); + webApp.initializeDefaults(provider.getAttributes()); // provides access to core classes ClassLoader coreLoader = (ClassLoader)osgiApp.getDeploymentManager().getServer().getAttribute(OSGiServerConstants.SERVER_CLASSLOADER); @@ -415,7 +415,7 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App webApp.setClassLoader(webAppLoader); //Take care of extra provider properties - webApp.setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, provider.getProperties().get(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN)); + webApp.setAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN, provider.getAttributes().getAttribute(OSGiMetaInfConfiguration.CONTAINER_BUNDLE_PATTERN)); //TODO needed? webApp.setAttribute(OSGiWebappConstants.REQUIRE_TLD_BUNDLE, requireTldBundles); diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java index 89be9babf757..8a1a696a1ede 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java +++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java @@ -57,7 +57,6 @@ import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.ExceptionUtil; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ClassLoaderDump; @@ -239,36 +238,6 @@ public WebAppContext(Handler.Container parent, String contextPath, SessionHandle HandlerWrapper.setAsParent(parent, this.get()); } - @Override - public void initializeDefaults(Map properties) - { - for (String property : properties.keySet()) - { - String value = properties.get(property); - if (LOG.isDebugEnabled()) - LOG.debug("init {}: {}", property, value); - switch (property) - { - case Deployable.WAR -> setWar(value); - case Deployable.TEMP_DIR -> setTempDirectory(IO.asFile(value)); - case Deployable.CONFIGURATION_CLASSES -> setConfigurationClasses(value == null ? null : value.split(",")); - case Deployable.CONTAINER_SCAN_JARS -> setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN, value); - case Deployable.EXTRACT_WARS -> setExtractWAR(Boolean.parseBoolean(value)); - case Deployable.PARENT_LOADER_PRIORITY -> setParentLoaderPriority(Boolean.parseBoolean(value)); - case Deployable.WEBINF_SCAN_JARS -> setAttribute(MetaInfConfiguration.WEBINF_JAR_PATTERN, value); - case Deployable.DEFAULTS_DESCRIPTOR -> setDefaultsDescriptor(value); - case Deployable.SCI_EXCLUSION_PATTERN -> setAttribute("org.eclipse.jetty.containerInitializerExclusionPattern", value); - case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value); - default -> - { - if (LOG.isDebugEnabled() && StringUtil.isNotBlank(value)) - LOG.debug("unknown property {}={}", property, value); - } - } - } - _defaultContextPath = true; - } - @Override public void initializeDefaults(Attributes attributes) { @@ -296,7 +265,7 @@ public void initializeDefaults(Attributes attributes) case Deployable.SCI_ORDER -> setAttribute("org.eclipse.jetty.containerInitializerOrder", value); default -> { - if (LOG.isDebugEnabled() && value != null) + if (LOG.isDebugEnabled()) LOG.debug("unknown property {}={}", keyName, value); } } From 96d5becd82ed36bce29cf61b29b5106e4ab8276f Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 19 Dec 2024 12:36:13 -0600 Subject: [PATCH 20/22] Simplify else --- .../org/eclipse/jetty/deploy/providers/ContextProvider.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index 89dd8f52db29..fae882d5773f 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -250,10 +250,7 @@ private Object newContextInstance(String contextHandlerClassName, App app) throw { throw new IllegalStateException("Unknown ContextHandler class " + contextHandlerClassName + " for " + app + " in environment " + app.getEnvironmentName()); } - else - { - return contextClass.getConstructor().newInstance(); - } + return contextClass.getConstructor().newInstance(); } /** From ee188456cfdddee6a68c5e40612f5b8749b4ac82 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 20 Dec 2024 15:10:21 -0600 Subject: [PATCH 21/22] Reworking deploy.createContextHandler to be easier to follow --- .../jetty/deploy/DeploymentManager.java | 3 +- .../deploy/providers/ContextProvider.java | 242 +++++++++++------- .../providers/ContextProviderStartupTest.java | 7 +- .../ee9/test/support/XmlBasedJettyServer.java | 2 +- .../test/websocket/JakartaWebSocketTest.java | 1 - .../src/test/resources/deploy.xml | 2 +- 6 files changed, 159 insertions(+), 98 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java index dce0331884c4..1b65b486f09d 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java @@ -153,7 +153,8 @@ public String getDefaultEnvironmentName() */ public void addApp(App app) { - LOG.info("addApp: {}", app); + if (LOG.isDebugEnabled()) + LOG.debug("addApp: {}", app); AppEntry entry = new AppEntry(); entry.app = app; entry.setLifeCycleNode(_lifecycle.getNodeByName("undeployed")); diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index fae882d5773f..e05663151a87 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -154,103 +154,165 @@ public ContextHandler createContextHandler(final App app) throws Exception // prepare app attributes to use for app deployment Attributes appAttributes = initAttributes(environment, app); - // check if there is a specific ContextHandler type to create set in the - // properties associated with the webapp. If there is, we create it _before_ - // applying the environment xml file. - Object context = newContextInstance((String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS), app); - initializeContextPath(getContextHandler(context), path); - - // Collect the optional environment context xml files. - // Order them according to the name of their property key names. - List sortedEnvXmlPaths = appAttributes.getAttributeNameSet() - .stream() - .filter(k -> k.startsWith(Deployable.ENVIRONMENT_XML)) - .map(k -> - { - Path envXmlPath = Paths.get((String)appAttributes.getAttribute(k)); - if (!envXmlPath.isAbsolute()) - { - Path monitoredPath = getMonitoredDirResource().getPath(); - // not all Resource implementations support java.nio.file.Path. - if (monitoredPath != null) - { - envXmlPath = monitoredPath.getParent().resolve(envXmlPath); - } - } - return envXmlPath; - }) - .filter(Files::isRegularFile) - .sorted(PathCollators.byName(true)) - .toList(); - - // apply each environment context xml file - for (Path envXmlPath : sortedEnvXmlPaths) - { - if (LOG.isDebugEnabled()) - LOG.debug("Applying environment specific context file {}", envXmlPath); - context = applyXml(context, envXmlPath, environment, appAttributes); - } - - // Handle a context XML file - if (FileID.isXml(path)) - { - context = applyXml(context, path, environment, appAttributes); - } - // Otherwise it must be a directory or an archive - else if (!Files.isDirectory(path) && !FileID.isWebArchive(path)) + /* + * The process now is to figure out the context object to use. + * This can come from a number of places. + * 1. If an XML deployable, this is the entry. + * 2. If another deployable (like a web archive, or directory), then check attributes. + * a. use the app attributes to figure out the context handler class. + * b. use the environment attributes default context handler class. + */ + Object context = newContextInstance(environment, app, appAttributes, path); + if (context == null) { throw new IllegalStateException("unable to create ContextHandler for " + app); } - else - { - // If we reach this point, we don't have a Context XML Deployable. - // We are either a directory or a web archive (war) - // Set the WAR attribute to point to this directory or web-archive. - appAttributes.setAttribute(Deployable.WAR, path.toString()); - } + if (LOG.isDebugEnabled()) + LOG.debug("Context {} created from app {}", context.getClass().getName(), app); - if (context == null) + // Apply environment properties and XML to context + if (applyEnvironmentXml(context, environment, appAttributes)) { - context = newContextInstance((String)environment.getAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT), app); - initializeContextPath(getContextHandler(context), path); + // If an XML deployable, apply full XML over environment XML changes + if (FileID.isXml(path)) + context = applyXml(context, path, environment, appAttributes); } - if (context == null) - { - throw new IllegalStateException("ContextHandler class is null for app " + app); - } + // Set a backup value for the path to the war in case it hasn't already been set + // via a different means. This is especially important for a deployable App + // that is only a .war file (no XML). The eventual WebInfConfiguration + // will use this attribute. + appAttributes.setAttribute(Deployable.WAR, path.toString()); + + // Initialize any deployable + if (context instanceof Deployable deployable) + deployable.initializeDefaults(appAttributes); - // Look for the contextHandler itself + return getContextHandler(context); + } + finally + { + Thread.currentThread().setContextClassLoader(old); + } + } + + /** + * Initialize a new Context object instance. + * + *

        + * The search order is: + *

        + *
          + *
        1. If app attribute {@link Deployable#CONTEXT_HANDLER_CLASS} is specified, use it, and initialize context
        2. + *
        3. If App deployable path is XML, apply XML {@code }
        4. + *
        5. Fallback to environment attribute {@link Deployable#CONTEXT_HANDLER_CLASS_DEFAULT}, and initialize context.
        6. + *
        + * + * @param environment the environment context applies to + * @param app the App for the context + * @param appAttributes the Attributes for the App + * @param path the path of the deployable + * @return the Context Object. + * @throws Exception if unable to create Object instance. + */ + private Object newContextInstance(Environment environment, App app, Attributes appAttributes, Path path) throws Exception + { + Object context = newInstance((String)appAttributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS)); + if (context != null) + { ContextHandler contextHandler = getContextHandler(context); if (contextHandler == null) throw new IllegalStateException("Unknown context type of " + context); - // Allow raw ContextHandler to be initialized properly + initializeContextPath(contextHandler, path); initializeContextHandler(contextHandler, path, appAttributes); + return context; + } - // Initialize deployable - if (contextHandler instanceof Deployable deployable) - deployable.initializeDefaults(appAttributes); - - return contextHandler; + if (FileID.isXml(path)) + { + context = applyXml(null, path, environment, appAttributes); + ContextHandler contextHandler = getContextHandler(context); + if (contextHandler == null) + throw new IllegalStateException("Unknown context type of " + context); + return context; } - finally + + // fallback to default from environment. + context = newInstance((String)environment.getAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT)); + + if (context != null) { - Thread.currentThread().setContextClassLoader(old); + ContextHandler contextHandler = getContextHandler(context); + if (contextHandler == null) + throw new IllegalStateException("Unknown context type of " + context); + + initializeContextPath(contextHandler, path); + initializeContextHandler(contextHandler, path, appAttributes); + return context; } + + return null; } - private Object newContextInstance(String contextHandlerClassName, App app) throws Exception + private Object newInstance(String className) throws Exception { - if (StringUtil.isBlank(contextHandlerClassName)) + if (StringUtil.isBlank(className)) + return null; + if (LOG.isDebugEnabled()) + LOG.debug("Attempting to load class {}", className); + Class clazz = Loader.loadClass(className); + if (clazz == null) return null; + return clazz.getConstructor().newInstance(); + } - Class contextClass = Loader.loadClass(contextHandlerClassName); - if (contextClass == null) + /** + * Apply optional environment specific XML to context. + * + * @param context the context to apply environment specific behavior to + * @param environment the environment to use + * @param appAttributes the attributes of the app + * @return true it environment specific XML was applied. + * @throws Exception if unable to apply environment configuration. + */ + private boolean applyEnvironmentXml(Object context, Environment environment, Attributes appAttributes) throws Exception + { + // Collect the optional environment context xml files. + // Order them according to the name of their property key names. + List sortedEnvXmlPaths = appAttributes.getAttributeNameSet() + .stream() + .filter(k -> k.startsWith(Deployable.ENVIRONMENT_XML)) + .map(k -> + { + Path envXmlPath = Paths.get((String)appAttributes.getAttribute(k)); + if (!envXmlPath.isAbsolute()) + { + Path monitoredPath = getMonitoredDirResource().getPath(); + // not all Resource implementations support java.nio.file.Path. + if (monitoredPath != null) + { + envXmlPath = monitoredPath.getParent().resolve(envXmlPath); + } + } + return envXmlPath; + }) + .filter(Files::isRegularFile) + .sorted(PathCollators.byName(true)) + .toList(); + + boolean xmlApplied = false; + + // apply each environment context xml file + for (Path envXmlPath : sortedEnvXmlPaths) { - throw new IllegalStateException("Unknown ContextHandler class " + contextHandlerClassName + " for " + app + " in environment " + app.getEnvironmentName()); + if (LOG.isDebugEnabled()) + LOG.debug("Applying environment specific context file {}", envXmlPath); + context = applyXml(context, envXmlPath, environment, appAttributes); + xmlApplied = true; } - return contextClass.getConstructor().newInstance(); + + return xmlApplied; } /** @@ -474,29 +536,30 @@ protected ClassLoader findCoreContextClassLoader(Path path) throws IOException return new URLClassLoader(urls.toArray(new URL[0]), Environment.CORE.getClassLoader()); } + /** + * Find the {@link ContextHandler} for the provided {@link Object} + * + * @param context the raw context object + * @return the {@link ContextHandler} for the context, or null if no ContextHandler associated with context. + */ private ContextHandler getContextHandler(Object context) { if (context == null) return null; - // find the ContextHandler - ContextHandler contextHandler; if (context instanceof ContextHandler handler) - contextHandler = handler; - else if (Supplier.class.isAssignableFrom(context.getClass())) + return handler; + + if (Supplier.class.isAssignableFrom(context.getClass())) { @SuppressWarnings("unchecked") Supplier provider = (Supplier)context; - contextHandler = provider.get(); - } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("Not a context {}", context); - return null; + return provider.get(); } - return contextHandler; + if (LOG.isDebugEnabled()) + LOG.debug("Not a context {}", context); + return null; } protected void initializeContextHandler(ContextHandler contextHandler, Path path, Attributes attributes) @@ -506,13 +569,10 @@ protected void initializeContextHandler(ContextHandler contextHandler, Path path assert contextHandler != null; - if (contextHandler.getBaseResource() == null) + if (contextHandler.getBaseResource() == null && Files.isDirectory(path)) { - if (Files.isDirectory(path)) - { - ResourceFactory resourceFactory = ResourceFactory.of(contextHandler); - contextHandler.setBaseResource(resourceFactory.newResource(path)); - } + ResourceFactory resourceFactory = ResourceFactory.of(contextHandler); + contextHandler.setBaseResource(resourceFactory.newResource(path)); } // pass through properties as attributes directly diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java index 57c55f1c7643..723f45d9109d 100644 --- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java +++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java @@ -35,6 +35,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -112,7 +113,7 @@ public void testStartupWithRelativeEnvironmentContext() throws Exception ContextHandler context = jetty.getContextHandler("/bar"); assertNotNull(context); assertThat(context.getAttribute("core-context-0"), equalTo("core-context-0")); - assertTrue(context instanceof BarContextHandler); + assertThat(context, instanceOf(BarContextHandler.class)); //check core-context-other.xml was applied to the produced context assertThat(context.getAttribute("other"), equalTo("othervalue")); //check core-context-other.xml was applied AFTER core-context.xml @@ -138,7 +139,7 @@ public void testStartupWithAbsoluteEnvironmentContext() throws Exception ContextHandler context = jetty.getContextHandler("/bar"); assertNotNull(context); assertThat(context.getAttribute("core-context-0"), equalTo("core-context-0")); - assertTrue(context instanceof BarContextHandler); + assertThat(context, instanceOf(BarContextHandler.class)); //check core-context-other.xml was applied to the produced context assertThat(context.getAttribute("other"), equalTo("othervalue")); //check core-context-other.xml was applied AFTER core-context.xml @@ -164,7 +165,7 @@ public void testNonEnvironmentPropertyFileNotApplied() throws Exception //test that the context was deployed as expected and that the non-applicable properties files were ignored ContextHandler context = jetty.getContextHandler("/bar"); assertNotNull(context); - assertTrue(context instanceof BarContextHandler); + assertThat(context, instanceOf(BarContextHandler.class)); } /** diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/support/XmlBasedJettyServer.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/support/XmlBasedJettyServer.java index 78608b6c4a6a..0bea58b94e93 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/support/XmlBasedJettyServer.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/support/XmlBasedJettyServer.java @@ -176,7 +176,7 @@ public void start() throws Exception { assertNotNull(_server, "Server should not be null (failed load?)"); - _server.setDumpAfterStart(false); + _server.setDumpAfterStart(true); _server.start(); // Find the active server port. diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/websocket/JakartaWebSocketTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/websocket/JakartaWebSocketTest.java index e3fa0584b1a0..1e7696b04a2e 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/websocket/JakartaWebSocketTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/java/org/eclipse/jetty/ee9/test/websocket/JakartaWebSocketTest.java @@ -45,7 +45,6 @@ public static void setUpServer() throws Exception server.addXmlConfiguration("NIOHttp.xml"); server.load(); - // server.getServer().setDumpAfterStart(true); server.start(); } diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/deploy.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/deploy.xml index 3e19a33ba574..aea87fa1f578 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/deploy.xml +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-integration/src/test/resources/deploy.xml @@ -29,7 +29,7 @@ true - .*/jakarta.servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ + .*/jetty-jakarta-servlet-api-[^/]*\.jar$|.*jakarta.servlet.jsp.jstl-.*\.jar$ From e498f2c2c0181b03481fc95fe6bc7642d51750bf Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 20 Dec 2024 16:57:55 -0600 Subject: [PATCH 22/22] Fixing WebAppContext default-context-path mistake --- .../deploy/providers/ContextProvider.java | 18 +++++++++++++----- .../jetty/ee10/webapp/WebAppContext.java | 3 +-- .../jetty/ee11/webapp/WebAppContext.java | 3 +-- .../jetty/ee9/webapp/WebAppContext.java | 3 +-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index e05663151a87..4193d5bc454b 100644 --- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -589,12 +589,16 @@ protected void initializeContextHandler(ContextHandler contextHandler, Path path String contextPath = (String)attributes.getAttribute(Deployable.CONTEXT_PATH); if (StringUtil.isNotBlank(contextPath)) + { + if (LOG.isDebugEnabled()) + LOG.debug("Context {} initialized with contextPath: {}", contextHandler, contextPath); contextHandler.setContextPath(contextPath); + } } - protected void initializeContextPath(ContextHandler context, Path path) + protected void initializeContextPath(ContextHandler contextHandler, Path path) { - if (context == null) + if (contextHandler == null) return; // Strip any 3 char extension from non directories @@ -611,7 +615,7 @@ else if (StringUtil.asciiStartsWithIgnoreCase(contextPath, "root-")) { int dash = contextPath.indexOf('-'); String virtual = contextPath.substring(dash + 1); - context.setVirtualHosts(Arrays.asList(virtual.split(","))); + contextHandler.setVirtualHosts(Arrays.asList(virtual.split(","))); contextPath = "/"; } @@ -619,8 +623,12 @@ else if (StringUtil.asciiStartsWithIgnoreCase(contextPath, "root-")) if (contextPath.charAt(0) != '/') contextPath = "/" + contextPath; - context.setDisplayName(basename); - context.setContextPath(contextPath); + if (LOG.isDebugEnabled()) + LOG.debug("ContextHandler {} initialized with displayName: {}", contextHandler, basename); + contextHandler.setDisplayName(basename); + if (LOG.isDebugEnabled()) + LOG.debug("ContextHandler {} initialized with contextPath: {}", contextHandler, contextPath); + contextHandler.setContextPath(contextPath); } protected boolean isDeployable(Path path) diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java index 727592c6d6ba..7eb6ebb322ea 100644 --- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java +++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java @@ -236,11 +236,10 @@ public void initializeDefaults(Attributes attributes) default -> { if (LOG.isDebugEnabled()) - LOG.debug("unknown property {}={}", keyName, value); + LOG.debug("skipped init property {}={}", keyName, value); } } } - _defaultContextPath = true; } public boolean isContextPathDefault() diff --git a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java index c208582faa22..f95670ff9ae7 100644 --- a/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java +++ b/jetty-ee11/jetty-ee11-webapp/src/main/java/org/eclipse/jetty/ee11/webapp/WebAppContext.java @@ -222,11 +222,10 @@ public void initializeDefaults(Attributes attributes) default -> { if (LOG.isDebugEnabled()) - LOG.debug("unknown property {}={}", keyName, value); + LOG.debug("skipped init property {}={}", keyName, value); } } } - _defaultContextPath = true; } public boolean isContextPathDefault() diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java index 8a1a696a1ede..d6aff27746db 100644 --- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java +++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java @@ -266,11 +266,10 @@ public void initializeDefaults(Attributes attributes) default -> { if (LOG.isDebugEnabled()) - LOG.debug("unknown property {}={}", keyName, value); + LOG.debug("skipped init property {}={}", keyName, value); } } } - _defaultContextPath = true; } public boolean isContextPathDefault()