_properties = new HashMap<>();
- private ContextHandler _context;
-
- /**
- * Create an App with specified Origin ID and archivePath
- *
- * Any properties file that exists with the same {@link FileID#getBasename(Path)} as the
- * filename passed will be used to initialize the properties returned by {@link #getProperties()}.
- * @param manager the deployment manager
- * @param provider the app provider
- * @param path the path to the application directory, war file or XML descriptor
- * @see App#getContextPath()
- */
- public App(DeploymentManager manager, AppProvider provider, Path path)
- {
- _manager = manager;
- _provider = provider;
- _path = path;
-
- try
- {
- String basename = FileID.getBasename(path);
- Path properties = path.getParent().resolve(basename + ".properties");
- if (Files.exists(properties))
- {
- try (InputStream stream = Files.newInputStream(properties))
- {
- Properties p = new Properties();
- p.load(stream);
- p.stringPropertyNames().forEach(k -> _properties.put(k, p.getProperty(k)));
- }
- }
- }
- catch (Exception e)
- {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * @return The deployment manager
- */
- public DeploymentManager getDeploymentManager()
- {
- return _manager;
- }
-
- /**
- * @return The AppProvider
- */
- public AppProvider getAppProvider()
- {
- return _provider;
- }
-
- public Map getProperties()
- {
- return _properties;
- }
+ String getName();
/**
- * Get ContextHandler for the App.
- *
- * Create it if needed.
- *
- * @return the {@link ContextHandler} to use for the App when fully started.
- * (Portions of which might be ignored when App is not yet
- * {@link AppLifeCycle#DEPLOYED} or {@link AppLifeCycle#STARTED})
- * @throws Exception if unable to get the context handler
+ * Get the active ContextHandler for this App.
+ * The App is not responsible for creating the ContextHandler, the AppProvider is.
*/
- public ContextHandler getContextHandler() throws Exception
+ // TODO: make not default
+ default ContextHandler getContextHandler()
{
- if (_context == null)
- _context = getAppProvider().createContextHandler(this);
- return _context;
+ return null;
}
- /**
- * The context path {@link App} relating to how it is installed on the
- * jetty server side.
- *
- * @return the contextPath for the App
- */
- public String getContextPath()
- {
- return _context == null ? null : _context.getContextPath();
- }
-
- /**
- * Get the environment name.
- * @return The {@link org.eclipse.jetty.util.component.Environment} name for the application
- * if set with the {@link Deployable#ENVIRONMENT} property, else null.
- */
- public String getEnvironmentName()
- {
- return getProperties().get(Deployable.ENVIRONMENT);
- }
-
- /**
- * The origin of this {@link App} as specified by the {@link AppProvider}
- *
- * @return String representing the origin of this app.
- */
- public Path getPath()
- {
- return _path;
- }
-
- /**
- * Set the environment name.
- *
- * @param name the name of the environment.
- */
- public void setEnvironmentName(String name)
- {
- getProperties().put(Deployable.ENVIRONMENT, name);
- }
-
- @Override
- public String toString()
- {
- return "App@%x[%s,%s,%s]".formatted(hashCode(), getEnvironmentName(), _context, _path);
- }
+// private final DeploymentManager _manager;
+// private final AppProvider _provider;
+// private final Path _path;
+// private final Map _properties = new HashMap<>();
+// private ContextHandler _context;
+//
+// /**
+// * Create an App with specified Origin ID and archivePath
+// *
+// * Any properties file that exists with the same {@link FileID#getBasename(Path)} as the
+// * filename passed will be used to initialize the properties returned by {@link #getProperties()}.
+// *
+// * @param manager the deployment manager
+// * @param provider the app provider
+// * @param path the path to the application directory, war file or XML descriptor
+// * @see App#getContextPath()
+// */
+// public App(DeploymentManager manager, AppProvider provider, Path path)
+// {
+// _manager = manager;
+// _provider = provider;
+// _path = path;
+//
+// if (path != null)
+// {
+// try
+// {
+// String basename = FileID.getBasename(path);
+// Path properties = path.getParent().resolve(basename + ".properties");
+// if (Files.exists(properties))
+// {
+// try (InputStream stream = Files.newInputStream(properties))
+// {
+// Properties p = new Properties();
+// p.load(stream);
+// p.stringPropertyNames().forEach(k -> _properties.put(k, p.getProperty(k)));
+// }
+// }
+// }
+// catch (Exception e)
+// {
+// throw new RuntimeException(e);
+// }
+// }
+// }
+//
+// /**
+// * @return The deployment manager
+// */
+// public DeploymentManager getDeploymentManager()
+// {
+// return _manager;
+// }
+//
+// /**
+// * @return The AppProvider
+// */
+// public AppProvider getAppProvider()
+// {
+// return _provider;
+// }
+//
+// protected ContextHandler newContextHandler()
+// {
+// return getDeploymentManager().getContextHandlerFactory().newContextHandler(this);
+// }
+//
+// /**
+// * Get ContextHandler for the App.
+// *
+// * Create it if needed.
+// *
+// * @return the {@link ContextHandler} to use for the App when fully started.
+// * (Portions of which might be ignored when App is not yet
+// * {@link AppLifeCycle#DEPLOYED} or {@link AppLifeCycle#STARTED})
+// * @throws Exception if unable to get the context handler
+// */
+// public ContextHandler getContextHandler() throws Exception
+// {
+// if (_context == null)
+// _context = newContextHandler();
+// return _context;
+// }
+//
+// /**
+// * The context path {@link App} relating to how it is installed on the
+// * jetty server side.
+// *
+// * @return the contextPath for the App
+// */
+// public String getContextPath()
+// {
+// return _context == null ? null : _context.getContextPath();
+// }
+//
+// /**
+// * The origin of this {@link App} as specified by the {@link AppProvider}
+// *
+// * @return String representing the origin of this app.
+// */
+// public Path getPath()
+// {
+// return _path;
+// }
+//
+// @Override
+// public String toString()
+// {
+// return "App@%x[%s,%s,%s]".formatted(hashCode(), getEnvironmentName(), _context, _path);
+// }
}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppLifeCycle.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppLifeCycle.java
index 64911b144207..c59af5b4a0ac 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppLifeCycle.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppLifeCycle.java
@@ -39,7 +39,7 @@ public class AppLifeCycle extends Graph
private static final String ALL_NODES = "*";
- public static interface Binding
+ public interface Binding
{
/**
* Get a list of targets that this implementation should bind to.
@@ -55,7 +55,7 @@ public static interface Binding
* @param app the app being processed
* @throws Exception if any problem severe enough to halt the AppLifeCycle processing
*/
- void processBinding(Node node, App app) throws Exception;
+ void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception;
}
// Well known existing lifecycle Nodes
@@ -180,7 +180,7 @@ public void runBindings(Node node, App app, DeploymentManager deploymentManager)
{
if (LOG.isDebugEnabled())
LOG.debug("Calling {} for {}", binding.getClass().getName(), app);
- binding.processBinding(node, app);
+ binding.processBinding(deploymentManager, node, app);
}
}
}
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 e65e07e3920b..4a8447d17baf 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
@@ -15,7 +15,6 @@
import java.io.IOException;
-import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.component.LifeCycle;
/**
@@ -39,15 +38,5 @@ public interface AppProvider extends LifeCycle
* @throws IOException if unable to create context
* @throws Exception if unable to create context
*/
- 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.
- */
- @Deprecated(forRemoval = true, since = "12.1.0")
- default String getEnvironmentName()
- {
- return "";
- }
+ // ContextHandler createContextHandler(App app) throws Exception;
}
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 c630e7444638..ba2074bb07a4 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
@@ -13,14 +13,11 @@
package org.eclipse.jetty.deploy;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -32,8 +29,6 @@
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;
import org.eclipse.jetty.util.ExceptionUtil;
@@ -42,7 +37,6 @@
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
-import org.eclipse.jetty.util.component.Environment;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -71,13 +65,6 @@ public class DeploymentManager extends ContainerLifeCycle
*/
public class AppEntry
{
- /**
- * Version of the app.
- *
- * Note: Auto-increments on each {@link DeploymentManager#addApp(App)}
- */
- private int version;
-
/**
* The app being tracked.
*/
@@ -88,11 +75,6 @@ public class AppEntry
*/
private Node lifecyleNode;
- /**
- * Tracking the various AppState timestamps (in system milliseconds)
- */
- private Map stateTimestamps = new HashMap();
-
public App getApp()
{
return app;
@@ -103,20 +85,9 @@ public Node getLifecyleNode()
return lifecyleNode;
}
- public Map getStateTimestamps()
- {
- return stateTimestamps;
- }
-
- public int getVersion()
- {
- return version;
- }
-
void setLifeCycleNode(Node node)
{
this.lifecyleNode = node;
- this.stateTimestamps.put(node, System.currentTimeMillis());
}
}
@@ -129,21 +100,6 @@ void setLifeCycleNode(Node node)
private boolean _useStandardBindings = true;
private String _defaultLifeCycleGoal = AppLifeCycle.STARTED;
- /**
- * 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 not used, replacement at {@link ContextProvider#getDefaultEnvironmentName()}
- */
- @Deprecated(since = "12.1.0", forRemoval = true)
- public String getDefaultEnvironmentName()
- {
- return _providers.stream()
- .map(AppProvider::getEnvironmentName)
- .max(Deployable.ENVIRONMENT_COMPARATOR)
- .orElse(null);
- }
-
/**
* Receive an app for processing.
*
@@ -188,16 +144,6 @@ 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()
- .map(AppProvider::getEnvironmentName).anyMatch(environmentName::equalsIgnoreCase);
- }
-
public Collection getAppProviders()
{
return Collections.unmodifiableList(_providers);
@@ -297,49 +243,26 @@ protected void doStop() throws Exception
super.doStop();
}
- private AppEntry findApp(String appId)
+ private AppEntry findAppEntry(String appId)
{
if (appId == null)
return null;
for (AppEntry entry : _apps)
{
- Path path = entry.app.getPath();
- if (appId.equals(path.getName(path.getNameCount() - 1).toString()))
- return entry;
- }
- return null;
- }
-
- private AppEntry findApp(Path path)
- {
- if (path == null)
- return null;
-
- for (AppEntry entry : _apps)
- {
- if (path.equals(entry.app.getPath()))
- {
+ String name = entry.app.getName();
+ if (appId.equals(name))
return entry;
- }
}
return null;
}
public App getApp(String appId)
{
- AppEntry entry = findApp(appId);
+ AppEntry entry = findAppEntry(appId);
return entry == null ? null : entry.getApp();
}
- public App getApp(Path path)
- {
- AppEntry entry = findApp(path);
- if (entry == null)
- return null;
- return entry.app;
- }
-
public Collection getAppEntries()
{
return Collections.unmodifiableCollection(_apps);
@@ -381,37 +304,6 @@ public Collection getApps(String nodeName)
return getApps(_lifecycle.getNodeByName(nodeName));
}
- public List getAppsWithSameContext(App app)
- {
- List ret = new ArrayList();
- if (app == null)
- {
- return ret;
- }
-
- String contextId = app.getContextPath();
- if (contextId == null)
- {
- // No context? Likely not deployed or started yet.
- return ret;
- }
-
- for (AppEntry entry : _apps)
- {
- if (entry.app.equals(app))
- {
- // Its the input app. skip it.
- continue;
- }
-
- if (contextId.equals(entry.app.getContextPath()))
- {
- ret.add(entry.app);
- }
- }
- return ret;
- }
-
@ManagedAttribute("Deployed Contexts")
public ContextHandlerCollection getContexts()
{
@@ -482,7 +374,7 @@ public void removeAppProvider(AppProvider provider)
*/
public void requestAppGoal(App app, String nodeName)
{
- AppEntry appentry = findApp(app.getPath());
+ AppEntry appentry = findAppEntry(app.getName());
if (appentry == null)
{
throw new IllegalStateException("App not being tracked by Deployment Manager: " + app);
@@ -573,7 +465,7 @@ private void addOnStartupError(Throwable cause)
@ManagedOperation(value = "request the app to be moved to the specified lifecycle node", impact = "ACTION")
public void requestAppGoal(@Name("appId") String appId, @Name("nodeName") String nodeName)
{
- AppEntry appentry = findApp(appId);
+ AppEntry appentry = findAppEntry(appId);
if (appentry == null)
{
throw new IllegalStateException("App not being tracked by Deployment Manager: " + appId);
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/DebugBinding.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/DebugBinding.java
index 3eee9b1a7158..af9f2a7e47d2 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/DebugBinding.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/DebugBinding.java
@@ -15,6 +15,7 @@
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.graph.Node;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,7 +43,7 @@ public String[] getBindingTargets()
}
@Override
- public void processBinding(Node node, App app) throws Exception
+ public void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
LOG.info("processBinding {} {}", node, app.getContextHandler());
}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/OrderedGroupBinding.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/OrderedGroupBinding.java
index 241d1bcfde76..f5db0a3df18b 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/OrderedGroupBinding.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/OrderedGroupBinding.java
@@ -13,10 +13,12 @@
package org.eclipse.jetty.deploy.bindings;
+import java.util.Arrays;
import java.util.LinkedList;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.graph.Node;
/**
@@ -51,10 +53,7 @@ public void addBindings(AppLifeCycle.Binding[] bindings)
_orderedBindings = new LinkedList();
}
- for (AppLifeCycle.Binding binding : bindings)
- {
- _orderedBindings.add(binding);
- }
+ _orderedBindings.addAll(Arrays.asList(bindings));
}
@Override
@@ -64,11 +63,11 @@ public String[] getBindingTargets()
}
@Override
- public void processBinding(Node node, App app) throws Exception
+ public void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
for (AppLifeCycle.Binding binding : _orderedBindings)
{
- binding.processBinding(node, app);
+ binding.processBinding(deploymentManager, node, app);
}
}
}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardDeployer.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardDeployer.java
index 0a9597fa45cb..0df238d17712 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardDeployer.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardDeployer.java
@@ -15,6 +15,7 @@
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;
@@ -28,14 +29,14 @@ public String[] getBindingTargets()
}
@Override
- public void processBinding(Node node, App app) throws Exception
+ public void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
ContextHandler handler = app.getContextHandler();
if (handler == null)
throw new NullPointerException("No Handler created for App: " + app);
Callback.Completable blocker = new Callback.Completable();
- app.getDeploymentManager().getContexts().deployHandler(handler, blocker);
+ deploymentManager.getContexts().deployHandler(handler, blocker);
blocker.get();
}
}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java
index ec408b41cb19..5cd28565ceeb 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java
@@ -15,6 +15,7 @@
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
@@ -28,9 +29,9 @@ public String[] getBindingTargets()
}
@Override
- public void processBinding(Node node, App app) throws Exception
+ public void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
- ContextHandlerCollection contexts = app.getDeploymentManager().getContexts();
+ ContextHandlerCollection contexts = deploymentManager.getContexts();
ContextHandler handler = app.getContextHandler();
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java
index 4cc0fabd22c6..d086486b6f65 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java
@@ -15,6 +15,7 @@
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.server.handler.ContextHandler;
@@ -27,12 +28,12 @@ public String[] getBindingTargets()
}
@Override
- public void processBinding(Node node, App app) throws Exception
+ public void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
ContextHandler handler = app.getContextHandler();
// Before stopping, take back management from the context
- app.getDeploymentManager().getContexts().unmanage(handler);
+ deploymentManager.getContexts().unmanage(handler);
// Stop it
handler.stop();
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java
index 564921b037e6..9de570187f00 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java
@@ -15,6 +15,7 @@
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
@@ -29,9 +30,9 @@ public String[] getBindingTargets()
}
@Override
- public void processBinding(Node node, App app) throws Exception
+ public void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
- ContextHandlerCollection contexts = app.getDeploymentManager().getContexts();
+ ContextHandlerCollection contexts = deploymentManager.getContexts();
ContextHandler context = app.getContextHandler();
Callback.Completable blocker = new Callback.Completable();
contexts.undeployHandler(context, blocker);
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/DeploymentManagerMBean.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/DeploymentManagerMBean.java
index 67b9fa57a0cd..a05178fe6af9 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/DeploymentManagerMBean.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/jmx/DeploymentManagerMBean.java
@@ -79,12 +79,12 @@ public Collection getNodes()
private String toRef(App app)
{
- return String.format("contextPath=%s,path=%s,appProvider=%s", app.getContextPath(), app.getPath(), app.getAppProvider().getClass().getName());
+ return String.format("%s,name=%s,%s", app.getClass().getSimpleName(), app.getName(), app.getContextHandler());
}
public Collection getContexts() throws Exception
{
- List apps = new ArrayList();
+ List apps = new ArrayList<>();
for (App app : _manager.getApps())
{
apps.add(app.getContextHandler());
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
deleted file mode 100644
index a907e015e48d..000000000000
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java
+++ /dev/null
@@ -1,1026 +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.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Properties;
-import java.util.Set;
-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.PathCollators;
-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;
-
-/**
- * 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 file is deployed (which may reuse 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 file is deployed (which may use the directory in its configuration)
- * - If a WAR file and a matching XML file exist (eg: {@code foo.war} and {@code foo.xml}) then the WAR file is assumed to
- * be configured by the XML file and only the XML file is deployed.
- *
- * 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 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}
- *
- */
-@ManagedObject("Provider for start-up deployment of webapps based on presence in directory")
-public class ContextProvider extends ScanningAppProvider
-{
- private static final Logger LOG = LoggerFactory.getLogger(ContextProvider.class);
- private String _defaultEnvironmentName;
-
- public ContextProvider()
- {
- super();
- setFilenameFilter(new Filter());
- setScanInterval(0);
- }
-
- private static Map asProperties(Attributes attributes)
- {
- Map props = new HashMap<>();
- attributes.getAttributeNameSet().forEach((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;
- }
-
- @Override
- public ContextHandler createContextHandler(final App app) throws Exception
- {
- String envName = app.getEnvironmentName();
- Environment environment = Environment.get(StringUtil.isNotBlank(envName) ? envName : getDefaultEnvironmentName());
-
- 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.getName());
-
- ClassLoader old = Thread.currentThread().getContextClassLoader();
- try
- {
- Thread.currentThread().setContextClassLoader(environment.getClassLoader());
-
- app.setEnvironmentName(environment.getName());
-
- // 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);
-
- /*
- * 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);
- }
- if (LOG.isDebugEnabled())
- LOG.debug("Context {} created from app {}", context.getClass().getName(), app);
-
- // Apply environment properties and XML to context
- if (applyEnvironmentXml(context, environment, appAttributes))
- {
- // If an XML deployable, apply full XML over environment XML changes
- if (FileID.isXml(path))
- context = applyXml(context, path, environment, appAttributes);
- }
-
- // 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);
-
- return getContextHandler(context);
- }
- finally
- {
- Thread.currentThread().setContextClassLoader(old);
- }
- }
-
- /**
- * Initialize a new Context object instance.
- *
- *
- * The search order is:
- *
- *
- * - If app attribute {@link Deployable#CONTEXT_HANDLER_CLASS} is specified, use it, and initialize context
- * - If App deployable path is XML, apply XML {@code }
- * - Fallback to environment attribute {@link Deployable#CONTEXT_HANDLER_CLASS_DEFAULT}, and initialize context.
- *
- *
- * @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);
-
- initializeContextPath(contextHandler, path);
- initializeContextHandler(contextHandler, path, appAttributes);
- return context;
- }
-
- 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;
- }
-
- // fallback to default from environment.
- context = newInstance((String)environment.getAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT));
-
- if (context != null)
- {
- 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 newInstance(String className) throws Exception
- {
- 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();
- }
-
- /**
- * 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)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Applying environment specific context file {}", envXmlPath);
- context = applyXml(context, envXmlPath, environment, appAttributes);
- xmlApplied = true;
- }
-
- return xmlApplied;
- }
-
- /**
- * 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));
- }
-
- /**
- * 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 env10config = provider.configureEnvironment("ee10");
- * env10config.setExtractWars(true);
- * env10config.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))
- return null;
-
- XmlConfiguration xmlc = new XmlConfiguration(ResourceFactory.of(this).newResource(xml), null, asProperties(attributes))
- {
- @Override
- public void initializeDefaults(Object context)
- {
- super.initializeDefaults(context);
- ContextHandler contextHandler = getContextHandler(context);
- if (contextHandler == null)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Not a ContextHandler: Not initializing Context {}", context);
- }
- else
- {
- ContextProvider.this.initializeContextPath(contextHandler, xml);
- ContextProvider.this.initializeContextHandler(contextHandler, xml, attributes);
- }
- }
- };
-
- xmlc.getIdMap().put("Environment", environment.getName());
- xmlc.setJettyStandardIdsAndProperties(getDeploymentManager().getServer(), xml);
-
- // Put all Environment attributes into XmlConfiguration 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:
- *
- * - The java archive {@code .jar}
- * - The java archives {@code .d/lib/*.jar}
- * - The directory {@code .d/classes/}
- *
- *
- * @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).toLowerCase(); // use system Locale as we are dealing with system FS.
- List urls = new ArrayList<>();
-
- try (Stream listingStream = Files.list(webapps))
- {
- // Collect all paths that match "webapps/*"
- List basenamePaths = listingStream
- .filter((p) ->
- {
- String filename = p.getFileName().toString().toLowerCase(); // use system Locale
- return filename.startsWith(basename);
- })
- .toList();
-
- // Walk basename paths
- for (Path p : basenamePaths)
- {
- if (Files.isDirectory(p))
- {
- if (p.getFileName().toString().toLowerCase().endsWith(".d"))
- {
- Path libDir = p.resolve("lib");
- // we have a possible lib directory.
- if (Files.isDirectory(libDir))
- {
- try (Stream libPaths = Files.list(libDir))
- {
- List javaArchives = libPaths.filter(FileID::isJavaArchive).toList();
-
- for (Path lib : javaArchives)
- {
- urls.add(lib.toUri().toURL());
- }
- }
- }
-
- Path classesDir = p.resolve(basename + "classes");
- if (Files.isDirectory(libDir))
- urls.add(classesDir.toUri().toURL());
- }
- }
-
- if (FileID.isJavaArchive(p))
- {
- urls.add(p.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());
- }
-
- /**
- * 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;
-
- if (context instanceof ContextHandler handler)
- return handler;
-
- if (Supplier.class.isAssignableFrom(context.getClass()))
- {
- @SuppressWarnings("unchecked")
- Supplier provider = (Supplier)context;
- return provider.get();
- }
-
- if (LOG.isDebugEnabled())
- LOG.debug("Not a context {}", context);
- return null;
- }
-
- protected void initializeContextHandler(ContextHandler contextHandler, Path path, Attributes attributes)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("initializeContextHandler {}", contextHandler);
-
- assert contextHandler != null;
-
- if (contextHandler.getBaseResource() == null && 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))
- .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))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Context {} initialized with contextPath: {}", contextHandler, contextPath);
- contextHandler.setContextPath(contextPath);
- }
- }
-
- protected void initializeContextPath(ContextHandler contextHandler, Path path)
- {
- if (contextHandler == null)
- return;
-
- // 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);
- contextHandler.setVirtualHosts(Arrays.asList(virtual.split(",")));
- contextPath = "/";
- }
-
- // Ensure "/" is Prepended to all context paths.
- if (contextPath.charAt(0) != '/')
- contextPath = "/" + 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);
- }
-
- /**
- * Apply the main deployable heuristics referenced in the main javadoc
- * for this class.
- *
- * @param unit the unit of deployment to interrogate.
- * @return the main deployable path
- */
- @Override
- protected Path getMainDeploymentPath(Unit unit)
- {
- List livePaths = unit.getLivePaths();
-
- // XML always win.
- List xmls = livePaths.stream()
- .filter(FileID::isXml)
- .toList();
- if (xmls.size() == 1)
- return xmls.get(0);
- else if (xmls.size() > 1)
- throw new IllegalStateException("More than 1 XML for deployable " + asStringList(xmls));
- // WAR files are next.
- List wars = livePaths.stream()
- .filter(FileID::isWebArchive)
- .toList();
- if (wars.size() == 1)
- return wars.get(0);
- else if (wars.size() > 1)
- throw new IllegalStateException("More than 1 WAR for deployable " + asStringList(wars));
- // Directories next.
- List dirs = livePaths.stream()
- .filter(Files::isDirectory)
- .toList();
- if (dirs.size() == 1)
- return dirs.get(0);
- if (dirs.size() > 1)
- throw new IllegalStateException("More than 1 Directory for deployable " + asStringList(dirs));
-
- throw new IllegalStateException("Unable to determine main deployable for " + unit);
- }
-
- @Override
- public void unitsChanged(List units)
- {
- Set envsChanges = units.stream()
- .map(Unit::getEnvironmentConfigName)
- .filter(Objects::nonNull)
- .collect(Collectors.toSet());
-
- if (envsChanges.isEmpty())
- {
- // normal set of changes, no environment configuration changes
- // present in the incoming list of changed units.
- super.unitsChanged(units);
- }
- else
- {
- // ensure that the units reported to super.unitsChanged
- // includes all (known) units that belong to the list of changed environments,
- // while filtering out the environment config specific units here.
- List unitsWithEnvChanges = new ArrayList<>(units.stream()
- .filter(Unit::isDeployable)
- .toList());
-
- // Add any missing units into change set.
- for (String envChange : envsChanges)
- {
- for (Unit unit : getUnitsForEnvironment(envChange))
- {
- if (!unitsWithEnvChanges.contains(unit))
- {
- // unit is not part of changeSet but is
- // in need of a redeploy due to an environment configuration
- // file being changed.
- unit.setState(Unit.State.CHANGED);
- unitsWithEnvChanges.add(unit);
- }
- }
- }
-
- super.unitsChanged(unitsWithEnvChanges);
- }
- }
-
- /**
- * 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)));
-
- 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)
- .filter(p -> FileID.isExtension(p, "properties"))
- .filter(p ->
- {
- String name = p.getFileName().toString();
- return name.startsWith(env);
- }).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)));
- }
-
- private static String asStringList(Collection paths)
- {
- return paths.stream()
- .sorted(PathCollators.byName(true))
- .map(Path::toString)
- .collect(Collectors.joining(", ", "[", "]"));
- }
-
- /**
- * 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 EnvironmentConfig
- {
- // 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;
-
- private EnvironmentConfig(Environment environment)
- {
- this._environment = environment;
- }
-
- /**
- * 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).
- *
- *
- * 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_DEFAULT
- */
- public void setContextHandlerClass(String classname)
- {
- _environment.setAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT, 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 static class Filter implements FilenameFilter
- {
- @Override
- public boolean accept(File dir, String name)
- {
- if (dir == null || !dir.canRead())
- return false;
-
- Path path = dir.toPath().resolve(name);
-
- // We don't monitor subdirectories.
- if (Files.isDirectory(path))
- return false;
-
- // Synthetic files (like consoles, printers, serial ports, etc) are ignored.
- if (!Files.isRegularFile(path))
- return false;
-
- try
- {
- // ignore traditional "hidden" path entries.
- if (name.startsWith("."))
- return false;
- // ignore path tagged as hidden by FS
- if (Files.isHidden(path))
- return false;
- }
- catch (IOException ignore)
- {
- // ignore
- }
-
- // The filetypes that are monitored, and we want updates for when they change.
- return FileID.isExtension(name, "jar", "war", "xml", "properties");
- }
- }
-}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DefaultApp.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DefaultApp.java
new file mode 100644
index 000000000000..cd513809862d
--- /dev/null
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DefaultApp.java
@@ -0,0 +1,332 @@
+//
+// ========================================================================
+// 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.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.stream.Collectors;
+
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.server.Deployable;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.FileID;
+import org.eclipse.jetty.util.resource.PathCollators;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Default {@link App} represents all the components that make up
+ * a from-file-system App deployment that the {@link DefaultProvider}
+ * creates and uses.
+ */
+public class DefaultApp implements App
+{
+ public enum State
+ {
+ UNCHANGED,
+ ADDED,
+ CHANGED,
+ REMOVED
+ }
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultApp.class);
+ private final String name;
+ private final Map paths = new HashMap<>();
+ private final Attributes attributes = new Attributes.Mapped();
+ private State state;
+ private ContextHandler contextHandler;
+
+ public DefaultApp(String name)
+ {
+ this.name = name;
+ this.state = calcState();
+ }
+
+ private static String asStringList(Collection paths)
+ {
+ return paths.stream()
+ .sorted(PathCollators.byName(true))
+ .map(Path::toString)
+ .collect(Collectors.joining(", ", "[", "]"));
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (o == null || getClass() != o.getClass())
+ return false;
+ DefaultApp that = (DefaultApp)o;
+ return Objects.equals(name, that.name);
+ }
+
+ public Attributes getAttributes()
+ {
+ return this.attributes;
+ }
+
+ @Override
+ public ContextHandler getContextHandler()
+ {
+ return contextHandler;
+ }
+
+ public void setContextHandler(ContextHandler contextHandler)
+ {
+ this.contextHandler = contextHandler;
+ }
+
+ public String getEnvironmentName()
+ {
+ return (String)getAttributes().getAttribute(Deployable.ENVIRONMENT);
+ }
+
+ public void setEnvironmentName(String name)
+ {
+ getAttributes().setAttribute(Deployable.ENVIRONMENT, name);
+ }
+
+ /**
+ * Get the main path used for deployment.
+ *
+ * Applies the heuristics reference in the main
+ * javadoc for {@link DefaultProvider}
+ *
+ *
+ * @return the main deployable path
+ */
+ public Path getMainPath()
+ {
+ List livePaths = paths
+ .entrySet()
+ .stream()
+ .filter((e) -> e.getValue() != State.REMOVED)
+ .map(Map.Entry::getKey)
+ .sorted(PathCollators.byName(true))
+ .toList();
+
+ if (livePaths.isEmpty())
+ return null;
+
+ // XML always win.
+ List xmls = livePaths.stream()
+ .filter(FileID::isXml)
+ .toList();
+ if (xmls.size() == 1)
+ return xmls.get(0);
+ else if (xmls.size() > 1)
+ throw new IllegalStateException("More than 1 XML for deployable " + asStringList(xmls));
+
+ // WAR files are next.
+ List wars = livePaths.stream()
+ .filter(FileID::isWebArchive)
+ .toList();
+ if (wars.size() == 1)
+ return wars.get(0);
+ else if (wars.size() > 1)
+ throw new IllegalStateException("More than 1 WAR for deployable " + asStringList(wars));
+
+ // Directories next.
+ List dirs = livePaths.stream()
+ .filter(Files::isDirectory)
+ .toList();
+ if (dirs.size() == 1)
+ return dirs.get(0);
+ if (dirs.size() > 1)
+ throw new IllegalStateException("More than 1 Directory for deployable " + asStringList(dirs));
+
+ LOG.warn("Unable to determine main deployable for {}", this);
+ return null;
+ }
+
+ @Override
+ public String getName()
+ {
+ return name;
+ }
+
+ public Map getPaths()
+ {
+ return paths;
+ }
+
+ public State getState()
+ {
+ return state;
+ }
+
+ public void setState(State state)
+ {
+ this.state = state;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hashCode(name);
+ }
+
+ /**
+ * Load all {@code properties} files belonging to this DefaultApp
+ * into the {@link Attributes} for this App.
+ *
+ * @see #getAttributes()
+ */
+ public void loadProperties()
+ {
+ // look for properties file for main basename.
+ String propFilename = String.format("%s.properties", getName());
+ List propFiles = paths.keySet().stream()
+ .filter(Files::isRegularFile)
+ .filter(p -> p.getFileName().toString().equalsIgnoreCase(propFilename))
+ .sorted(PathCollators.byName(true))
+ .toList();
+
+ if (propFiles.isEmpty())
+ {
+ // No properties file found
+ return;
+ }
+
+ if (propFiles.size() > 1)
+ {
+ LOG.warn("Multiple matching files with name [{}]: {}", propFilename,
+ asStringList(propFiles));
+ }
+
+ for (Path propFile : propFiles)
+ {
+ try (InputStream inputStream = Files.newInputStream(propFile))
+ {
+ Properties props = new Properties();
+ props.load(inputStream);
+ props.stringPropertyNames().forEach(
+ (name) -> getAttributes().setAttribute(name, props.getProperty(name)));
+ }
+ catch (IOException e)
+ {
+ LOG.warn("Unable to read properties file: {}", propFile, e);
+ }
+ }
+ }
+
+ public void putPath(Path path, State state)
+ {
+ this.paths.put(path, state);
+ setState(calcState());
+ }
+
+ public void resetStates()
+ {
+ // Drop paths that were removed.
+ List removedPaths = paths.entrySet()
+ .stream().filter(e -> e.getValue() == State.REMOVED)
+ .map(Map.Entry::getKey)
+ .toList();
+ for (Path removedPath : removedPaths)
+ {
+ paths.remove(removedPath);
+ }
+ // Set all remaining path states to UNCHANGED
+ paths.replaceAll((p, v) -> State.UNCHANGED);
+ state = calcState();
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder("%s@%x".formatted(this.getClass().getSimpleName(), hashCode()));
+ str.append("[").append(name);
+ str.append("|").append(getState());
+ str.append(", env=").append(getEnvironmentName());
+ str.append(", paths=");
+ str.append(paths.entrySet().stream()
+ .map((e) -> String.format("%s|%s", e.getKey(), e.getValue()))
+ .collect(Collectors.joining(", ", "[", "]"))
+ );
+ str.append(", contextHandler=");
+ if (contextHandler == null)
+ str.append("");
+ else
+ str.append(contextHandler);
+ str.append("]");
+ return str.toString();
+ }
+
+ /**
+ *
+ * Calculate the State of the overall Unit based on the States in the Paths.
+ *
+ *
+ * - UNCHANGED
+ * - All Path states are in UNCHANGED state
+ * - ADDED
+ * - All Path states are in ADDED state
+ * - CHANGED
+ * - At least one Path state is CHANGED, or there is a variety of states
+ * - REMOVED
+ * - All Path states are in REMOVED state, or there are no Paths being tracked
+ *
+ *
+ * @return the state of the Unit.
+ */
+ private DefaultApp.State calcState()
+ {
+ if (paths.isEmpty())
+ return DefaultApp.State.REMOVED;
+
+ // Calculate state of unit from Path states.
+ State ret = null;
+ for (State pathState : paths.values())
+ {
+ switch (pathState)
+ {
+ case UNCHANGED ->
+ {
+ if (ret == null)
+ ret = State.UNCHANGED;
+ else if (ret != State.UNCHANGED)
+ ret = State.CHANGED;
+ }
+ case ADDED ->
+ {
+ if (ret == null)
+ ret = State.ADDED;
+ else if (ret == State.UNCHANGED || ret == State.REMOVED)
+ ret = State.ADDED;
+ }
+ case CHANGED ->
+ {
+ ret = State.CHANGED;
+ }
+ case REMOVED ->
+ {
+ if (ret == null)
+ ret = State.REMOVED;
+ else if (ret != State.REMOVED)
+ ret = State.CHANGED;
+ }
+ }
+ }
+ return ret != null ? ret : DefaultApp.State.UNCHANGED;
+ }
+}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DefaultContextHandlerFactory.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DefaultContextHandlerFactory.java
new file mode 100644
index 000000000000..c2cd79752539
--- /dev/null
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DefaultContextHandlerFactory.java
@@ -0,0 +1,449 @@
+//
+// ========================================================================
+// 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.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.server.Deployable;
+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.component.Environment;
+import org.eclipse.jetty.util.resource.ResourceFactory;
+import org.eclipse.jetty.xml.XmlConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultContextHandlerFactory
+{
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultContextHandlerFactory.class);
+ private static final String ENV_XML_PATHS = "jetty.deploy.defaultApp.envXmls";
+
+ private String _defaultEnvironmentName;
+
+ private static Map asProperties(Attributes attributes)
+ {
+ Map props = new HashMap<>();
+ attributes.getAttributeNameSet().forEach((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;
+ }
+
+ public static List getEnvironmentXmlPaths(Attributes attributes)
+ {
+ //noinspection unchecked
+ return (List)attributes.getAttribute(ENV_XML_PATHS);
+ }
+
+ public static void setEnvironmentXmlPaths(Attributes attributes, List paths)
+ {
+ attributes.setAttribute(ENV_XML_PATHS, paths);
+ }
+
+ /**
+ * 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;
+ }
+
+ public ContextHandler newContextHandler(DefaultProvider provider, DefaultApp app, Attributes deployAttributes) throws Exception
+ {
+ Path mainPath = app.getMainPath();
+ if (mainPath == null)
+ {
+ LOG.warn("Unable to create ContextHandler for app with no main path: {}", app);
+ return null;
+ }
+
+ // Resolve real file (hopefully eliminating alias issues)
+ mainPath = mainPath.toRealPath();
+
+ // Can happen if the file existed when notified by scanner (as either an ADD or CHANGE),
+ // and then the file was deleted before reaching this code.
+ if (!Files.exists(mainPath))
+ throw new IllegalStateException("App path does not exist " + mainPath);
+
+ String envName = app.getEnvironmentName();
+ if (StringUtil.isBlank(envName))
+ {
+ envName = getDefaultEnvironmentName();
+ app.setEnvironmentName(envName);
+ }
+
+ // Verify that referenced Environment even exists.
+ Environment environment = Environment.get(envName);
+
+ if (environment == null)
+ {
+ LOG.warn("Environment [{}] does not exist (referenced in 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.getName());
+
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ try
+ {
+ Thread.currentThread().setContextClassLoader(environment.getClassLoader());
+
+ /*
+ * 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(provider, environment, app, deployAttributes, mainPath);
+ if (context == null)
+ throw new IllegalStateException("unable to create ContextHandler for " + app);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Context {} created from app {}", context.getClass().getName(), app);
+
+ // Apply environment properties and XML to context
+ if (applyEnvironmentXml(provider, app, context, environment, deployAttributes))
+ {
+ // If an XML deployable, apply full XML over environment XML changes
+ if (FileID.isXml(mainPath))
+ context = applyXml(provider, context, mainPath, environment, deployAttributes);
+ }
+
+ // 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.
+ app.getAttributes().setAttribute(Deployable.WAR, mainPath.toString());
+
+ // Initialize any deployable
+ if (context instanceof Deployable deployable)
+ deployable.initializeDefaults(deployAttributes);
+
+ return getContextHandler(context);
+ }
+ finally
+ {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+
+ protected Object applyXml(DefaultProvider provider, Object context, Path xml, Environment environment, Attributes attributes) throws Exception
+ {
+ if (!FileID.isXml(xml))
+ return null;
+
+ try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
+ {
+ XmlConfiguration xmlc = new XmlConfiguration(resourceFactory.newResource(xml), null, asProperties(attributes))
+ {
+ @Override
+ public void initializeDefaults(Object context)
+ {
+ super.initializeDefaults(context);
+ ContextHandler contextHandler = getContextHandler(context);
+ if (contextHandler == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Not a ContextHandler: Not initializing Context {}", context);
+ }
+ else
+ {
+ DefaultContextHandlerFactory.this.initializeContextPath(contextHandler, xml);
+ DefaultContextHandlerFactory.this.initializeContextHandler(contextHandler, xml, attributes);
+ }
+ }
+ };
+
+ xmlc.getIdMap().put("Environment", environment.getName());
+ xmlc.setJettyStandardIdsAndProperties(provider.getDeploymentManager().getServer(), xml);
+
+ // Put all Environment attributes into XmlConfiguration 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 = environment.getClassLoader();
+ 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);
+ }
+ }
+ }
+
+ protected void initializeContextHandler(ContextHandler contextHandler, Path path, Attributes attributes)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("initializeContextHandler {}", contextHandler);
+
+ assert contextHandler != null;
+
+ if (contextHandler.getBaseResource() == null && 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))
+ .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))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Context {} initialized with contextPath: {}", contextHandler, contextPath);
+ contextHandler.setContextPath(contextPath);
+ }
+ }
+
+ protected void initializeContextPath(ContextHandler contextHandler, Path path)
+ {
+ if (contextHandler == null)
+ return;
+
+ // 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);
+ contextHandler.setVirtualHosts(Arrays.asList(virtual.split(",")));
+ contextPath = "/";
+ }
+
+ // Ensure "/" is Prepended to all context paths.
+ if (contextPath.charAt(0) != '/')
+ contextPath = "/" + 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);
+ }
+
+ /**
+ * Apply optional environment specific XML to context.
+ *
+ * @param provider the DefaultProvider responsible for this context creation
+ * @param app the default app
+ * @param context the context to apply environment specific behavior to
+ * @param environment the environment to use
+ * @param attributes the attributes used to deploy the app
+ * @return true if environment specific XML was applied.
+ * @throws Exception if unable to apply environment configuration.
+ */
+ private boolean applyEnvironmentXml(DefaultProvider provider, DefaultApp app, Object context, Environment environment, Attributes attributes) throws Exception
+ {
+ // Collect the optional environment context xml files.
+ // Order them according to the name of their property key names.
+ List sortedEnvXmlPaths = getEnvironmentXmlPaths(attributes);
+
+ if (sortedEnvXmlPaths == null || sortedEnvXmlPaths.isEmpty())
+ // nothing to do here
+ return false;
+
+ boolean xmlApplied = false;
+
+ // apply each context environment xml file
+ for (Path envXmlPath : sortedEnvXmlPaths)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Applying environment specific context file {}", envXmlPath);
+ context = applyXml(provider, context, envXmlPath, environment, attributes);
+ xmlApplied = true;
+ }
+
+ return xmlApplied;
+ }
+
+ /**
+ * 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;
+
+ if (context instanceof ContextHandler handler)
+ return handler;
+
+ if (Supplier.class.isAssignableFrom(context.getClass()))
+ {
+ @SuppressWarnings("unchecked")
+ Supplier provider = (Supplier)context;
+ return provider.get();
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Not a context {}", context);
+ return null;
+ }
+
+ /**
+ * Initialize a new Context object instance.
+ *
+ *
+ * The search order is:
+ *
+ *
+ * - If app attribute {@link Deployable#CONTEXT_HANDLER_CLASS} is specified, use it, and initialize context
+ * - If App deployable path is XML, apply XML {@code }
+ * - Fallback to environment attribute {@link Deployable#CONTEXT_HANDLER_CLASS_DEFAULT}, and initialize context.
+ *
+ *
+ * @param environment the environment context applies to
+ * @param app the App for the context
+ * @param attributes the Attributes used to deploy the App
+ * @param path the path of the deployable
+ * @return the Context Object.
+ * @throws Exception if unable to create Object instance.
+ */
+ private Object newContextInstance(DefaultProvider provider, Environment environment, App app, Attributes attributes, Path path) throws Exception
+ {
+ Object context = newInstance((String)attributes.getAttribute(Deployable.CONTEXT_HANDLER_CLASS));
+ if (context != null)
+ {
+ ContextHandler contextHandler = getContextHandler(context);
+ if (contextHandler == null)
+ throw new IllegalStateException("Unknown context type of " + context);
+
+ initializeContextPath(contextHandler, path);
+ initializeContextHandler(contextHandler, path, attributes);
+ return context;
+ }
+
+ if (FileID.isXml(path))
+ {
+ context = applyXml(provider, null, path, environment, attributes);
+ ContextHandler contextHandler = getContextHandler(context);
+ if (contextHandler == null)
+ throw new IllegalStateException("Unknown context type of " + context);
+ return context;
+ }
+
+ // fallback to default from environment.
+ context = newInstance((String)environment.getAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT));
+
+ if (context != null)
+ {
+ ContextHandler contextHandler = getContextHandler(context);
+ if (contextHandler == null)
+ throw new IllegalStateException("Unknown context type of " + context);
+
+ initializeContextPath(contextHandler, path);
+ initializeContextHandler(contextHandler, path, attributes);
+ return context;
+ }
+
+ return null;
+ }
+
+ private Object newInstance(String className) throws Exception
+ {
+ 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();
+ }
+}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DefaultProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DefaultProvider.java
new file mode 100644
index 000000000000..f6ff93c8fd01
--- /dev/null
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DefaultProvider.java
@@ -0,0 +1,1054 @@
+//
+// ========================================================================
+// 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.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+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.FileID;
+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.PathCollators;
+import org.eclipse.jetty.xml.XmlConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default 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 file is deployed (which may reuse 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 file is deployed (which may use the directory in its configuration)
+ * - If a WAR file and a matching XML file exist (eg: {@code foo.war} and {@code foo.xml}) then the WAR file is assumed to
+ * be configured by the XML file and only the XML file is deployed.
+ *
+ * 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 embedded calls modifying {@link DefaultApp#getAttributes()}
+ * - The app specific properties file {@code webapps/.properties}
+ * - The environment specific properties file {@code webapps/[-zzz].properties}
+ * - The {@link Attributes} from the {@link Environment}
+ *
+ *
+ *
+ * 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
+ * DefaultDeploymentAppProvider provider = new DefaultDeploymentAppProvider();
+ * EnvironmentConfig env10config = provider.configureEnvironment("ee10");
+ * env10config.setExtractWars(true);
+ * env10config.setParentLoaderPriority(false);
+ * }
+ */
+@ManagedObject("Provider for start-up deployment of webapps based on presence in directory")
+public class DefaultProvider extends ContainerLifeCycle implements AppProvider, Scanner.ChangeSetListener
+{
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultProvider.class);
+
+ private final FilenameFilter filenameFilter;
+ private final List monitoredDirs = new CopyOnWriteArrayList<>();
+
+ private Map apps = new HashMap<>();
+ private DeploymentManager deploymentManager;
+ private Comparator actionComparator = new DeployActionComparator();
+ private DefaultContextHandlerFactory contextHandlerFactory = new DefaultContextHandlerFactory();
+ private Path environmentsDir;
+ private int scanInterval = 10;
+ private Scanner scanner;
+ private boolean useRealPaths;
+ private boolean deferInitialScan = false;
+
+ public DefaultProvider()
+ {
+ this(new MonitoredPathFilter());
+ }
+
+ public DefaultProvider(FilenameFilter filter)
+ {
+ filenameFilter = filter;
+ setScanInterval(0);
+ }
+
+ private static String asStringList(Collection paths)
+ {
+ return paths.stream()
+ .sorted(PathCollators.byName(true))
+ .map(Path::toString)
+ .collect(Collectors.joining(", ", "[", "]"));
+ }
+
+ /**
+ * @param dir Directory to scan for deployable artifacts
+ */
+ public void addMonitoredDirectory(Path dir)
+ {
+ if (isStarted())
+ throw new IllegalStateException("Unable to add monitored directory while running");
+ monitoredDirs.add(Objects.requireNonNull(dir));
+ }
+
+ public void addScannerListener(Scanner.Listener listener)
+ {
+ scanner.addListener(listener);
+ }
+
+ /**
+ * 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));
+ }
+
+ public Comparator getActionComparator()
+ {
+ return actionComparator;
+ }
+
+ public void setActionComparator(Comparator actionComparator)
+ {
+ this.actionComparator = actionComparator;
+ }
+
+ public Collection getApps()
+ {
+ return apps.values();
+ }
+
+ public DefaultContextHandlerFactory getContextHandlerFactory()
+ {
+ return contextHandlerFactory;
+ }
+
+ public void setContextHandlerFactory(DefaultContextHandlerFactory contextHandlerFactory)
+ {
+ this.contextHandlerFactory = contextHandlerFactory;
+ }
+
+ /**
+ * 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()
+ {
+ return this.contextHandlerFactory.getDefaultEnvironmentName();
+ }
+
+ public void setDefaultEnvironmentName(String name)
+ {
+ this.contextHandlerFactory.setDefaultEnvironmentName(name);
+ }
+
+ /**
+ * Get the deploymentManager.
+ *
+ * @return the deploymentManager
+ */
+ public DeploymentManager getDeploymentManager()
+ {
+ return deploymentManager;
+ }
+
+ @Override
+ public void setDeploymentManager(DeploymentManager deploymentManager)
+ {
+ this.deploymentManager = deploymentManager;
+ }
+
+ public Path getEnvironmentsDirectory()
+ {
+ return environmentsDir;
+ }
+
+ public void setEnvironmentsDirectory(Path dir)
+ {
+ if (isStarted())
+ throw new IllegalStateException("Unable to add environments directory while running");
+ environmentsDir = dir;
+ }
+
+ public List getMonitoredDirectories()
+ {
+ return monitoredDirs;
+ }
+
+ public void setMonitoredDirectories(Collection directories)
+ {
+ if (isStarted())
+ throw new IllegalStateException("Unable to add monitored directories while running");
+
+ for (Path dir : directories)
+ {
+ monitoredDirs.add(Objects.requireNonNull(dir));
+ }
+ }
+
+ @ManagedAttribute("scanning interval to detect changes which need reloaded")
+ public int getScanInterval()
+ {
+ return scanInterval;
+ }
+
+ public void setScanInterval(int scanInterval)
+ {
+ this.scanInterval = scanInterval;
+ }
+
+ public Comparator getUnitComparator()
+ {
+ return actionComparator;
+ }
+
+ public void setUnitComparator(Comparator comparator)
+ {
+ this.actionComparator = comparator;
+ }
+
+ /**
+ * Test if initial scan should be deferred.
+ *
+ * @return true if initial scan is deferred, false to have initial scan occur on startup of ScanningAppProvider.
+ */
+ public boolean isDeferInitialScan()
+ {
+ return deferInitialScan;
+ }
+
+ /**
+ * Flag to control initial scan behavior.
+ *
+ *
+ * - {@code true} - to have initial scan deferred until the {@link Server} component
+ * has reached it's STARTED state.
+ * Note: any failures in a deployment will not fail the Server startup in this mode.
+ * - {@code false} - (default value) to have initial scan occur as normal on
+ * ScanningAppProvider startup.
+ *
+ *
+ * @param defer true to defer initial scan, false to have initial scan occur on startup of ScanningAppProvider.
+ */
+ public void setDeferInitialScan(boolean defer)
+ {
+ deferInitialScan = defer;
+ }
+
+ /**
+ * @return True if the real path of the scanned files should be used for deployment.
+ */
+ public boolean isUseRealPaths()
+ {
+ return useRealPaths;
+ }
+
+ /**
+ * @param useRealPaths True if the real path of the scanned files should be used for deployment.
+ */
+ public void setUseRealPaths(boolean useRealPaths)
+ {
+ this.useRealPaths = useRealPaths;
+ }
+
+ /**
+ * This is the listener event for Scanner to report changes.
+ *
+ * @param changeSet the changeset from the Scanner.
+ */
+ @Override
+ public void pathsChanged(Map changeSet)
+ {
+ Objects.requireNonNull(changeSet);
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("pathsChanged: {}",
+ changeSet.entrySet()
+ .stream()
+ .map((e) -> String.format("%s|%s", e.getKey(), e.getValue()))
+ .collect(Collectors.joining(", ", "[", "]"))
+ );
+ }
+
+ Set changedBaseNames = new HashSet<>();
+ Set availableEnvironmentNames = Environment.getAll().stream()
+ .map(Environment::getName).collect(Collectors.toUnmodifiableSet());
+ Set changedEnvironments = new HashSet<>();
+
+ for (Map.Entry entry : changeSet.entrySet())
+ {
+ Path path = entry.getKey();
+ DefaultApp.State state = switch (entry.getValue())
+ {
+ case ADDED ->
+ {
+ yield DefaultApp.State.ADDED;
+ }
+ case CHANGED ->
+ {
+ yield DefaultApp.State.CHANGED;
+ }
+ case REMOVED ->
+ {
+ yield DefaultApp.State.REMOVED;
+ }
+ };
+
+ // Using lower-case as defined by System Locale, as the files themselves from System FS.
+ String basename = FileID.getBasename(path).toLowerCase();
+
+ if (isMonitoredPath(path))
+ {
+ // we have a normal path entry
+ changedBaseNames.add(basename);
+ DefaultApp app = apps.computeIfAbsent(basename, DefaultApp::new);
+ app.putPath(path, state);
+ }
+ else if (isEnvironmentConfigPath(path))
+ {
+ String envname = null;
+ for (String name : availableEnvironmentNames)
+ {
+ if (basename.startsWith(name))
+ envname = name;
+ }
+ if (StringUtil.isBlank(envname))
+ {
+ LOG.warn("Unable to determine Environment for file: {}", path);
+ continue;
+ }
+ changedEnvironments.add(envname);
+ }
+ }
+
+ // Now we know the DefaultApp instances that are changed by the incoming
+ // Scanner changes alone.
+
+ List changedApps = changedBaseNames
+ .stream()
+ .map(name -> apps.get(name))
+ .collect(Collectors.toList());
+
+ if (changedEnvironments.isEmpty())
+ {
+ // We have a set of changes, with no environment configuration
+ // changes present.
+ List actions = buildActionList(changedApps);
+ performActions(actions);
+ }
+ else
+ {
+ // We have incoming environment configuration changes
+ // We need to add any missing DefaultApp that have changed
+ // due to incoming environment configuration changes.
+
+ for (String changedEnvName : changedEnvironments)
+ {
+ // Add any missing apps to changedApps list
+ for (DefaultApp app : apps.values())
+ {
+ if (changedBaseNames.contains(app.getName()))
+ continue; // skip app that's already in the change list.
+
+ if (changedEnvName.equalsIgnoreCase(app.getEnvironmentName()))
+ {
+ if (app.getState() == DefaultApp.State.UNCHANGED)
+ app.setState(DefaultApp.State.CHANGED);
+ changedApps.add(app);
+ changedBaseNames.add(app.getName());
+ }
+ }
+
+ // Load any Environment properties files into Environment attributes.
+ try
+ {
+ Properties envProps = loadEnvironmentProperties(changedEnvName);
+ Environment environment = Environment.get(changedEnvName);
+ envProps.stringPropertyNames().forEach(
+ k -> environment.setAttribute(k, envProps.getProperty(k))
+ );
+ }
+ catch (IOException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Unable to load environment properties for environment [{}]", changedEnvName, e);
+ }
+ }
+
+ List actions = buildActionList(changedApps);
+ performActions(actions);
+ }
+ }
+
+ @ManagedOperation(value = "Scan the monitored directories", impact = "ACTION")
+ public void scan()
+ {
+ LOG.info("Performing scan of monitored directories: {}",
+ monitoredDirs.stream()
+ .map(Path::toUri)
+ .map(URI::toASCIIString)
+ .collect(Collectors.joining(", ", "[", "]"))
+ );
+ scanner.nudge();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[dirs=%s]", this.getClass(), hashCode(), monitoredDirs);
+ }
+
+ protected List buildActionList(List changedApps)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("buildActionList: {}", changedApps);
+
+ List actions = new ArrayList<>();
+ for (DefaultApp app : changedApps)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("changed app: {}", app);
+
+ switch (app.getState())
+ {
+ case ADDED ->
+ {
+ actions.add(new DeployAction(DeployAction.Type.ADD, app));
+ }
+ case CHANGED ->
+ {
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, app));
+ actions.add(new DeployAction(DeployAction.Type.ADD, app));
+ }
+ case REMOVED ->
+ {
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, app));
+ }
+ }
+ }
+ return sortActions(actions);
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}.doStart()", this.getClass().getSimpleName());
+ if (monitoredDirs.isEmpty())
+ throw new IllegalStateException("No monitored dir specified");
+
+ LOG.info("Deployment monitor in {} at intervals {}s", monitoredDirs, getScanInterval());
+
+ Predicate validDir = (path) ->
+ {
+ if (!Files.exists(path))
+ {
+ LOG.warn("Does not exist: {}", path);
+ return false;
+ }
+
+ if (!Files.isDirectory(path))
+ {
+ LOG.warn("Is not a directory: {}", path);
+ return false;
+ }
+
+ return true;
+ };
+
+ List dirs = new ArrayList<>();
+ for (Path dir : monitoredDirs)
+ {
+ if (validDir.test(dir))
+ dirs.add(dir);
+ }
+
+ if (environmentsDir != null)
+ {
+ if (validDir.test(environmentsDir))
+ dirs.add(environmentsDir);
+ }
+
+ scanner = new Scanner(null, useRealPaths);
+ scanner.setScanDirs(dirs);
+ scanner.setScanInterval(scanInterval);
+ scanner.setFilenameFilter(filenameFilter);
+ scanner.setReportDirs(true);
+ scanner.setScanDepth(1);
+ scanner.addListener(this);
+ scanner.setReportExistingFilesOnStartup(true);
+ scanner.setAutoStartScanning(!deferInitialScan);
+ addBean(scanner);
+
+ if (isDeferInitialScan())
+ {
+ // Setup listener to wait for Server in STARTED state, which
+ // triggers the first scan of the monitored directories
+ getDeploymentManager().getServer().addEventListener(
+ new LifeCycle.Listener()
+ {
+ @Override
+ public void lifeCycleStarted(LifeCycle event)
+ {
+ if (event instanceof Server)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Triggering Deferred Scan of {}", dirs);
+ scanner.startScanning();
+ }
+ }
+ });
+ }
+
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ super.doStop();
+ if (scanner != null)
+ {
+ removeBean(scanner);
+ scanner.removeListener(this);
+ scanner = null;
+ }
+ }
+
+ protected boolean exists(String path)
+ {
+ return scanner.exists(path);
+ }
+
+ protected List getAppsInEnvironment(String envName)
+ {
+ return apps.values().stream()
+ .filter((app) -> envName.equals(app.getEnvironmentName()))
+ .toList();
+ }
+
+ protected boolean isEnvironmentConfigPath(Path path)
+ {
+ if (environmentsDir == null)
+ return false;
+
+ return isSameDir(environmentsDir, path.getParent());
+ }
+
+ protected boolean isMonitoredPath(Path path)
+ {
+ Path parentDir = path.getParent();
+ for (Path monitoredDir : monitoredDirs)
+ {
+ if (isSameDir(monitoredDir, parentDir))
+ return true;
+ }
+ return false;
+ }
+
+ protected boolean isSameDir(Path dirA, Path dirB)
+ {
+ try
+ {
+ return Files.isSameFile(dirA, dirB);
+ }
+ catch (IOException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ignoring: Unable to use Files.isSameFile({}, {})", dirA, dirB, e);
+ return false;
+ }
+ }
+
+ protected void performActions(List actions)
+ {
+ for (DeployAction step : actions)
+ {
+ try
+ {
+ switch (step.getType())
+ {
+ case REMOVE ->
+ {
+ apps.remove(step.getName());
+ deploymentManager.removeApp(step.getApp());
+ }
+ case ADD ->
+ {
+ // Load .properties into app.
+ step.getApp().loadProperties();
+
+ // Ensure Environment name is set
+ String appEnvironment = step.getApp().getEnvironmentName();
+ if (StringUtil.isBlank(appEnvironment))
+ appEnvironment = getDefaultEnvironmentName();
+ step.getApp().setEnvironmentName(appEnvironment);
+
+ // Create a new Attributes for the App, which is the
+ // combination of Environment Attributes with App Attributes overlaying them.
+ Environment environment = Environment.get(appEnvironment);
+ Attributes deployAttributes = initAttributes(environment, step.getApp());
+
+ // Ensure that Environment configuration XMLs are listed in deployAttributes
+ List envXmlPaths = findEnvironmentXmlPaths(deployAttributes);
+ envXmlPaths.sort(PathCollators.byName(true));
+ DefaultContextHandlerFactory.setEnvironmentXmlPaths(deployAttributes, envXmlPaths);
+
+ // Create the Context Handler
+ ContextHandler contextHandler = getContextHandlerFactory().newContextHandler(this, step.getApp(), deployAttributes);
+ step.getApp().setContextHandler(contextHandler);
+
+ // Introduce the App to the DeploymentManager
+ deploymentManager.addApp(step.getApp());
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ LOG.warn("Failed to to perform action {} on {}", step.getType(), step.getApp(), t);
+ }
+ finally
+ {
+ step.getApp().resetStates();
+ }
+ }
+ }
+
+ protected List sortActions(List actions)
+ {
+ Comparator deployActionComparator = getActionComparator();
+ if (deployActionComparator != null)
+ actions.sort(deployActionComparator);
+ return actions;
+ }
+
+ private void copyAttributes(Attributes sourceAttributes, Attributes destAttributes)
+ {
+ sourceAttributes.getAttributeNameSet().forEach((key) ->
+ destAttributes.setAttribute(key, sourceAttributes.getAttribute(key)));
+ }
+
+ private List findEnvironmentXmlPaths(Attributes deployAttributes)
+ {
+ List rawEnvXmlPaths = deployAttributes.getAttributeNameSet()
+ .stream()
+ .filter(k -> k.startsWith(Deployable.ENVIRONMENT_XML))
+ .map(k -> Path.of((String)deployAttributes.getAttribute(k)))
+ .toList();
+
+ List ret = new ArrayList<>();
+ for (Path rawPath : rawEnvXmlPaths)
+ {
+ if (Files.exists(rawPath))
+ {
+ if (Files.isRegularFile(rawPath))
+ {
+ // just add it, nothing else to do.
+ if (rawPath.isAbsolute())
+ ret.add(rawPath);
+ else
+ ret.add(rawPath.toAbsolutePath());
+ }
+ else
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Ignoring non-file reference to environment xml: {}", rawPath);
+ }
+ }
+ else if (!rawPath.isAbsolute())
+ {
+ // we have a relative defined path, try to resolve it from known locations
+ if (LOG.isDebugEnabled())
+ LOG.debug("Resolving environment xml path relative reference: {}", rawPath);
+ boolean found = false;
+ for (Path monitoredDir : getMonitoredDirectories())
+ {
+ Path resolved = monitoredDir.resolve(rawPath);
+ if (Files.isRegularFile(resolved))
+ {
+ found = true;
+ // add resolved path
+ ret.add(resolved);
+ }
+ else
+ {
+ // try resolve from parent (this is backward compatible with 12.0.0)
+ resolved = monitoredDir.getParent().resolve(rawPath);
+ if (Files.isRegularFile(resolved))
+ {
+ found = true;
+ // add resolved path
+ ret.add(resolved);
+ }
+ }
+ }
+ if (!found && LOG.isDebugEnabled())
+ LOG.debug("Ignoring relative environment xml path that doesn't exist: {}", rawPath);
+ }
+ }
+
+ return ret;
+ }
+
+ private Attributes initAttributes(Environment environment, DefaultApp app) throws IOException
+ {
+ Attributes attributes = new Attributes.Mapped();
+
+ // Grab Environment attributes first
+ copyAttributes(environment, attributes);
+
+ // Overlay the app attributes
+ copyAttributes(app.getAttributes(), attributes);
+
+ // The now merged attributes
+ 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 env the environment name
+ */
+ private Properties loadEnvironmentProperties(String env) throws IOException
+ {
+ Properties props = new Properties();
+
+ Path dir = getEnvironmentsDirectory();
+ if (dir == null)
+ {
+ // nothing to load
+ return props;
+ }
+
+ if (!Files.isDirectory(dir))
+ {
+ LOG.warn("Not an environments directory: {}", dir);
+ return props;
+ }
+
+ List envPropertyFiles;
+
+ // Get all environment specific properties files for this environment,
+ // order them according to the lexical ordering of the filenames
+ try (Stream paths = Files.list(dir))
+ {
+ envPropertyFiles = paths.filter(Files::isRegularFile)
+ .filter(p -> FileID.isExtension(p, "properties"))
+ .filter(p ->
+ {
+ String name = p.getFileName().toString();
+ return name.startsWith(env);
+ })
+ .sorted(PathCollators.byName(true))
+ .toList();
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Environment property files {}", envPropertyFiles);
+
+ // Load each *.properties file
+ for (Path file : envPropertyFiles)
+ {
+ try (InputStream stream = Files.newInputStream(file))
+ {
+ Properties tmp = new Properties();
+ tmp.load(stream);
+ //put each property into our substitution pool
+ tmp.stringPropertyNames().forEach(k -> props.put(k, tmp.getProperty(k)));
+ }
+ }
+
+ return props;
+ }
+
+ /**
+ * 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 EnvironmentConfig
+ {
+ // 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;
+
+ private EnvironmentConfig(Environment environment)
+ {
+ this._environment = environment;
+ }
+
+ /**
+ * Load a java properties file as a set of Attributes for this Environment.
+ *
+ * @param path the path of the properties file
+ * @throws IOException if unable to read the properties file
+ */
+ public void loadProperties(Path path) throws IOException
+ {
+ Properties props = new Properties();
+ try (InputStream inputStream = Files.newInputStream(path))
+ {
+ props.load(inputStream);
+ props.forEach((key, value) -> _environment.setAttribute((String)key, value));
+ }
+ }
+
+ /**
+ * Convenience method for {@code loadProperties(Path.of(pathName));}
+ *
+ * @param pathName the name of the path to load.
+ * @throws IOException if unable to read the properties file
+ * @see #loadProperties(Path)
+ */
+ public void loadPropertiesFromPathName(String pathName) throws IOException
+ {
+ loadProperties(Path.of(pathName));
+ }
+
+ /**
+ * 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).
+ *
+ *
+ * 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_DEFAULT
+ */
+ public void setContextHandlerClass(String classname)
+ {
+ _environment.setAttribute(Deployable.CONTEXT_HANDLER_CLASS_DEFAULT, 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 static class MonitoredPathFilter implements FilenameFilter
+ {
+ @Override
+ public boolean accept(File dir, String name)
+ {
+ if (dir == null || !dir.canRead())
+ return false;
+
+ Path path = dir.toPath().resolve(name);
+
+ // We don't monitor subdirectories.
+ if (Files.isDirectory(path))
+ return false;
+
+ // Synthetic files (like consoles, printers, serial ports, etc) are ignored.
+ if (!Files.isRegularFile(path))
+ return false;
+
+ try
+ {
+ // ignore traditional "hidden" path entries.
+ if (name.startsWith("."))
+ return false;
+ // ignore path tagged as hidden by FS
+ if (Files.isHidden(path))
+ return false;
+ }
+ catch (IOException ignore)
+ {
+ // ignore
+ }
+
+ // The filetypes that are monitored, and we want updates for when they change.
+ return FileID.isExtension(name, "jar", "war", "xml", "properties");
+ }
+ }
+}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DeployAction.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DeployAction.java
new file mode 100644
index 000000000000..521054a82e72
--- /dev/null
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DeployAction.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// 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;
+
+/**
+ * Represents a single step in one update as a result of a scanner event.
+ */
+public class DeployAction
+{
+ public enum Type
+ {
+ REMOVE,
+ ADD;
+ }
+
+ private final Type type;
+ private final DefaultApp app;
+
+ public DeployAction(Type type, DefaultApp app)
+ {
+ this.type = type;
+ this.app = app;
+ }
+
+ public String getName()
+ {
+ return app.getName();
+ }
+
+ public DefaultApp getApp()
+ {
+ return app;
+ }
+
+ public Type getType()
+ {
+ return type;
+ }
+}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DeployActionComparator.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DeployActionComparator.java
new file mode 100644
index 000000000000..48f5235af55d
--- /dev/null
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/DeployActionComparator.java
@@ -0,0 +1,57 @@
+//
+// ========================================================================
+// 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.util.Comparator;
+
+/**
+ * The List of {@link DeployAction} sort.
+ *
+ *
+ * - {@link DeployAction#getType()} is sorted by all {@link DeployAction.Type#REMOVE}
+ * actions first, followed by all {@link DeployAction.Type#ADD} actions.
+ * - {@link DeployAction.Type#REMOVE} type are in descending alphabetically order.
+ * - {@link DeployAction.Type#ADD} type are in ascending alphabetically order.
+ *
>
+ */
+public class DeployActionComparator implements Comparator
+{
+ private final Comparator typeComparator;
+ private final Comparator basenameComparator;
+
+ public DeployActionComparator()
+ {
+ typeComparator = Comparator.comparing(DeployAction::getType);
+ basenameComparator = Comparator.comparing(DeployAction::getName);
+ }
+
+ @Override
+ public int compare(DeployAction o1, DeployAction o2)
+ {
+ int diff = typeComparator.compare(o1, o2);
+ if (diff != 0)
+ return diff;
+ return switch (o1.getType())
+ {
+ case REMOVE ->
+ {
+ yield basenameComparator.compare(o2, o1);
+ }
+ case ADD ->
+ {
+ yield basenameComparator.compare(o1, o2);
+ }
+ };
+ }
+}
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
deleted file mode 100644
index 93a776553029..000000000000
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java
+++ /dev/null
@@ -1,422 +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.FilenameFilter;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.stream.Collectors;
-
-import org.eclipse.jetty.deploy.App;
-import org.eclipse.jetty.deploy.AppProvider;
-import org.eclipse.jetty.deploy.DeploymentManager;
-import org.eclipse.jetty.deploy.providers.internal.DeploymentUnits;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.Scanner;
-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.LifeCycle;
-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;
-
-@ManagedObject("Abstract Provider for loading webapps")
-public abstract class ScanningAppProvider extends ContainerLifeCycle implements AppProvider, DeploymentUnits.Listener
-{
- private static final Logger LOG = LoggerFactory.getLogger(ScanningAppProvider.class);
-
- private DeploymentUnits _units = new DeploymentUnits();
- private DeploymentManager _deploymentManager;
- private FilenameFilter _filenameFilter;
- private Comparator _unitComparator = Comparator.comparing(Unit::getBaseName);
- private final List _monitored = new CopyOnWriteArrayList<>();
- private int _scanInterval = 10;
- private Scanner _scanner;
- private boolean _useRealPaths;
- private boolean _deferInitialScan = false;
-
- protected ScanningAppProvider()
- {
- this(null);
- }
-
- protected ScanningAppProvider(FilenameFilter filter)
- {
- _filenameFilter = filter;
- _units.setListener(this);
- installBean(_units);
- }
-
- /**
- * @return True if the real path of the scanned files should be used for deployment.
- */
- public boolean isUseRealPaths()
- {
- return _useRealPaths;
- }
-
- /**
- * @param useRealPaths True if the real path of the scanned files should be used for deployment.
- */
- public void setUseRealPaths(boolean useRealPaths)
- {
- _useRealPaths = useRealPaths;
- }
-
- protected List getUnitsForEnvironment(String envName)
- {
- return _units.getUnits().stream()
- .filter((unit) -> envName.equals(unit.getEnvironmentName()))
- .toList();
- }
-
- protected void setFilenameFilter(FilenameFilter filter)
- {
- if (isRunning())
- throw new IllegalStateException();
- _filenameFilter = filter;
- }
-
- public Comparator getUnitComparator()
- {
- return _unitComparator;
- }
-
- public void setUnitComparator(Comparator comparator)
- {
- this._unitComparator = comparator;
- }
-
- /**
- * @return The index of currently deployed applications.
- */
- protected Collection getDeployedApps()
- {
- return _units.getUnits()
- .stream()
- .map(Unit::getApp)
- .collect(Collectors.toUnmodifiableSet());
- }
-
- /**
- * Called by the Scanner.DiscreteListener to create a new App object.
- * 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 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)
- {
- App app = new App(_deploymentManager, this, path);
- if (LOG.isDebugEnabled())
- LOG.debug("{} creating {}", this, app);
- return app;
- }
-
- @Override
- protected void doStart() throws Exception
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{}.doStart()", this.getClass().getSimpleName());
- if (_monitored.isEmpty())
- throw new IllegalStateException("No monitored dir specified");
-
- LOG.info("Deployment monitor in {} at intervals {}s", _monitored, getScanInterval());
- List files = new ArrayList<>();
- for (Resource resource : _monitored)
- {
- if (Resources.missing(resource))
- {
- LOG.warn("Does not exist: {}", resource);
- continue; // skip
- }
-
- // handle resource smartly
- for (Resource r: resource)
- {
- Path path = r.getPath();
- if (path == null)
- {
- LOG.warn("Not based on FileSystem Path: {}", r);
- continue; // skip
- }
- if (Files.isDirectory(path) || Files.isReadable(path))
- files.add(resource.getPath());
- else
- LOG.warn("Unsupported Path (not a directory and/or not readable): {}", r);
- }
- }
-
- _scanner = new Scanner(null, _useRealPaths);
- _scanner.setScanDirs(files);
- _scanner.setScanInterval(_scanInterval);
- _scanner.setFilenameFilter(_filenameFilter);
- _scanner.setReportDirs(true);
- _scanner.setScanDepth(1); //consider direct dir children of monitored dir
- _scanner.addListener(_units);
- _scanner.setReportExistingFilesOnStartup(true);
- _scanner.setAutoStartScanning(!_deferInitialScan);
- addBean(_scanner);
-
- if (isDeferInitialScan())
- {
- // Setup listener to wait for Server in STARTED state, which
- // triggers the first scan of the monitored directories
- getDeploymentManager().getServer().addEventListener(
- new LifeCycle.Listener()
- {
- @Override
- public void lifeCycleStarted(LifeCycle event)
- {
- if (event instanceof Server)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Triggering Deferred Scan of {}", _monitored);
- _scanner.startScanning();
- }
- }
- });
- }
-
- super.doStart();
- }
-
- @Override
- protected void doStop() throws Exception
- {
- super.doStop();
- if (_scanner != null)
- {
- removeBean(_scanner);
- _scanner.removeListener(_units);
- _scanner = null;
- }
- }
-
- protected boolean exists(String path)
- {
- return _scanner.exists(path);
- }
-
- /**
- * Given a deployment unit, pick the main Path that is actually the point of deployment.
- *
- * @param unit a single unit of deployment.
- * @return the specific path that represents the main point of deployment (eg: xml if it exists)
- */
- protected abstract Path getMainDeploymentPath(Unit unit);
-
- @Override
- public void unitsChanged(List units)
- {
- units.sort(getUnitComparator());
-
- if (LOG.isDebugEnabled())
- LOG.debug("unitsChanged: {}", units);
-
- for (Unit unit : units)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("unit changed: {}", unit);
-
- switch (unit.getState())
- {
- case ADDED ->
- {
- Path mainDeploymentPath = getMainDeploymentPath(unit);
- App app = this.createApp(mainDeploymentPath);
- unit.setApp(app);
- if (LOG.isDebugEnabled())
- LOG.debug("Unit ADDED: {}", unit);
-
- if (app != null)
- _deploymentManager.addApp(app);
- }
- case CHANGED ->
- {
- App oldApp = unit.removeApp();
- if (oldApp != null)
- _deploymentManager.removeApp(oldApp);
-
- Path mainDeploymentPath = getMainDeploymentPath(unit);
- App app = this.createApp(mainDeploymentPath);
- unit.setApp(app);
-
- if (LOG.isDebugEnabled())
- LOG.debug("Unit CHANGED: {}", unit);
-
- if (app != null)
- _deploymentManager.addApp(app);
- }
- case REMOVED ->
- {
- App app = unit.removeApp();
- if (LOG.isDebugEnabled())
- LOG.debug("Unit REMOVED: {}", unit);
- if (app != null)
- _deploymentManager.removeApp(app);
- }
- }
-
- // we are done processing unit, reset its state
- unit.resetStates();
- }
- }
-
- /**
- * Get the deploymentManager.
- *
- * @return the deploymentManager
- */
- public DeploymentManager getDeploymentManager()
- {
- return _deploymentManager;
- }
-
- public Resource getMonitoredDirResource()
- {
- if (_monitored.isEmpty())
- return null;
- if (_monitored.size() > 1)
- throw new IllegalStateException();
- return _monitored.get(0);
- }
-
- public String getMonitoredDirName()
- {
- Resource resource = getMonitoredDirResource();
- return resource == null ? null : resource.toString();
- }
-
- @ManagedAttribute("scanning interval to detect changes which need reloaded")
- public int getScanInterval()
- {
- return _scanInterval;
- }
-
- @Override
- public void setDeploymentManager(DeploymentManager deploymentManager)
- {
- _deploymentManager = deploymentManager;
- }
-
- public void setMonitoredResources(List resources)
- {
- _monitored.clear();
- if (resources == null)
- return;
- resources.stream().filter(Objects::nonNull).forEach(_monitored::add);
- }
-
- public List getMonitoredResources()
- {
- return Collections.unmodifiableList(_monitored);
- }
-
- public void setMonitoredDirResource(Resource resource)
- {
- setMonitoredResources(Collections.singletonList(resource));
- }
-
- public void addScannerListener(Scanner.Listener listener)
- {
- _scanner.addListener(listener);
- }
-
- /**
- * @param dir Directory to scan for context descriptors or war files
- */
- public void setMonitoredDirName(String dir)
- {
- setMonitoredDirectories(Collections.singletonList(dir));
- }
-
- public void setMonitoredDirectories(Collection directories)
- {
- try
- {
- List resources = new ArrayList<>();
- for (String dir : directories)
- {
- resources.add(ResourceFactory.of(this).newResource(dir));
- }
- setMonitoredResources(resources);
- }
- catch (Exception e)
- {
- throw new IllegalArgumentException(e);
- }
- }
-
- /**
- * Test if initial scan should be deferred.
- *
- * @return true if initial scan is deferred, false to have initial scan occur on startup of ScanningAppProvider.
- */
- public boolean isDeferInitialScan()
- {
- return _deferInitialScan;
- }
-
- /**
- * Flag to control initial scan behavior.
- *
- *
- * - {@code true} - to have initial scan deferred until the {@link Server} component
- * has reached it's STARTED state.
- * Note: any failures in a deploy will not fail the Server startup in this mode.
- * - {@code false} - (default value) to have initial scan occur as normal on
- * ScanningAppProvider startup.
- *
- *
- * @param defer true to defer initial scan, false to have initial scan occur on startup of ScanningAppProvider.
- */
- public void setDeferInitialScan(boolean defer)
- {
- _deferInitialScan = defer;
- }
-
- public void setScanInterval(int scanInterval)
- {
- _scanInterval = scanInterval;
- }
-
- @ManagedOperation(value = "Scan the monitored directories", impact = "ACTION")
- public void scan()
- {
- LOG.info("Performing scan of monitored directories: {}",
- getMonitoredResources().stream().map((r) -> r.getURI().toASCIIString())
- .collect(Collectors.joining(", ", "[", "]"))
- );
- _scanner.nudge();
- }
-
- @Override
- public String toString()
- {
- return String.format("%s@%x%s", this.getClass(), hashCode(), _monitored);
- }
-}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/Unit.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/Unit.java
deleted file mode 100644
index fabd82d33523..000000000000
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/Unit.java
+++ /dev/null
@@ -1,249 +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.nio.file.Path;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-import org.eclipse.jetty.deploy.App;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.resource.PathCollators;
-
-/**
- * A Unit of deployment, a basename and all the associated
- * paths that belong to that basename, along with state.
- */
-public class Unit
-{
- public enum State
- {
- UNCHANGED,
- ADDED,
- CHANGED,
- REMOVED
- }
-
- private final String baseName;
- private final Map paths = new HashMap<>();
- /**
- * Indicates a synthetic Unit, used to track Environment specific configurations.
- * Such as {@code .xml}, {@code .properties}, {@code -.xml}, {@code -.properties},
- * all under a single unit that will not actually be deployed.
- */
- private String envConfig;
- private App app;
- private State state;
-
- public Unit(String basename)
- {
- this.baseName = basename;
- this.state = calcState();
- }
-
- public String getBaseName()
- {
- return baseName;
- }
-
- public State getState()
- {
- return state;
- }
-
- /**
- *
- * Calculate the State of the overall Unit based on the States in the Paths.
- *
- *
- * - UNCHANGED
- * - All Path states are in UNCHANGED state
- * - ADDED
- * - All Path states are in ADDED state
- * - CHANGED
- * - At least one Path state is CHANGED, or there is a variety of states
- * - REMOVED
- * - All Path states are in REMOVED state, or there are no Paths being tracked
- *
- *
- * @return the state of the Unit.
- */
- private State calcState()
- {
- if (paths.isEmpty())
- return State.REMOVED;
-
- // Calculate state of unit from Path states.
- Unit.State ret = null;
- for (Unit.State pathState : paths.values())
- {
- switch (pathState)
- {
- case UNCHANGED ->
- {
- if (ret == null)
- ret = State.UNCHANGED;
- else if (ret != State.UNCHANGED)
- ret = State.CHANGED;
- }
- case ADDED ->
- {
- if (ret == null)
- ret = State.ADDED;
- else if (ret == State.UNCHANGED || ret == State.REMOVED)
- ret = State.ADDED;
- }
- case CHANGED ->
- {
- ret = State.CHANGED;
- }
- case REMOVED ->
- {
- if (ret == null)
- ret = State.REMOVED;
- else if (ret != State.REMOVED)
- ret = State.CHANGED;
- }
- }
- }
- return ret != null ? ret : State.UNCHANGED;
- }
-
- public Map getPaths()
- {
- return paths;
- }
-
- /**
- * Return list of Paths being tracked that are live (not-removed).
- *
- * @return the list of live paths.
- */
- public List getLivePaths()
- {
- return paths
- .entrySet()
- .stream()
- .filter((e) -> e.getValue() != Unit.State.REMOVED)
- .map(Map.Entry::getKey)
- .sorted(PathCollators.byName(true))
- .toList();
- }
-
- public void putPath(Path path, State state)
- {
- this.paths.put(path, state);
- setState(calcState());
- }
-
- public boolean isDeployable()
- {
- return StringUtil.isBlank(envConfig);
- }
-
- public String getEnvironmentName()
- {
- if (app == null)
- return null;
- return app.getEnvironmentName();
- }
-
- public String getEnvironmentConfigName()
- {
- return envConfig;
- }
-
- public void setEnvironmentConfigName(String environmentConfigName)
- {
- this.envConfig = environmentConfigName;
- }
-
- public App getApp()
- {
- return app;
- }
-
- public void setApp(App app)
- {
- this.app = app;
- }
-
- public App removeApp()
- {
- App oldApp = this.app;
- this.app = null;
- return oldApp;
- }
-
- public void resetStates()
- {
- // Drop paths that were removed.
- List removedPaths = paths.entrySet()
- .stream().filter(e -> e.getValue() == State.REMOVED)
- .map(Map.Entry::getKey)
- .toList();
- for (Path removedPath : removedPaths)
- {
- paths.remove(removedPath);
- }
- // Set all remaining path states to UNCHANGED
- paths.replaceAll((p, v) -> State.UNCHANGED);
- state = calcState();
- }
-
- @Override
- public boolean equals(Object o)
- {
- if (o == null || getClass() != o.getClass())
- return false;
- Unit unit = (Unit)o;
- return Objects.equals(baseName, unit.baseName);
- }
-
- @Override
- public int hashCode()
- {
- return Objects.hashCode(baseName);
- }
-
- public void setState(State state)
- {
- this.state = state;
- }
-
- @Override
- public String toString()
- {
- StringBuilder str = new StringBuilder();
- str.append("Unit[").append(baseName);
- str.append("|").append(getState());
- if (envConfig != null)
- str.append(", envConfig=").append(envConfig);
- str.append(", paths=");
- str.append(paths.entrySet().stream()
- .map((e) -> String.format("%s|%s", e.getKey(), e.getValue()))
- .collect(Collectors.joining(", ", "[", "]"))
- );
- str.append(", app=");
- if (app == null)
- str.append("");
- else
- str.append(app);
- str.append("]");
- return str.toString();
- }
-}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/internal/DeploymentUnits.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/internal/DeploymentUnits.java
deleted file mode 100644
index aadcdea2a4b0..000000000000
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/internal/DeploymentUnits.java
+++ /dev/null
@@ -1,178 +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.internal;
-
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.EventListener;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import org.eclipse.jetty.deploy.providers.Unit;
-import org.eclipse.jetty.util.FileID;
-import org.eclipse.jetty.util.Scanner;
-import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.component.Environment;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The role of DeploymentUnits is to keep track of {@link Unit} instances, and
- * process changes coming from {@link java.util.Scanner} into a set of units
- * that will be processed via a listener.
- */
-public class DeploymentUnits implements Scanner.ChangeSetListener
-{
- private static final Logger LOG = LoggerFactory.getLogger(DeploymentUnits.class);
-
- /**
- * Basename of unit, to the Unit instance.
- */
- private Map units = new HashMap<>();
-
- private Listener listener;
-
- public Listener getListener()
- {
- return listener;
- }
-
- public void setListener(Listener listener)
- {
- this.listener = listener;
- }
-
- public Unit getUnit(String basename)
- {
- return units.get(basename);
- }
-
- public Collection getUnits()
- {
- return units.values();
- }
-
- @Override
- public void pathsChanged(Map changeSet)
- {
- Objects.requireNonNull(changeSet);
- if (LOG.isDebugEnabled())
- {
- LOG.debug("processChanges: changeSet: {}",
- changeSet.entrySet()
- .stream()
- .map((e) -> String.format("%s|%s", e.getKey(), e.getValue()))
- .collect(Collectors.joining(", ", "[", "]"))
- );
- }
-
- Set changedBaseNames = new HashSet<>();
- Set environmentNames = Environment.getAll().stream()
- .map(Environment::getName).collect(Collectors.toUnmodifiableSet());
-
- for (Map.Entry entry : changeSet.entrySet())
- {
- Path path = entry.getKey();
- Unit.State state = toState(entry.getValue());
-
- // Using lower-case as defined by System Locale, as the files themselves from System FS.
- String basename = FileID.getBasename(path).toLowerCase();
-
- String envname = getEnvironmentName(basename, environmentNames);
- if (StringUtil.isNotBlank(envname))
- {
- // The file starts with a known environment name.
- // We only care if it is a Environment Configuration file, namely an XML or Properties file.
- if (FileID.isExtension(path, "xml", "properties"))
- {
- // we have an environment configuration specific path entry.
- changedBaseNames.add(envname);
- Unit unit = units.computeIfAbsent(envname, Unit::new);
- unit.setEnvironmentConfigName(envname);
- unit.putPath(path, state);
- }
- else
- {
- LOG.warn("Encountered Path that uses reserved Environment name prefix " +
- "(This is seen as a configuration for the environment) [{}]: {}", envname, path);
- // This is something like ee10-app.war
- // For now, we add it to the basename, but in the future, this will be rejected
- // as it prevents the use of a configuration file like ee10-app.xml next to it (which
- // will be seen as a XML to configure the ee10 environment, not an XML for deploying that war)
- changedBaseNames.add(basename);
- Unit unit = units.computeIfAbsent(basename, Unit::new);
- unit.putPath(path, state);
- }
- }
- else
- {
- // we have a normal path entry
- changedBaseNames.add(basename);
- Unit unit = units.computeIfAbsent(basename, Unit::new);
- unit.putPath(path, state);
- }
- }
-
- Listener listener = getListener();
- if (listener != null)
- {
- List changedUnits = changedBaseNames.stream()
- .map(name -> units.get(name))
- .collect(Collectors.toList());
-
- listener.unitsChanged(changedUnits);
- }
- }
-
- protected String getEnvironmentName(String basename, Set environmentNames)
- {
- // Handle the case where the basename is part of an environment configuration
- for (String envname : environmentNames)
- {
- if (basename.startsWith(envname))
- return envname;
- }
-
- return null;
- }
-
- private Unit.State toState(Scanner.Notification notification)
- {
- return switch (notification)
- {
- case ADDED ->
- {
- yield Unit.State.ADDED;
- }
- case CHANGED ->
- {
- yield Unit.State.CHANGED;
- }
- case REMOVED ->
- {
- yield Unit.State.REMOVED;
- }
- };
- }
-
- public interface Listener extends EventListener
- {
- void unitsChanged(List units);
- }
-}
diff --git a/jetty-core/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/lifecycle-bindings.txt b/jetty-core/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/lifecycle-bindings.txt
deleted file mode 100644
index ed00104daf35..000000000000
--- a/jetty-core/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/lifecycle-bindings.txt
+++ /dev/null
@@ -1 +0,0 @@
-# Default Bindings
\ No newline at end of file
diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCyclePathCollector.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCyclePathCollector.java
index cb0c14e2f3b5..ef6355cf7922 100644
--- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCyclePathCollector.java
+++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCyclePathCollector.java
@@ -45,7 +45,7 @@ public String[] getBindingTargets()
}
@Override
- public void processBinding(Node node, App app) throws Exception
+ public void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
actualOrder.add(node);
}
diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCycleRouteTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCycleRouteTest.java
index 21646d04e897..fc1578e674f6 100644
--- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCycleRouteTest.java
+++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCycleRouteTest.java
@@ -42,8 +42,8 @@ public void testStateTransitionNewToDeployed() throws Exception
depman.start();
// Trigger new App
- mockProvider.createWebapp("foo-webapp-1.war");
- App app = depman.getApp("mock-foo-webapp-1.war");
+ mockProvider.createWebapp("foo-webapp-1");
+ App app = depman.getApp("foo-webapp-1");
// Request Deploy of App
depman.requestAppGoal(app, "deployed");
@@ -78,7 +78,7 @@ public void testStateTransitionReceive() throws Exception
// Perform no goal request.
// Setup Expectations.
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
pathtracker.assertExpected("Test StateTransition / New only", expected);
}
@@ -103,8 +103,8 @@ public void testStateTransitionDeployedToUndeployed() throws Exception
depman.start();
// Trigger new App
- App foo = mockProvider.createWebapp("foo-webapp-1.war");
- App app = depman.getApp(foo.getPath());
+ App foo = mockProvider.createWebapp("foo-webapp-1");
+ App app = depman.getApp(foo.getName());
// Request Deploy of App
depman.requestAppGoal(app, "deployed");
@@ -114,12 +114,12 @@ public void testStateTransitionDeployedToUndeployed() throws Exception
MBeanServerConnection mbsConnection = jmxConnection.getConnection();
ObjectName dmObjName = new ObjectName("org.eclipse.jetty.deploy:type=deploymentmanager,id=0");
- String[] params = new String[]{"mock-foo-webapp-1.war", "undeployed"};
+ String[] params = new String[]{"foo-webapp-1", "undeployed"};
String[] signature = new String[]{"java.lang.String", "java.lang.String"};
mbsConnection.invoke(dmObjName, "requestAppGoal", params, signature);
// Setup Expectations.
- List expected = new ArrayList();
+ List expected = new ArrayList<>();
// SHOULD NOT SEE THIS NODE VISITED - expected.add("undeployed");
expected.add("deploying");
expected.add("deployed");
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 f3e8b85d4a12..2c2dae447df1 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
@@ -19,20 +19,17 @@
import java.util.Set;
import org.eclipse.jetty.deploy.test.XmlConfiguredJetty;
-import org.eclipse.jetty.server.Deployable;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+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.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;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -84,9 +81,9 @@ public void testReceiveApp() throws Exception
// Test app get
App app = apps.stream().findFirst().orElse(null);
assertNotNull(app);
- App actual = depman.getApp(app.getPath());
+ App actual = depman.getApp(app.getName());
assertNotNull(actual, "Should have gotten app (by id)");
- assertThat(actual.getPath().toString(), endsWith("mock-foo-webapp-1.war"));
+ assertThat(actual.getName(), is("foo-webapp-1"));
}
finally
{
@@ -110,76 +107,6 @@ public void testBinding()
assertEquals(1, deploybindings.size(), "'deploying' Bindings.size");
}
- @Test
- public void testDefaultEnvironment()
- {
- DeploymentManager depman = new DeploymentManager();
- assertThat(depman.getDefaultEnvironmentName(), Matchers.nullValue());
-
- Environment.ensure("ee7");
- depman.addAppProvider(new MockAppProvider()
- {
- @Override
- public String getEnvironmentName()
- {
- return "ee7";
- }
- });
- assertThat(depman.getDefaultEnvironmentName(), is("ee7"));
-
- Environment.ensure("ee12");
- depman.addAppProvider(new MockAppProvider()
- {
- @Override
- public String getEnvironmentName()
- {
- return "ee12";
- }
- });
- assertThat(depman.getDefaultEnvironmentName(), is("ee12"));
-
- Environment.ensure("ee11");
- depman.addAppProvider(new MockAppProvider()
- {
- @Override
- public String getEnvironmentName()
- {
- return "ee11";
- }
- });
- assertThat(depman.getDefaultEnvironmentName(), is("ee12"));
-
- Environment.ensure("somethingElse");
- depman.addAppProvider(new MockAppProvider()
- {
- @Override
- public String getEnvironmentName()
- {
- return "somethingElse";
- }
- });
- assertThat(depman.getDefaultEnvironmentName(), is("ee12"));
-
- Environment.ensure("other");
- depman.addAppProvider(new MockAppProvider()
- {
- @Override
- public String getEnvironmentName()
- {
- return "other";
- }
- });
-
- assertThat(depman.getAppProviders().stream().map(AppProvider::getEnvironmentName).sorted(Deployable.ENVIRONMENT_COMPARATOR).toList(),
- contains(
- "other",
- "somethingElse",
- "ee7",
- "ee11",
- "ee12"
- ));
- }
-
@Test
public void testXmlConfigured(WorkDir workDir) throws Exception
{
@@ -188,9 +115,9 @@ public void testXmlConfigured(WorkDir workDir) throws Exception
try
{
jetty = new XmlConfiguredJetty(testdir);
- jetty.addConfiguration("jetty.xml");
- jetty.addConfiguration("jetty-http.xml");
- jetty.addConfiguration("jetty-core-deploy-custom.xml");
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty.xml"));
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty-http.xml"));
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("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/MockApp.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockApp.java
new file mode 100644
index 000000000000..3f1fa51dbe91
--- /dev/null
+++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockApp.java
@@ -0,0 +1,50 @@
+//
+// ========================================================================
+// 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;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+public class MockApp implements App
+{
+ private final String name;
+ private ContextHandler contextHandler;
+
+ public MockApp(String name)
+ {
+ this.name = name;
+ }
+
+ @Override
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setContextHandler(ContextHandler contextHandler)
+ {
+ this.contextHandler = contextHandler;
+ }
+
+ @Override
+ public ContextHandler getContextHandler()
+ {
+ return contextHandler;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[%s]", this.getClass().getSimpleName(), hashCode(), getName());
+ }
+}
diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java
index 57af532999fc..051b35a8eece 100644
--- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java
+++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java
@@ -26,7 +26,6 @@ public class MockAppProvider extends AbstractLifeCycle implements AppProvider
private DeploymentManager deployMan;
private Path webappsDir;
- @Override
public String getEnvironmentName()
{
return Environment.ensure("mock").getName();
@@ -46,41 +45,41 @@ public void doStart()
public App createWebapp(String name)
{
- App app = new App(deployMan, this, Path.of("./mock-" + name));
+ String basename = FileID.getBasename(name);
+ MockApp app = new MockApp(basename);
+ app.setContextHandler(createContextHandler(app));
this.deployMan.addApp(app);
return app;
}
- @Override
public ContextHandler createContextHandler(App app)
{
ContextHandler contextHandler = new ContextHandler();
- String name = app.getPath().toString();
- name = name.substring(name.lastIndexOf("-") + 1);
- Path war = webappsDir.resolve(name);
+ String name = app.getName();
+ Path war = webappsDir.resolve(name + ".war");
- String path = war.toString();
+ String contextPath = war.toString();
if (FileID.isWebArchive(war))
{
// Context Path is the same as the archive.
- path = path.substring(0, path.length() - 4);
+ contextPath = FileID.getBasename(war);
}
- // special case of archive (or dir) named "root" is / context
- if (path.equalsIgnoreCase("root") || path.equalsIgnoreCase("root/"))
- path = "/";
+ // special case of archive named "root" is / context-path
+ if (contextPath.equalsIgnoreCase("root"))
+ contextPath = "/";
// Ensure "/" is Prepended to all context paths.
- if (path.charAt(0) != '/')
- path = "/" + path;
+ if (contextPath.charAt(0) != '/')
+ contextPath = "/" + contextPath;
// Ensure "/" is Not Trailing in context paths.
- if (path.endsWith("/") && path.length() > 0)
- path = path.substring(0, path.length() - 1);
+ if (contextPath.endsWith("/"))
+ contextPath = contextPath.substring(0, contextPath.length() - 1);
- contextHandler.setContextPath(path);
+ contextHandler.setContextPath(contextPath);
return contextHandler;
}
diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderTest.java
deleted file mode 100644
index 6a30dc6d1c71..000000000000
--- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderTest.java
+++ /dev/null
@@ -1,154 +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.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.eclipse.jetty.deploy.AbstractCleanEnvironmentTest;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-
-@ExtendWith(WorkDirExtension.class)
-public class ContextProviderTest extends AbstractCleanEnvironmentTest
-{
- public WorkDir workDir;
-
- /**
- * Ensuring heuristics defined in javadoc for {@link ContextProvider} is followed.
- *
- * Test with only a single XML.
- */
- @Test
- public void testMainDeploymentPathOnlyXml() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path xml = dir.resolve("bar.xml");
- Files.writeString(xml, "XML for bar", UTF_8);
-
- Unit unit = new Unit("bar");
- unit.putPath(xml, Unit.State.UNCHANGED);
-
- ContextProvider provider = new ContextProvider();
- Path main = provider.getMainDeploymentPath(unit);
-
- assertThat("main path", main, equalTo(xml));
- }
-
- /**
- * Ensuring heuristics defined in javadoc for {@link ContextProvider} is followed.
- *
- * Test with an XML and WAR.
- */
- @Test
- public void testMainDeploymentPathXmlAndWar() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path xml = dir.resolve("bar.xml");
- Files.writeString(xml, "XML for bar", UTF_8);
- Path war = dir.resolve("bar.war");
- Files.writeString(war, "WAR for bar", UTF_8);
-
- Unit unit = new Unit("bar");
- unit.putPath(xml, Unit.State.UNCHANGED);
- unit.putPath(war, Unit.State.UNCHANGED);
-
- ContextProvider provider = new ContextProvider();
- Path main = provider.getMainDeploymentPath(unit);
-
- assertThat("main path", main, equalTo(xml));
- }
-
- /**
- * Ensuring heuristics defined in javadoc for {@link ContextProvider} is followed.
- *
- * Test with a Directory and WAR.
- */
- @Test
- public void testMainDeploymentPathDirAndWar() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path appDir = dir.resolve("bar");
- Files.createDirectory(appDir);
- Path war = dir.resolve("bar.war");
- Files.writeString(war, "WAR for bar", UTF_8);
-
- Unit unit = new Unit("bar");
- unit.putPath(appDir, Unit.State.UNCHANGED);
- unit.putPath(war, Unit.State.UNCHANGED);
-
- ContextProvider provider = new ContextProvider();
- Path main = provider.getMainDeploymentPath(unit);
-
- assertThat("main path", main, equalTo(war));
- }
-
- /**
- * Ensuring heuristics defined in javadoc for {@link ContextProvider} is followed.
- *
- * Test with a Directory and XML.
- */
- @Test
- public void testMainDeploymentPathDirAndXml() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path appDir = dir.resolve("bar");
- Files.createDirectory(appDir);
- Path xml = dir.resolve("bar.xml");
- Files.writeString(xml, "XML for bar", UTF_8);
-
- Unit unit = new Unit("bar");
- unit.putPath(appDir, Unit.State.UNCHANGED);
- unit.putPath(xml, Unit.State.UNCHANGED);
-
- ContextProvider provider = new ContextProvider();
- Path main = provider.getMainDeploymentPath(unit);
-
- assertThat("main path", main, equalTo(xml));
- }
-
- /**
- * Ensuring heuristics defined in javadoc for {@link ContextProvider} is followed.
- *
- * Test with a Directory and XML and WAR
- */
- @Test
- public void testMainDeploymentPathDirAndXmlAndWar() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path appDir = dir.resolve("bar");
- Files.createDirectory(appDir);
- Path xml = dir.resolve("bar.xml");
- Files.writeString(xml, "XML for bar", UTF_8);
- Path war = dir.resolve("bar.war");
- Files.writeString(war, "WAR for bar", UTF_8);
-
- Unit unit = new Unit("bar");
- unit.putPath(appDir, Unit.State.UNCHANGED);
- unit.putPath(xml, Unit.State.UNCHANGED);
- unit.putPath(war, Unit.State.UNCHANGED);
-
- ContextProvider provider = new ContextProvider();
- Path main = provider.getMainDeploymentPath(unit);
-
- assertThat("main path", main, equalTo(xml));
- }
-}
diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DefaultAppTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DefaultAppTest.java
new file mode 100644
index 000000000000..6eebbf81854a
--- /dev/null
+++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DefaultAppTest.java
@@ -0,0 +1,257 @@
+//
+// ========================================================================
+// 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.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
+@ExtendWith(WorkDirExtension.class)
+public class DefaultAppTest
+{
+ public WorkDir workDir;
+
+ /**
+ * Ensuring main path heuristics is followed.
+ *
+ * Test with only a single XML.
+ */
+ @Test
+ public void testMainPathOnlyXml() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+
+ Path xml = dir.resolve("bar.xml");
+ Files.writeString(xml, "XML for bar", UTF_8);
+
+ DefaultApp app = new DefaultApp("bar");
+ app.putPath(xml, DefaultApp.State.UNCHANGED);
+
+ Path main = app.getMainPath();
+
+ assertThat("main path", main, equalTo(xml));
+ }
+
+ /**
+ * Ensuring main path heuristics is followed.
+ *
+ * Test with an XML and WAR.
+ */
+ @Test
+ public void testMainPathXmlAndWar() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+ Path xml = dir.resolve("bar.xml");
+ Files.writeString(xml, "XML for bar", UTF_8);
+ Path war = dir.resolve("bar.war");
+ Files.writeString(war, "WAR for bar", UTF_8);
+
+ DefaultApp app = new DefaultApp("bar");
+ app.putPath(xml, DefaultApp.State.UNCHANGED);
+ app.putPath(war, DefaultApp.State.UNCHANGED);
+
+ Path main = app.getMainPath();
+
+ assertThat("main path", main, equalTo(xml));
+ }
+
+ /**
+ * Ensuring main path heuristics is followed.
+ *
+ * Test with a Directory and WAR.
+ */
+ @Test
+ public void testMainPathDirAndWar() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+ Path appDir = dir.resolve("bar");
+ Files.createDirectory(appDir);
+ Path war = dir.resolve("bar.war");
+ Files.writeString(war, "WAR for bar", UTF_8);
+
+ DefaultApp app = new DefaultApp("bar");
+ app.putPath(appDir, DefaultApp.State.UNCHANGED);
+ app.putPath(war, DefaultApp.State.UNCHANGED);
+
+ Path main = app.getMainPath();
+
+ assertThat("main path", main, equalTo(war));
+ }
+
+ /**
+ * Ensuring main path heuristics is followed.
+ *
+ * Test with a Directory and XML.
+ */
+ @Test
+ public void testMainPathDirAndXml() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+ Path appDir = dir.resolve("bar");
+ Files.createDirectory(appDir);
+ Path xml = dir.resolve("bar.xml");
+ Files.writeString(xml, "XML for bar", UTF_8);
+
+ DefaultApp app = new DefaultApp("bar");
+ app.putPath(appDir, DefaultApp.State.UNCHANGED);
+ app.putPath(xml, DefaultApp.State.UNCHANGED);
+
+ Path main = app.getMainPath();
+
+ assertThat("main path", main, equalTo(xml));
+ }
+
+ /**
+ * Ensuring main path heuristics is followed.
+ *
+ * Test with a Directory and XML and WAR
+ */
+ @Test
+ public void testMainPathDirAndXmlAndWar() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+ Path appDir = dir.resolve("bar");
+ Files.createDirectory(appDir);
+ Path xml = dir.resolve("bar.xml");
+ Files.writeString(xml, "XML for bar", UTF_8);
+ Path war = dir.resolve("bar.war");
+ Files.writeString(war, "WAR for bar", UTF_8);
+
+ DefaultApp app = new DefaultApp("bar");
+ app.putPath(appDir, DefaultApp.State.UNCHANGED);
+ app.putPath(xml, DefaultApp.State.UNCHANGED);
+ app.putPath(war, DefaultApp.State.UNCHANGED);
+
+ Path main = app.getMainPath();
+
+ assertThat("main path", main, equalTo(xml));
+ }
+
+ @Test
+ public void testStateUnchanged()
+ {
+ DefaultApp app = new DefaultApp("test");
+ app.putPath(Path.of("test-a"), DefaultApp.State.UNCHANGED);
+ app.putPath(Path.of("test-b"), DefaultApp.State.UNCHANGED);
+
+ assertThat(app.getState(), is(DefaultApp.State.UNCHANGED));
+ }
+
+ @Test
+ public void testStateInitialEmpty()
+ {
+ DefaultApp app = new DefaultApp("test");
+ // intentionally empty of Paths
+
+ assertThat(app.getState(), is(DefaultApp.State.REMOVED));
+ }
+
+ @Test
+ public void testStatePutThenRemoveAll()
+ {
+ DefaultApp app = new DefaultApp("test");
+ app.putPath(Path.of("test-a"), DefaultApp.State.ADDED);
+ assertThat(app.getState(), is(DefaultApp.State.ADDED));
+
+ // Now it gets flagged as removed. (eg: by a Scanner change)
+ app.putPath(Path.of("test-a"), DefaultApp.State.REMOVED);
+ // Then it gets processed, which results in a state reset.
+ app.resetStates();
+
+ // The resulting Unit should have no paths, and be flagged as removed.
+ assertThat(app.getPaths().size(), is(0));
+ assertThat(app.getState(), is(DefaultApp.State.REMOVED));
+ }
+
+ @Test
+ public void testStateAddedOnly()
+ {
+ DefaultApp app = new DefaultApp("test");
+ app.putPath(Path.of("test-a"), DefaultApp.State.ADDED);
+ app.putPath(Path.of("test-b"), DefaultApp.State.ADDED);
+
+ assertThat(app.getState(), is(DefaultApp.State.ADDED));
+ }
+
+ @Test
+ public void testStateAddedRemoved()
+ {
+ DefaultApp app = new DefaultApp("test");
+ app.putPath(Path.of("test-a"), DefaultApp.State.REMOVED); // existing file removed in this scan
+ app.putPath(Path.of("test-b"), DefaultApp.State.ADDED); // new file introduced in this scan event
+
+ assertThat(app.getState(), is(DefaultApp.State.CHANGED));
+ }
+
+ @Test
+ public void testStateAddedChanged()
+ {
+ DefaultApp app = new DefaultApp("test");
+ app.putPath(Path.of("test-a"), DefaultApp.State.CHANGED); // existing file changed in this scan
+ app.putPath(Path.of("test-b"), DefaultApp.State.ADDED); // new file introduced in this scan event
+
+ assertThat(app.getState(), is(DefaultApp.State.CHANGED));
+ }
+
+ @Test
+ public void testStateUnchangedAdded()
+ {
+ DefaultApp app = new DefaultApp("test");
+ app.putPath(Path.of("test-a"), DefaultApp.State.UNCHANGED); // existed in previous scan
+ app.putPath(Path.of("test-b"), DefaultApp.State.ADDED); // new file introduced in this scan event
+
+ assertThat(app.getState(), is(DefaultApp.State.CHANGED));
+ }
+
+ @Test
+ public void testStateUnchangedChanged()
+ {
+ DefaultApp app = new DefaultApp("test");
+ app.putPath(Path.of("test-a"), DefaultApp.State.UNCHANGED); // existed in previous scan
+ app.putPath(Path.of("test-b"), DefaultApp.State.CHANGED); // existing file changed in this scan event
+
+ assertThat(app.getState(), is(DefaultApp.State.CHANGED));
+ }
+
+ @Test
+ public void testStateUnchangedRemoved()
+ {
+ DefaultApp app = new DefaultApp("test");
+ app.putPath(Path.of("test-a"), DefaultApp.State.UNCHANGED); // existed in previous scan
+ app.putPath(Path.of("test-b"), DefaultApp.State.REMOVED); // existing file removed in this scan event
+
+ assertThat(app.getState(), is(DefaultApp.State.CHANGED));
+ }
+
+ @Test
+ public void testStateUnchangedRemovedAdded()
+ {
+ DefaultApp app = new DefaultApp("test");
+ app.putPath(Path.of("test-a"), DefaultApp.State.UNCHANGED); // existed in previous scan
+ app.putPath(Path.of("test-b"), DefaultApp.State.REMOVED); // existing file changed in this scan event
+ app.putPath(Path.of("test-c"), DefaultApp.State.ADDED); // new file introduced in this scan event
+
+ assertThat(app.getState(), is(DefaultApp.State.CHANGED));
+ }
+}
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/DefaultProviderDeferredStartupTest.java
similarity index 92%
rename from jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderDeferredStartupTest.java
rename to jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DefaultProviderDeferredStartupTest.java
index 7b957380255e..0a892fb6b7df 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/DefaultProviderDeferredStartupTest.java
@@ -48,7 +48,7 @@
* Tests {@link ContextProvider} behaviors when in Deferred Startup mode
*/
@ExtendWith(WorkDirExtension.class)
-public class ContextProviderDeferredStartupTest extends AbstractCleanEnvironmentTest
+public class DefaultProviderDeferredStartupTest extends AbstractCleanEnvironmentTest
{
public WorkDir testdir;
private static XmlConfiguredJetty jetty;
@@ -66,11 +66,11 @@ public void testDelayedDeploy() throws Exception
// Set jetty up on the real base
jetty = new XmlConfiguredJetty(realBase);
- jetty.addConfiguration("jetty.xml");
- jetty.addConfiguration("jetty-http.xml");
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty.xml"));
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty-http.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");
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty-core-deploy-custom.xml"));
// Put a context into the base
jetty.copyWebapp("bar-core-context.xml", "bar.xml");
@@ -99,7 +99,7 @@ public void lifeCycleStarted(LifeCycle event)
{
eventQueue.add("Server started");
}
- if (event instanceof ScanningAppProvider)
+ if (event instanceof DefaultProvider)
{
eventQueue.add("ScanningAppProvider started");
}
@@ -112,13 +112,13 @@ public void lifeCycleStarted(LifeCycle event)
server.addEventListener(eventCaptureListener);
- ScanningAppProvider scanningAppProvider = null;
+ DefaultProvider scanningAppProvider = null;
DeploymentManager deploymentManager = server.getBean(DeploymentManager.class);
for (AppProvider appProvider : deploymentManager.getAppProviders())
{
- if (appProvider instanceof ScanningAppProvider)
+ if (appProvider instanceof DefaultProvider)
{
- scanningAppProvider = (ScanningAppProvider)appProvider;
+ scanningAppProvider = (DefaultProvider)appProvider;
}
}
assertNotNull(scanningAppProvider, "Should have found ScanningAppProvider");
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/DefaultProviderRuntimeUpdatesTest.java
similarity index 92%
rename from jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderRuntimeUpdatesTest.java
rename to jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DefaultProviderRuntimeUpdatesTest.java
index c54ec97d769e..958b6b8f096d 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/DefaultProviderRuntimeUpdatesTest.java
@@ -45,13 +45,13 @@
import static org.junit.jupiter.api.Assertions.assertNotSame;
/**
- * Similar in scope to {@link ContextProviderStartupTest}, except is concerned with the modification of existing
- * deployed contexts due to incoming changes identified by the {@link ContextProvider}.
+ * Similar in scope to {@link DefaultProviderStartupTest}, except is concerned with the modification of existing
+ * deployed contexts due to incoming changes identified by the {@link DefaultProvider}.
*/
@ExtendWith(WorkDirExtension.class)
-public class ContextProviderRuntimeUpdatesTest extends AbstractCleanEnvironmentTest
+public class DefaultProviderRuntimeUpdatesTest extends AbstractCleanEnvironmentTest
{
- private static final Logger LOG = LoggerFactory.getLogger(ContextProviderRuntimeUpdatesTest.class);
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultProviderRuntimeUpdatesTest.class);
private static XmlConfiguredJetty jetty;
private final AtomicInteger _scans = new AtomicInteger();
@@ -81,11 +81,11 @@ public void createJettyBase(Path testdir) throws Exception
public void startJetty() throws Exception
{
- jetty.addConfiguration("jetty.xml");
- jetty.addConfiguration("jetty-http.xml");
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty.xml"));
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty-http.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");
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty-core-deploy-custom.xml"));
// Should not throw an Exception
jetty.load();
@@ -97,7 +97,7 @@ public void startJetty() throws Exception
DeploymentManager dm = jetty.getServer().getBean(DeploymentManager.class);
for (AppProvider provider : dm.getAppProviders())
{
- if (provider instanceof ScanningAppProvider scanningAppProvider)
+ if (provider instanceof DefaultProvider scanningAppProvider)
{
_providerCount++;
scanningAppProvider.addScannerListener(new Scanner.ScanCycleListener()
@@ -170,6 +170,9 @@ public void testWebAppsWithAddedEnvConfig(WorkDir workDir) throws Exception
Path testdir = workDir.getEmptyPathDir();
createJettyBase(testdir);
+ Path environments = jetty.getJettyBasePath().resolve("environments");
+ FS.ensureDirExists(environments);
+
Environment.ensure("core").setAttribute("testname", "Initial");
// Setup initial webapp, with XML
@@ -187,7 +190,7 @@ public void testWebAppsWithAddedEnvConfig(WorkDir workDir) throws Exception
assertThat("displayname", contextHandler.getDisplayName(), is("Simple Initial"));
// Add environment configuration
- Path coreProp = jetty.getJettyBasePath().resolve("webapps/core.properties");
+ Path coreProp = environments.resolve("core.properties");
Files.writeString(coreProp, "testname=New EnvConfig");
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/DefaultProviderStartupTest.java
similarity index 64%
rename from jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/ContextProviderStartupTest.java
rename to jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DefaultProviderStartupTest.java
index d864ff87fa55..cd60a063fec3 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/DefaultProviderStartupTest.java
@@ -39,13 +39,12 @@
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;
/**
- * Tests {@link ContextProvider} as it starts up for the first time.
+ * Tests {@link DefaultProvider} as it starts up for the first time.
*/
@ExtendWith(WorkDirExtension.class)
-public class ContextProviderStartupTest extends AbstractCleanEnvironmentTest
+public class DefaultProviderStartupTest extends AbstractCleanEnvironmentTest
{
public WorkDir testdir;
private static XmlConfiguredJetty jetty;
@@ -62,11 +61,11 @@ public void setupEnvironment() throws Exception
Files.writeString(resourceBase.resolve("text.txt"), "This is the resourceBase text");
- jetty.addConfiguration("jetty.xml");
- jetty.addConfiguration("jetty-http.xml");
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty.xml"));
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty-http.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");
+ jetty.addConfiguration(MavenPaths.findTestResourceFile("jetty-core-deploy-custom.xml"));
// Setup initial context
jetty.copyWebapp("bar-core-context.xml", "bar.xml");
@@ -100,10 +99,13 @@ public void testStartupContext() throws Exception
public void testStartupWithRelativeEnvironmentContext() throws Exception
{
Path jettyBase = jetty.getJettyBasePath();
- Path propsFile = Files.writeString(jettyBase.resolve("webapps/core.properties"), Deployable.ENVIRONMENT_XML + " = etc/core-context.xml", StandardOpenOption.CREATE_NEW);
- Path props2File = Files.writeString(jettyBase.resolve("webapps/core-other.properties"), Deployable.ENVIRONMENT_XML + ".other = etc/core-context-other.xml", StandardOpenOption.CREATE_NEW);
- assertTrue(Files.exists(propsFile));
- assertTrue(Files.exists(props2File));
+
+ Path environments = jettyBase.resolve("environments");
+ FS.ensureDirExists(environments);
+
+ Files.writeString(environments.resolve("core.properties"), Deployable.ENVIRONMENT_XML + "=etc/core-context.xml", StandardOpenOption.CREATE_NEW);
+ Files.writeString(environments.resolve("core-other.properties"), Deployable.ENVIRONMENT_XML + ".other=etc/core-context-other.xml", StandardOpenOption.CREATE_NEW);
+
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);
@@ -125,14 +127,17 @@ public void testStartupWithRelativeEnvironmentContext() throws Exception
public void testStartupWithAbsoluteEnvironmentContext() throws Exception
{
Path jettyBase = jetty.getJettyBasePath();
- Path propsFile = Files.writeString(jettyBase.resolve("webapps/core.properties"),
- String.format("%s = %s%n", Deployable.ENVIRONMENT_XML, MavenPaths.findTestResourceFile("etc/core-context.xml")),
+
+ Path environments = jettyBase.resolve("environments");
+ FS.ensureDirExists(environments);
+
+ Files.writeString(environments.resolve("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"),
- String.format("%s = %s%n", (Deployable.ENVIRONMENT_XML + ".other"), MavenPaths.findTestResourceFile("etc/core-context-other.xml")),
+ Files.writeString(environments.resolve("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));
+
jetty.copyWebapp("bar-core-context.properties", "bar.properties");
startJetty();
@@ -151,15 +156,17 @@ public void testStartupWithAbsoluteEnvironmentContext() throws Exception
public void testNonEnvironmentPropertyFileNotApplied() throws Exception
{
Path jettyBase = jetty.getJettyBasePath();
- Path propsFile = Files.writeString(jettyBase.resolve("webapps/non-env.properties"), Deployable.ENVIRONMENT_XML + " = some/file/that/should/be/ignored.txt", StandardOpenOption.CREATE_NEW);
- Path propsEE8File = Files.writeString(jettyBase.resolve("webapps/ee8.properties"), Deployable.ENVIRONMENT_XML + " = some/file/that/should/be/ignored.txt", StandardOpenOption.CREATE_NEW);
- Path propsEE9File = Files.writeString(jettyBase.resolve("webapps/ee9.properties"), Deployable.ENVIRONMENT_XML + " = some/file/that/should/be/ignored.txt", StandardOpenOption.CREATE_NEW);
- Path propsEE10File = Files.writeString(jettyBase.resolve("webapps/ee10.properties"), Deployable.ENVIRONMENT_XML + " = some/file/that/should/be/ignored.txt", StandardOpenOption.CREATE_NEW);
- Path propsNonCoreFile = Files.writeString(jettyBase.resolve("webapps/not-core.properties"), Deployable.ENVIRONMENT_XML + " = some/file/that/should/be/ignored.txt", StandardOpenOption.CREATE_NEW);
- assertTrue(Files.exists(propsFile));
- assertTrue(Files.exists(propsEE8File));
- assertTrue(Files.exists(propsEE9File));
- assertTrue(Files.exists(propsEE10File));
+
+ // Create some property files in ${jetty.base}/environments/ directory.
+ Path environments = jettyBase.resolve("environments");
+ FS.ensureDirExists(environments);
+
+ Files.writeString(environments.resolve("non-env.properties"), Deployable.ENVIRONMENT_XML + "=some/file/that/should/be/ignored.txt");
+ Files.writeString(environments.resolve("ee8.properties"), Deployable.ENVIRONMENT_XML + "=some/file/that/should/be/ignored.txt");
+ Files.writeString(environments.resolve("ee9.properties"), Deployable.ENVIRONMENT_XML + "=some/file/that/should/be/ignored.txt");
+ Files.writeString(environments.resolve("ee10.properties"), Deployable.ENVIRONMENT_XML + "=some/file/that/should/be/ignored.txt");
+ Files.writeString(environments.resolve("not-core.properties"), Deployable.ENVIRONMENT_XML + "=some/file/that/should/be/ignored.txt");
+
jetty.copyWebapp("bar-core-context.properties", "bar.properties");
startJetty();
@@ -176,26 +183,26 @@ public void testNonEnvironmentPropertyFileNotApplied() throws Exception
public void testPropertyOverriding() throws Exception
{
Path jettyBase = jetty.getJettyBasePath();
- Path propsCoreAFile = Files.writeString(jettyBase.resolve("webapps/core-a.properties"), Deployable.ENVIRONMENT_XML + " = etc/a.xml", StandardOpenOption.CREATE_NEW);
- Path propsCoreBFile = Files.writeString(jettyBase.resolve("webapps/core-b.properties"), Deployable.ENVIRONMENT_XML + " = etc/b.xml", StandardOpenOption.CREATE_NEW);
- Path propsCoreCFile = Files.writeString(jettyBase.resolve("webapps/core-c.properties"), Deployable.ENVIRONMENT_XML + " = etc/c.xml", StandardOpenOption.CREATE_NEW);
- Path propsCoreDFile = Files.writeString(jettyBase.resolve("webapps/core-d.properties"), Deployable.ENVIRONMENT_XML + " = etc/d.xml", StandardOpenOption.CREATE_NEW);
- assertTrue(Files.exists(propsCoreAFile));
- assertTrue(Files.exists(propsCoreBFile));
- assertTrue(Files.exists(propsCoreCFile));
- assertTrue(Files.exists(propsCoreDFile));
- Path aPath = jettyBase.resolve("etc/a.xml");
+
+ Path environments = jettyBase.resolve("environments");
+ FS.ensureDirExists(environments);
+
+ Files.writeString(environments.resolve("core-a.properties"), Deployable.ENVIRONMENT_XML + "=etc/a.xml");
+ Files.writeString(environments.resolve("core-b.properties"), Deployable.ENVIRONMENT_XML + "=etc/b.xml");
+ Files.writeString(environments.resolve("core-c.properties"), Deployable.ENVIRONMENT_XML + "=etc/c.xml");
+ Files.writeString(environments.resolve("core-d.properties"), Deployable.ENVIRONMENT_XML + "=etc/d.xml");
+
+ Path etc = jettyBase.resolve("etc");
+ FS.ensureDirExists(etc);
+
+ Path aPath = etc.resolve("a.xml");
writeXmlDisplayName(aPath, "A WebApp");
- Path bPath = jettyBase.resolve("etc/b.xml");
+ Path bPath = etc.resolve("b.xml");
writeXmlDisplayName(bPath, "B WebApp");
- Path cPath = jettyBase.resolve("etc/c.xml");
+ Path cPath = etc.resolve("c.xml");
writeXmlDisplayName(cPath, "C WebApp");
- Path dPath = jettyBase.resolve("etc/d.xml");
+ Path dPath = etc.resolve("d.xml");
writeXmlDisplayName(dPath, "D WebApp");
- assertTrue(Files.exists(propsCoreAFile));
- assertTrue(Files.exists(propsCoreBFile));
- assertTrue(Files.exists(propsCoreCFile));
- assertTrue(Files.exists(propsCoreDFile));
jetty.copyWebapp("bar-core-context.properties", "bar.properties");
startJetty();
@@ -208,17 +215,28 @@ public void testPropertyOverriding() throws Exception
/**
* Test that properties defined in an environment-specific properties file
* are used for substitution.
- *
- * @throws Exception
*/
@Test
public void testPropertySubstitution() throws Exception
{
Path jettyBase = jetty.getJettyBasePath();
- 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);
+
+ Path environments = jettyBase.resolve("environments");
+ FS.ensureDirExists(environments);
+
+ Files.writeString(environments.resolve("core.properties"),
+ """
+ jetty.deploy.environmentXml=etc/core-context-sub.xml
+ test.displayName=DisplayName Set By Property
+ """);
+
+ 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();
+
ContextHandler context = jetty.getContextHandler("/bar");
assertNotNull(context);
assertEquals("DisplayName Set By Property", context.getDisplayName());
diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DefaultProviderTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DefaultProviderTest.java
new file mode 100644
index 000000000000..a5bd9ffc03c9
--- /dev/null
+++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DefaultProviderTest.java
@@ -0,0 +1,371 @@
+//
+// ========================================================================
+// 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.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.eclipse.jetty.deploy.AbstractCleanEnvironmentTest;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
+import org.eclipse.jetty.util.Scanner;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+
+@ExtendWith(WorkDirExtension.class)
+public class DefaultProviderTest extends AbstractCleanEnvironmentTest
+{
+ public WorkDir workDir;
+
+ public static class AssertActionListDefaultProvider extends DefaultProvider
+ {
+ Consumer> assertActionList;
+
+ @Override
+ protected void performActions(List actions)
+ {
+ assertActionList.accept(actions);
+
+ // Perform post performActions cleanup that normally happens
+ for (DeployAction action : actions)
+ {
+ action.getApp().resetStates();
+ }
+ }
+ }
+
+ @Test
+ public void testActionListNewXmlOnly() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+ Path xml = dir.resolve("bar.xml");
+ Files.writeString(xml, "XML for bar", UTF_8);
+
+ AssertActionListDefaultProvider defaultProvider = new AssertActionListDefaultProvider();
+ defaultProvider.addMonitoredDirectory(dir);
+
+ Map changeSet = new HashMap<>();
+ changeSet.put(xml, Scanner.Notification.ADDED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("actions.size", actions.size(), is(1));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.ADD));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.ADDED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), Matchers.contains(xml));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(xml));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+ }
+
+ @Test
+ public void testActionListXmlThenRemoved() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+ Path xml = dir.resolve("foo.xml");
+ Files.writeString(xml, "XML for foo", UTF_8);
+
+ AssertActionListDefaultProvider defaultProvider = new AssertActionListDefaultProvider();
+ defaultProvider.addMonitoredDirectory(dir);
+
+ // Initial deployment.
+ Map changeSet = new HashMap<>();
+ changeSet.put(xml, Scanner.Notification.ADDED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("actions.size", actions.size(), is(1));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("foo"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.ADD));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.ADDED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), contains(xml));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.ADDED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(xml));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+
+ // Removed only deployment file.
+ Files.deleteIfExists(xml);
+ changeSet.clear();
+ changeSet.put(xml, Scanner.Notification.REMOVED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("changedUnits.size", actions.size(), is(1));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("foo"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.REMOVED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), contains(xml));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.REMOVED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(nullValue()));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+ }
+
+ @Test
+ public void testActionListNewXmlAndWarOnly() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+ Path xml = dir.resolve("bar.xml");
+ Files.writeString(xml, "XML for bar", UTF_8);
+ Path war = dir.resolve("bar.war");
+ Files.writeString(war, "WAR for bar", UTF_8);
+
+ AssertActionListDefaultProvider defaultProvider = new AssertActionListDefaultProvider();
+ defaultProvider.addMonitoredDirectory(dir);
+
+ // Initial deployment
+ Map changeSet = new HashMap<>();
+ changeSet.put(xml, Scanner.Notification.ADDED);
+ changeSet.put(war, Scanner.Notification.ADDED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("actions.size", actions.size(), is(1));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.ADD));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.ADDED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(xml, war));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.ADDED));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.ADDED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(xml));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+ }
+
+ @Test
+ public void testActionListXmlAndWarWithXmlUpdate() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+ Path xml = dir.resolve("bar.xml");
+ Files.writeString(xml, "XML for bar", UTF_8);
+ Path war = dir.resolve("bar.war");
+ Files.writeString(war, "WAR for bar", UTF_8);
+
+ AssertActionListDefaultProvider defaultProvider = new AssertActionListDefaultProvider();
+ defaultProvider.addMonitoredDirectory(dir);
+
+ // Initial deployment
+ Map changeSet = new HashMap<>();
+ changeSet.put(xml, Scanner.Notification.ADDED);
+ changeSet.put(war, Scanner.Notification.ADDED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("actions.size", actions.size(), is(1));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.ADD));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.ADDED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(xml, war));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.ADDED));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.ADDED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(xml));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+
+ // Change/Touch war
+ changeSet = new HashMap<>();
+ changeSet.put(war, Scanner.Notification.CHANGED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("actions.size", actions.size(), is(2));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(xml, war));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.UNCHANGED));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(xml));
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.ADD));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(xml, war));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.UNCHANGED));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(xml));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+ }
+
+ @Test
+ public void testActionListXmlAndWarWithXmlRemoved() throws IOException
+ {
+ Path dir = workDir.getEmptyPathDir();
+ Path xml = dir.resolve("bar.xml");
+ Files.writeString(xml, "XML for bar", UTF_8);
+ Path war = dir.resolve("bar.war");
+ Files.writeString(war, "WAR for bar", UTF_8);
+
+ AssertActionListDefaultProvider defaultProvider = new AssertActionListDefaultProvider();
+ defaultProvider.addMonitoredDirectory(dir);
+
+ // Initial deployment
+ Map changeSet = new HashMap<>();
+ changeSet.put(xml, Scanner.Notification.ADDED);
+ changeSet.put(war, Scanner.Notification.ADDED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("actions.size", actions.size(), is(1));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.ADD));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.ADDED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(xml, war));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.ADDED));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.ADDED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(xml));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+
+ // Change/Touch war and xml
+ changeSet = new HashMap<>();
+ changeSet.put(war, Scanner.Notification.CHANGED);
+ changeSet.put(xml, Scanner.Notification.CHANGED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("actions.size", actions.size(), is(2));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(xml, war));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(xml));
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.ADD));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(xml, war));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(xml));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+
+ // Delete XML (now only war exists)
+ Files.deleteIfExists(xml);
+ changeSet = new HashMap<>();
+ changeSet.put(xml, Scanner.Notification.REMOVED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("actions.size", actions.size(), is(2));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(xml, war));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.REMOVED));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.UNCHANGED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(war));
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.ADD));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.CHANGED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(xml, war));
+ assertThat("action.app.paths[xml].state", action.getApp().getPaths().get(xml), is(DefaultApp.State.REMOVED));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.UNCHANGED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(war));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+
+ // Delete WAR
+ Files.deleteIfExists(war);
+ changeSet = new HashMap<>();
+ changeSet.put(war, Scanner.Notification.REMOVED);
+
+ defaultProvider.assertActionList = (actions) ->
+ {
+ assertThat("actions.size", actions.size(), is(1));
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ action = iterator.next();
+ assertThat("action.name", action.getName(), is("bar"));
+ assertThat("action.type", action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat("action.app.state", action.getApp().getState(), is(DefaultApp.State.REMOVED));
+ assertThat("action.app.paths", action.getApp().getPaths().keySet(), containsInAnyOrder(war));
+ assertThat("action.app.paths[war].state", action.getApp().getPaths().get(war), is(DefaultApp.State.REMOVED));
+ assertThat("action.app.mainPath", action.getApp().getMainPath(), is(nullValue()));
+ };
+
+ defaultProvider.pathsChanged(changeSet);
+ }
+}
diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DeployActionComparatorTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DeployActionComparatorTest.java
new file mode 100644
index 000000000000..e859e5a1c2a1
--- /dev/null
+++ b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/DeployActionComparatorTest.java
@@ -0,0 +1,196 @@
+//
+// ========================================================================
+// 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.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class DeployActionComparatorTest
+{
+ @Test
+ public void testAddOnly()
+ {
+ DefaultApp appFoo = new DefaultApp("foo");
+ appFoo.putPath(Path.of("foo.xml"), DefaultApp.State.ADDED);
+ DefaultApp appBar = new DefaultApp("bar");
+ appBar.putPath(Path.of("bar.xml"), DefaultApp.State.ADDED);
+
+ List actions = new ArrayList<>();
+ actions.add(new DeployAction(DeployAction.Type.ADD, new DefaultApp("bar")));
+ actions.add(new DeployAction(DeployAction.Type.ADD, new DefaultApp("foo")));
+
+ actions.sort(new DeployActionComparator());
+
+ // Verify order
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ // expected in ascending basename order
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.ADD));
+ assertThat(action.getApp().getName(), is("bar"));
+
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.ADD));
+ assertThat(action.getApp().getName(), is("foo"));
+ }
+
+ @Test
+ public void testRemoveOnly()
+ {
+ DefaultApp appFoo = new DefaultApp("foo");
+ appFoo.putPath(Path.of("foo.xml"), DefaultApp.State.REMOVED);
+
+ DefaultApp appBar = new DefaultApp("bar");
+ appBar.putPath(Path.of("bar.xml"), DefaultApp.State.REMOVED);
+
+ List actions = new ArrayList<>();
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, appFoo));
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, appBar));
+
+ actions.sort(new DeployActionComparator());
+
+ // Verify order
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ // expected in descending basename order
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat(action.getApp().getName(), is("foo"));
+
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat(action.getApp().getName(), is("bar"));
+ }
+
+ @Test
+ public void testRemoveTwoAndAddTwo()
+ {
+ DefaultApp appFoo = new DefaultApp("foo");
+ appFoo.putPath(Path.of("foo.xml"), DefaultApp.State.REMOVED);
+
+ DefaultApp appBar = new DefaultApp("bar");
+ appBar.putPath(Path.of("bar.xml"), DefaultApp.State.REMOVED);
+
+ List actions = new ArrayList<>();
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, appFoo));
+ actions.add(new DeployAction(DeployAction.Type.ADD, appFoo));
+ actions.add(new DeployAction(DeployAction.Type.ADD, appBar));
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, appBar));
+
+ // Perform sort
+ actions.sort(new DeployActionComparator());
+
+ // Verify order
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ // expecting REMOVE first
+
+ // REMOVE is in descending basename order
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat(action.getApp().getName(), is("foo"));
+
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat(action.getApp().getName(), is("bar"));
+
+ // expecting ADD next
+
+ // ADD is in ascending basename order
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.ADD));
+ assertThat(action.getApp().getName(), is("bar"));
+
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.ADD));
+ assertThat(action.getApp().getName(), is("foo"));
+ }
+
+ @Test
+ public void testRemoveFourAndAddTwo()
+ {
+ DefaultApp appA = new DefaultApp("app-a");
+ appA.putPath(Path.of("app-a.xml"), DefaultApp.State.REMOVED);
+
+ DefaultApp appB = new DefaultApp("app-b");
+ appB.putPath(Path.of("app-b.xml"), DefaultApp.State.REMOVED);
+
+ DefaultApp appC = new DefaultApp("app-c");
+ appC.putPath(Path.of("app-c.xml"), DefaultApp.State.REMOVED);
+
+ DefaultApp appD = new DefaultApp("app-d");
+ appD.putPath(Path.of("app-d.xml"), DefaultApp.State.REMOVED);
+
+ List actions = new ArrayList<>();
+ // app A is going through hot-reload
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, appA));
+ actions.add(new DeployAction(DeployAction.Type.ADD, appA));
+ // app B is being removed
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, appB));
+ // app C is being removed
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, appC));
+ // app D is going through hot-reload
+ actions.add(new DeployAction(DeployAction.Type.ADD, appD));
+ actions.add(new DeployAction(DeployAction.Type.REMOVE, appD));
+
+ assertThat(actions.size(), is(6));
+
+ // Perform sort
+ actions.sort(new DeployActionComparator());
+
+ // Verify order
+ Iterator iterator = actions.iterator();
+ DeployAction action;
+
+ // expecting REMOVE first
+
+ // REMOVE is in descending basename order
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat(action.getApp().getName(), is("app-d"));
+
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat(action.getApp().getName(), is("app-c"));
+
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat(action.getApp().getName(), is("app-b"));
+
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.REMOVE));
+ assertThat(action.getApp().getName(), is("app-a"));
+
+ // expecting ADD next
+
+ // ADD is in ascending basename order
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.ADD));
+ assertThat(action.getApp().getName(), is("app-a"));
+
+ action = iterator.next();
+ assertThat(action.getType(), is(DeployAction.Type.ADD));
+ assertThat(action.getApp().getName(), is("app-d"));
+ }
+}
diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/UnitTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/UnitTest.java
deleted file mode 100644
index 587c19ed9367..000000000000
--- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/UnitTest.java
+++ /dev/null
@@ -1,131 +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.nio.file.Path;
-
-import org.junit.jupiter.api.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-public class UnitTest
-{
- @Test
- public void testUnitStateUnchanged()
- {
- Unit unit = new Unit("test");
- unit.putPath(Path.of("test-a"), Unit.State.UNCHANGED);
- unit.putPath(Path.of("test-b"), Unit.State.UNCHANGED);
-
- assertThat(unit.getState(), is(Unit.State.UNCHANGED));
- }
-
- @Test
- public void testUnitStateInitialEmpty()
- {
- Unit unit = new Unit("test");
- // intentionally empty of Paths
-
- assertThat(unit.getState(), is(Unit.State.REMOVED));
- }
-
- @Test
- public void testUnitStatePutThenRemoveAll()
- {
- Unit unit = new Unit("test");
- unit.putPath(Path.of("test-a"), Unit.State.ADDED);
- assertThat(unit.getState(), is(Unit.State.ADDED));
-
- // Now it gets flagged as removed. (eg: by a Scanner change)
- unit.putPath(Path.of("test-a"), Unit.State.REMOVED);
- // Then it gets processed, which results in a state reset.
- unit.resetStates();
-
- // The resulting Unit should have no paths, and be flagged as removed.
- assertThat(unit.getPaths().size(), is(0));
- assertThat(unit.getState(), is(Unit.State.REMOVED));
- }
-
- @Test
- public void testUnitStateAddedOnly()
- {
- Unit unit = new Unit("test");
- unit.putPath(Path.of("test-a"), Unit.State.ADDED);
- unit.putPath(Path.of("test-b"), Unit.State.ADDED);
-
- assertThat(unit.getState(), is(Unit.State.ADDED));
- }
-
- @Test
- public void testUnitStateAddedRemoved()
- {
- Unit unit = new Unit("test");
- unit.putPath(Path.of("test-a"), Unit.State.REMOVED); // existing file removed in this scan
- unit.putPath(Path.of("test-b"), Unit.State.ADDED); // new file introduced in this scan event
-
- assertThat(unit.getState(), is(Unit.State.CHANGED));
- }
-
- @Test
- public void testUnitStateAddedChanged()
- {
- Unit unit = new Unit("test");
- unit.putPath(Path.of("test-a"), Unit.State.CHANGED); // existing file changed in this scan
- unit.putPath(Path.of("test-b"), Unit.State.ADDED); // new file introduced in this scan event
-
- assertThat(unit.getState(), is(Unit.State.CHANGED));
- }
-
- @Test
- public void testUnitStateUnchangedAdded()
- {
- Unit unit = new Unit("test");
- unit.putPath(Path.of("test-a"), Unit.State.UNCHANGED); // existed in previous scan
- unit.putPath(Path.of("test-b"), Unit.State.ADDED); // new file introduced in this scan event
-
- assertThat(unit.getState(), is(Unit.State.CHANGED));
- }
-
- @Test
- public void testUnitStateUnchangedChanged()
- {
- Unit unit = new Unit("test");
- unit.putPath(Path.of("test-a"), Unit.State.UNCHANGED); // existed in previous scan
- unit.putPath(Path.of("test-b"), Unit.State.CHANGED); // existing file changed in this scan event
-
- assertThat(unit.getState(), is(Unit.State.CHANGED));
- }
-
- @Test
- public void testUnitStateUnchangedRemoved()
- {
- Unit unit = new Unit("test");
- unit.putPath(Path.of("test-a"), Unit.State.UNCHANGED); // existed in previous scan
- unit.putPath(Path.of("test-b"), Unit.State.REMOVED); // existing file removed in this scan event
-
- assertThat(unit.getState(), is(Unit.State.CHANGED));
- }
-
- @Test
- public void testUnitStateUnchangedRemovedAdded()
- {
- Unit unit = new Unit("test");
- unit.putPath(Path.of("test-a"), Unit.State.UNCHANGED); // existed in previous scan
- unit.putPath(Path.of("test-b"), Unit.State.REMOVED); // existing file changed in this scan event
- unit.putPath(Path.of("test-c"), Unit.State.ADDED); // new file introduced in this scan event
-
- assertThat(unit.getState(), is(Unit.State.CHANGED));
- }
-}
diff --git a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/internal/DeploymentUnitsTest.java b/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/internal/DeploymentUnitsTest.java
deleted file mode 100644
index f7792ab139f1..000000000000
--- a/jetty-core/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/internal/DeploymentUnitsTest.java
+++ /dev/null
@@ -1,255 +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.internal;
-
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.eclipse.jetty.deploy.providers.Unit;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
-import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
-import org.eclipse.jetty.util.Scanner;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.is;
-
-@ExtendWith(WorkDirExtension.class)
-public class DeploymentUnitsTest
-{
- public WorkDir workDir;
-
- @Test
- public void testNewXmlOnly() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path xml = dir.resolve("bar.xml");
- Files.writeString(xml, "XML for bar", UTF_8);
-
- DeploymentUnits units = new DeploymentUnits();
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("bar"));
- assertThat("changedUnit[bar].state", unit.getState(), is(Unit.State.ADDED));
- assertThat("changedUnit[bar].paths", unit.getPaths().keySet(), contains(xml));
- });
- Map changeSet = new HashMap<>();
- changeSet.put(xml, Scanner.Notification.ADDED);
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
- }
-
- @Test
- public void testXmlThenRemoved() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path xml = dir.resolve("foo.xml");
- Files.writeString(xml, "XML for foo", UTF_8);
-
- DeploymentUnits units = new DeploymentUnits();
-
- // Initial deployment.
- Map changeSet = new HashMap<>();
- changeSet.put(xml, Scanner.Notification.ADDED);
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("foo"));
- assertThat("changedUnit[foo].state", unit.getState(), is(Unit.State.ADDED));
- assertThat("changedUnit[foo].paths", unit.getPaths().keySet(), contains(xml));
- assertThat("changedUnit[foo].paths[xml].state", unit.getPaths().get(xml), is(Unit.State.ADDED));
- });
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
-
- // Removed deployment.
- Files.deleteIfExists(xml);
- changeSet.clear();
- changeSet.put(xml, Scanner.Notification.REMOVED);
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("foo"));
- assertThat("changedUnit[foo].state", unit.getState(), is(Unit.State.REMOVED));
- assertThat("changedUnit[foo].paths", unit.getPaths().keySet(), contains(xml));
- assertThat("changedUnit[foo].paths[xml].state", unit.getPaths().get(xml), is(Unit.State.REMOVED));
- });
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
- }
-
- @Test
- public void testNewXmlAndWarOnly() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path xml = dir.resolve("bar.xml");
- Files.writeString(xml, "XML for bar", UTF_8);
- Path war = dir.resolve("bar.war");
- Files.writeString(war, "WAR for bar", UTF_8);
-
- DeploymentUnits units = new DeploymentUnits();
-
- // Initial deployment
- Map changeSet = new HashMap<>();
- changeSet.put(xml, Scanner.Notification.ADDED);
- changeSet.put(war, Scanner.Notification.ADDED);
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("bar"));
- assertThat("changedUnit[bar].state", unit.getState(), is(Unit.State.ADDED));
- assertThat("changedUnit[bar].paths", unit.getPaths().keySet(), containsInAnyOrder(xml, war));
- assertThat("changedUnit[bar].paths[xml].state", unit.getPaths().get(xml), is(Unit.State.ADDED));
- assertThat("changedUnit[bar].paths[war].state", unit.getPaths().get(war), is(Unit.State.ADDED));
- });
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
- }
-
- @Test
- public void testXmlAndWarWithXmlUpdate() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path xml = dir.resolve("bar.xml");
- Files.writeString(xml, "XML for bar", UTF_8);
- Path war = dir.resolve("bar.war");
- Files.writeString(war, "WAR for bar", UTF_8);
-
- DeploymentUnits units = new DeploymentUnits();
-
- // Initial deployment
- Map changeSet = new HashMap<>();
- changeSet.put(xml, Scanner.Notification.ADDED);
- changeSet.put(war, Scanner.Notification.ADDED);
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("bar"));
- assertThat("changedUnit[bar].state", unit.getState(), is(Unit.State.ADDED));
- assertThat("changedUnit[bar].paths", unit.getPaths().keySet(), containsInAnyOrder(xml, war));
- assertThat("changedUnit[bar].paths[xml].state", unit.getPaths().get(xml), is(Unit.State.ADDED));
- assertThat("changedUnit[bar].paths[war].state", unit.getPaths().get(war), is(Unit.State.ADDED));
- });
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
-
- // Change/Touch war
- changeSet = new HashMap<>();
- changeSet.put(war, Scanner.Notification.CHANGED);
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("bar"));
- assertThat("changedUnit[bar].state", unit.getState(), is(Unit.State.CHANGED));
- assertThat("changedUnit[bar].paths", unit.getPaths().keySet(), containsInAnyOrder(xml, war));
- assertThat("changedUnit[bar].paths[xml].state", unit.getPaths().get(xml), is(Unit.State.UNCHANGED));
- assertThat("changedUnit[bar].paths[war].state", unit.getPaths().get(war), is(Unit.State.CHANGED));
- });
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
- }
-
- @Test
- public void testXmlAndWarWithXmlRemoved() throws IOException
- {
- Path dir = workDir.getEmptyPathDir();
- Path xml = dir.resolve("bar.xml");
- Files.writeString(xml, "XML for bar", UTF_8);
- Path war = dir.resolve("bar.war");
- Files.writeString(war, "WAR for bar", UTF_8);
-
- DeploymentUnits units = new DeploymentUnits();
-
- // Initial deployment
- Map changeSet = new HashMap<>();
- changeSet.put(xml, Scanner.Notification.ADDED);
- changeSet.put(war, Scanner.Notification.ADDED);
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("bar"));
- assertThat("changedUnit[bar].state", unit.getState(), is(Unit.State.ADDED));
- assertThat("changedUnit[bar].paths", unit.getPaths().keySet(), containsInAnyOrder(xml, war));
- assertThat("changedUnit[bar].paths[xml].state", unit.getPaths().get(xml), is(Unit.State.ADDED));
- assertThat("changedUnit[bar].paths[war].state", unit.getPaths().get(war), is(Unit.State.ADDED));
- });
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
-
- // Change/Touch war and xml
- changeSet = new HashMap<>();
- changeSet.put(war, Scanner.Notification.CHANGED);
- changeSet.put(xml, Scanner.Notification.CHANGED);
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("bar"));
- assertThat("changedUnit[bar].state", unit.getState(), is(Unit.State.CHANGED));
- assertThat("changedUnit[bar].paths", unit.getPaths().keySet(), containsInAnyOrder(xml, war));
- assertThat("changedUnit[bar].paths[xml].state", unit.getPaths().get(xml), is(Unit.State.CHANGED));
- assertThat("changedUnit[bar].paths[war].state", unit.getPaths().get(war), is(Unit.State.CHANGED));
- });
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
-
- // Delete XML
- Files.deleteIfExists(xml);
- changeSet = new HashMap<>();
- changeSet.put(xml, Scanner.Notification.REMOVED);
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("bar"));
- assertThat("changedUnit[bar].state", unit.getState(), is(Unit.State.CHANGED));
- assertThat("changedUnit[bar].paths", unit.getPaths().keySet(), containsInAnyOrder(xml, war));
- assertThat("changedUnit[bar].paths[xml].state", unit.getPaths().get(xml), is(Unit.State.REMOVED));
- assertThat("changedUnit[bar].paths[war].state", unit.getPaths().get(war), is(Unit.State.UNCHANGED));
- });
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
-
- // Delete WAR
- Files.deleteIfExists(war);
- changeSet = new HashMap<>();
- changeSet.put(war, Scanner.Notification.REMOVED);
- units.setListener(changedUnits ->
- {
- assertThat("changedUnits.size", changedUnits.size(), is(1));
- Unit unit = changedUnits.get(0);
- assertThat("changedUnit[0].basename", unit.getBaseName(), is("bar"));
- assertThat("changedUnit[bar].state", unit.getState(), is(Unit.State.REMOVED));
- assertThat("changedUnit[bar].paths", unit.getPaths().keySet(), containsInAnyOrder(war));
- assertThat("changedUnit[bar].paths[war].state", unit.getPaths().get(war), is(Unit.State.REMOVED));
- });
- units.pathsChanged(changeSet);
- units.getUnits().forEach(Unit::resetStates);
- }
-}
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 9822961c6657..601eb57d63a1 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
@@ -17,7 +17,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
-import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
@@ -141,11 +140,6 @@ public void addConfiguration(Path xmlConfigFile)
addConfiguration(ResourceFactory.root().newResource(xmlConfigFile));
}
- public void addConfiguration(String testConfigName) throws MalformedURLException
- {
- addConfiguration(MavenPaths.findTestResourceFile(testConfigName));
- }
-
public void addConfiguration(Resource xmlConfig)
{
_xmlConfigurations.add(xmlConfig);
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
index 748ab6ecbda8..d07554671715 100644
--- 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
@@ -1,21 +1,18 @@
-
+
+
+ core
+
core
-
-
- core
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/jetty-core/jetty-deploy/src/test/resources/webapps/bar-core-context.properties b/jetty-core/jetty-deploy/src/test/resources/webapps/bar-core-context.properties
index 2ad513333744..ab4fbf31c1bf 100644
--- a/jetty-core/jetty-deploy/src/test/resources/webapps/bar-core-context.properties
+++ b/jetty-core/jetty-deploy/src/test/resources/webapps/bar-core-context.properties
@@ -1,2 +1,2 @@
-environment: core
-jetty.deploy.contextHandlerClass: org.eclipse.jetty.deploy.BarContextHandler
\ No newline at end of file
+environment=core
+jetty.deploy.contextHandlerClass=org.eclipse.jetty.deploy.BarContextHandler
\ No newline at end of file
diff --git a/jetty-core/jetty-deploy/src/main/config/etc/jetty-core-deploy.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-core-deploy.xml
similarity index 77%
rename from jetty-core/jetty-deploy/src/main/config/etc/jetty-core-deploy.xml
rename to jetty-core/jetty-server/src/main/config/etc/jetty-core-deploy.xml
index 7adf72376086..1c0886e12019 100644
--- a/jetty-core/jetty-deploy/src/main/config/etc/jetty-core-deploy.xml
+++ b/jetty-core/jetty-server/src/main/config/etc/jetty-core-deploy.xml
@@ -3,11 +3,11 @@
-
+
core
-
+
diff --git a/jetty-core/jetty-deploy/src/main/config/modules/core-deploy.mod b/jetty-core/jetty-server/src/main/config/modules/core-deploy.mod
similarity index 69%
rename from jetty-core/jetty-deploy/src/main/config/modules/core-deploy.mod
rename to jetty-core/jetty-server/src/main/config/modules/core-deploy.mod
index bf893a6f3cf8..6e02d6341f04 100644
--- a/jetty-core/jetty-deploy/src/main/config/modules/core-deploy.mod
+++ b/jetty-core/jetty-server/src/main/config/modules/core-deploy.mod
@@ -7,14 +7,9 @@ core
[depend]
deploy
-[lib]
-
-[files]
-webapps/
-
[xml]
etc/jetty-core-deploy.xml
[ini-template]
## Default ContextHandler class for "core" environment deployments
-# contextHandlerClass=org.eclipse.jetty.server.handler.ResourceHandler$ResourceContext
+# contextHandlerClass=org.eclipse.jetty.server.handler.CoreWebAppContext
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/CoreWebAppContext.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/CoreWebAppContext.java
new file mode 100644
index 000000000000..e7380004715c
--- /dev/null
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/CoreWebAppContext.java
@@ -0,0 +1,214 @@
+//
+// ========================================================================
+// 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.server.handler;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jetty.util.FileID;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+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.util.resource.Resources;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A classloader isolated Core WebApp.
+ *
+ *
+ * The Base Resource represents the metadata base that defines this {@code CoreWebAppContext}.
+ *
+ *
+ * The metadata base can be a directory on disk, or a non-traditional {@code war} file with the following contents.
+ *
+ *
+ * - {@code /lib/*.jar} - the jar files for the classloader of this webapp
+ * - {@code /classes/} - the raw class files for this webapp
+ * - {@code /static/} - the static content to serve for this webapp
+ *
+ *
+ * Note: if using the non-traditional {@code war} as your metadata base, the
+ * existence of {@code /lib/*.jar} files means the archive will be
+ * unpacked into the temp directory defined by this core webapp.
+ *
+ */
+public class CoreWebAppContext extends ContextHandler
+{
+ private static final Logger LOG = LoggerFactory.getLogger(CoreWebAppContext.class);
+ private static final String ORIGINAL_BASE_RESOURCE = "org.eclipse.jetty.webapp.originalBaseResource";
+ private List _extraClasspath;
+ private boolean _builtClassLoader = false;
+
+ public CoreWebAppContext()
+ {
+ this("/");
+ }
+
+ public CoreWebAppContext(String contextPath)
+ {
+ super();
+ setContextPath(contextPath);
+ }
+
+ /**
+ * @return List of Resources that each will represent a new classpath entry
+ */
+ @ManagedAttribute(value = "extra classpath for context classloader", readonly = true)
+ public List getExtraClasspath()
+ {
+ return _extraClasspath == null ? Collections.emptyList() : _extraClasspath;
+ }
+
+ /**
+ * Set the Extra ClassPath via delimited String.
+ *
+ * This is a convenience method for {@link #setExtraClasspath(List)}
+ *
+ *
+ * @param extraClasspath Comma or semicolon separated path of filenames or URLs
+ * pointing to directories or jar files. Directories should end
+ * with '/'.
+ * @see #setExtraClasspath(List)
+ */
+ public void setExtraClasspath(String extraClasspath)
+ {
+ setExtraClasspath(getResourceFactory().split(extraClasspath));
+ }
+
+ public void setExtraClasspath(List extraClasspath)
+ {
+ _extraClasspath = extraClasspath;
+ }
+
+ public ResourceFactory getResourceFactory()
+ {
+ return ResourceFactory.of(this);
+ }
+
+ protected Resource unpack(Resource dir) throws IOException
+ {
+ Path tempDir = getTempDirectory().toPath();
+ dir.copyTo(tempDir);
+ return ResourceFactory.of(this).newResource(tempDir);
+ }
+
+ protected ClassLoader newClassLoader(Resource base) throws IOException
+ {
+ List urls = new ArrayList<>();
+
+ if (Resources.isDirectory(base))
+ {
+ Resource libDir = base.resolve("lib");
+ if (Resources.isDirectory(libDir))
+ {
+ for (Resource entry : libDir.list())
+ {
+ URI uri = entry.getURI();
+ if (FileID.isJavaArchive(uri))
+ urls.add(uri.toURL());
+ }
+ }
+
+ Resource classesDir = base.resolve("classes");
+ if (Resources.isDirectory(classesDir))
+ {
+ urls.add(classesDir.getURI().toURL());
+ }
+ }
+
+ List extraEntries = getExtraClasspath();
+ if (extraEntries != null && !extraEntries.isEmpty())
+ {
+ for (Resource entry : extraEntries)
+ {
+ urls.add(entry.getURI().toURL());
+ }
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Core classloader for {}", urls);
+
+ if (urls.isEmpty())
+ return Environment.CORE.getClassLoader();
+
+ return new URLClassLoader(urls.toArray(URL[]::new), Environment.CORE.getClassLoader());
+ }
+
+ protected void initWebApp() throws IOException
+ {
+ if (getBaseResource() == null)
+ {
+ // Nothing to do.
+ return;
+ }
+
+ // needs to be a directory (either raw, or archive mounted)
+ if (!Resources.isDirectory(getBaseResource()))
+ {
+ LOG.warn("Invalid Metadata Base Resource (not a directory): {}", getBaseResource());
+ return;
+ }
+
+ Resource dir = getBaseResource();
+ // attempt to figure out if the resource is compressed or not
+ if (!dir.getURI().getScheme().equals("file"))
+ {
+ setAttribute(ORIGINAL_BASE_RESOURCE, dir.getURI());
+ dir = unpack(dir);
+ setBaseResource(dir);
+ }
+
+ Resource staticDir = getBaseResource().resolve("static");
+ if (Resources.isDirectory(staticDir))
+ {
+ ResourceHandler resourceHandler = new ResourceHandler();
+ resourceHandler.setBaseResource(staticDir);
+ setHandler(resourceHandler);
+ }
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ initWebApp();
+
+ if (getClassLoader() == null)
+ {
+ _builtClassLoader = true;
+ // Build Classloader (since once wasn't created)
+ setClassLoader(newClassLoader(getBaseResource()));
+ }
+
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ if (_builtClassLoader)
+ {
+ setClassLoader(null);
+ }
+
+ super.doStop();
+ }
+}
diff --git a/jetty-core/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-core/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
index 9c45a486319d..7494f94621a0 100644
--- a/jetty-core/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
+++ b/jetty-core/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
@@ -435,11 +435,27 @@ public void initializeDefaults(Object object)
* @return String to resolved and normalized path, or null if dir or destPath is empty.
*/
public static String resolvePath(String dir, String destPath)
+ {
+ Path resolved = resolvedPath(dir, destPath);
+ if (resolved == null)
+ return null;
+ return resolved.toString();
+ }
+
+ /**
+ * Utility method to resolve a provided path against a directory.
+ *
+ * @param dir the directory (should be a directory reference, does not have to exist)
+ * @param destPath the destination path (can be relative or absolute, syntax depends on OS + FileSystem in use,
+ * and does not need to exist)
+ * @return String to resolved and normalized path, or null if dir or destPath is empty.
+ */
+ public static Path resolvedPath(String dir, String destPath)
{
if (StringUtil.isEmpty(dir) || StringUtil.isEmpty(destPath))
return null;
- return Paths.get(dir).resolve(destPath).normalize().toString();
+ return Paths.get(dir).resolve(destPath).normalize();
}
private static class JettyXmlConfiguration implements ConfigurationProcessor
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 19c0107e843a..26854ef2df23 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,7 +3,7 @@
-
+
ee10
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 117abad9dd0c..b80283ce7a34 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,7 +3,7 @@
-
+
ee11
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 f1b3b32fa3cd..03a047702c7d 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,7 +3,7 @@
-
+
ee8
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 0de7b4693173..3ab52a5c8977 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,7 +3,7 @@
-
+
ee9
From fbedd51b595a6e4420b3b6b53ca0f5ddf64c5dd0 Mon Sep 17 00:00:00 2001
From: Joakim Erdfelt
Date: Thu, 23 Jan 2025 11:01:33 -0600
Subject: [PATCH 039/104] Fixing compilation on OSGI modules
---
.../java/org/eclipse/jetty/deploy/App.java | 125 ++----------------
.../jetty/osgi/AbstractContextProvider.java | 5 -
.../jetty/osgi/BundleContextProvider.java | 28 ++--
.../jetty/osgi/BundleWebAppProvider.java | 9 +-
.../java/org/eclipse/jetty/osgi/OSGiApp.java | 50 ++++---
.../org/eclipse/jetty/osgi/OSGiDeployer.java | 28 ++--
.../eclipse/jetty/osgi/OSGiUndeployer.java | 18 ++-
.../jetty/ee10/osgi/boot/EE10Activator.java | 40 +++---
.../jetty/ee10/test/DeploymentErrorTest.java | 23 ++--
.../jetty/ee11/osgi/boot/EE11Activator.java | 40 +++---
.../jetty/ee11/test/DeploymentErrorTest.java | 25 ++--
.../jetty/ee9/osgi/boot/EE9Activator.java | 40 +++---
.../jetty/ee9/test/DeploymentErrorTest.java | 25 ++--
13 files changed, 197 insertions(+), 259 deletions(-)
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java
index 7a7db0c75ff5..3b39e77c679a 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java
@@ -20,126 +20,17 @@
*/
public interface App
{
+ /**
+ * The application name.
+ *
+ * @return the application name.
+ */
String getName();
/**
* Get the active ContextHandler for this App.
- * The App is not responsible for creating the ContextHandler, the AppProvider is.
+ *
+ * @return the ContextHandler for the App. null means the ContextHandler hasn't been created yet.
*/
- // TODO: make not default
- default ContextHandler getContextHandler()
- {
- return null;
- }
-
-// private final DeploymentManager _manager;
-// private final AppProvider _provider;
-// private final Path _path;
-// private final Map _properties = new HashMap<>();
-// private ContextHandler _context;
-//
-// /**
-// * Create an App with specified Origin ID and archivePath
-// *
-// * Any properties file that exists with the same {@link FileID#getBasename(Path)} as the
-// * filename passed will be used to initialize the properties returned by {@link #getProperties()}.
-// *
-// * @param manager the deployment manager
-// * @param provider the app provider
-// * @param path the path to the application directory, war file or XML descriptor
-// * @see App#getContextPath()
-// */
-// public App(DeploymentManager manager, AppProvider provider, Path path)
-// {
-// _manager = manager;
-// _provider = provider;
-// _path = path;
-//
-// if (path != null)
-// {
-// try
-// {
-// String basename = FileID.getBasename(path);
-// Path properties = path.getParent().resolve(basename + ".properties");
-// if (Files.exists(properties))
-// {
-// try (InputStream stream = Files.newInputStream(properties))
-// {
-// Properties p = new Properties();
-// p.load(stream);
-// p.stringPropertyNames().forEach(k -> _properties.put(k, p.getProperty(k)));
-// }
-// }
-// }
-// catch (Exception e)
-// {
-// throw new RuntimeException(e);
-// }
-// }
-// }
-//
-// /**
-// * @return The deployment manager
-// */
-// public DeploymentManager getDeploymentManager()
-// {
-// return _manager;
-// }
-//
-// /**
-// * @return The AppProvider
-// */
-// public AppProvider getAppProvider()
-// {
-// return _provider;
-// }
-//
-// protected ContextHandler newContextHandler()
-// {
-// return getDeploymentManager().getContextHandlerFactory().newContextHandler(this);
-// }
-//
-// /**
-// * Get ContextHandler for the App.
-// *
-// * Create it if needed.
-// *
-// * @return the {@link ContextHandler} to use for the App when fully started.
-// * (Portions of which might be ignored when App is not yet
-// * {@link AppLifeCycle#DEPLOYED} or {@link AppLifeCycle#STARTED})
-// * @throws Exception if unable to get the context handler
-// */
-// public ContextHandler getContextHandler() throws Exception
-// {
-// if (_context == null)
-// _context = newContextHandler();
-// return _context;
-// }
-//
-// /**
-// * The context path {@link App} relating to how it is installed on the
-// * jetty server side.
-// *
-// * @return the contextPath for the App
-// */
-// public String getContextPath()
-// {
-// return _context == null ? null : _context.getContextPath();
-// }
-//
-// /**
-// * The origin of this {@link App} as specified by the {@link AppProvider}
-// *
-// * @return String representing the origin of this app.
-// */
-// public Path getPath()
-// {
-// return _path;
-// }
-//
-// @Override
-// public String toString()
-// {
-// return "App@%x[%s,%s,%s]".formatted(hashCode(), getEnvironmentName(), _context, _path);
-// }
+ ContextHandler getContextHandler();
}
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 1e1e2ad7148f..96dd6fc1698c 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
@@ -63,7 +63,6 @@ public Attributes getAttributes()
return _attributes;
}
- @Override
public ContextHandler createContextHandler(App app) throws Exception
{
if (app == null)
@@ -71,7 +70,6 @@ public ContextHandler createContextHandler(App app) throws Exception
//Create a ContextHandler suitable to deploy in OSGi
ContextHandler h = _contextFactory.createContextHandler(this, app);
-
return h;
}
@@ -81,11 +79,8 @@ 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;
}
diff --git a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/BundleContextProvider.java b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/BundleContextProvider.java
index 1f76665fcd35..7bc69b407c5f 100644
--- a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/BundleContextProvider.java
+++ b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/BundleContextProvider.java
@@ -23,7 +23,6 @@
import java.util.List;
import java.util.Map;
-import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.osgi.util.Util;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.StringUtil;
@@ -45,9 +44,9 @@ public class BundleContextProvider extends AbstractContextProvider implements Bu
{
private static final Logger LOG = LoggerFactory.getLogger(AbstractContextProvider.class);
- private Map _appMap = new HashMap<>();
+ private Map _appMap = new HashMap<>();
- private Map> _bundleMap = new HashMap<>();
+ private Map> _bundleMap = new HashMap<>();
private ServiceRegistration _serviceRegForBundles;
@@ -77,7 +76,7 @@ public Object addingBundle(Bundle bundle, BundleEvent event)
{
try
{
- String serverName = (String)bundle.getHeaders().get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
+ String serverName = bundle.getHeaders().get(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME);
if ((StringUtil.isBlank(serverName) && _serverName.equals(OSGiServerConstants.MANAGED_JETTY_SERVER_DEFAULT_NAME)) ||
(!StringUtil.isBlank(serverName) && (serverName.equals(_serverName))))
{
@@ -121,7 +120,7 @@ protected void doStart() throws Exception
_tracker.open();
//register as an osgi service for deploying contexts defined in a bundle, advertising the name of the jetty Server instance we are related to
- Dictionary properties = new Hashtable();
+ Dictionary properties = new Hashtable<>();
properties.put(OSGiServerConstants.MANAGED_JETTY_SERVER_NAME, serverName);
_serviceRegForBundles = FrameworkUtil.getBundle(this.getClass()).getBundleContext().registerService(BundleProvider.class.getName(), this, properties);
super.doStart();
@@ -181,26 +180,21 @@ public boolean bundleAdded(Bundle bundle) throws Exception
String[] tmp = contextFiles.split("[,;]");
for (String contextFile : tmp)
- {
- OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle);
+ {
+ OSGiApp app = new OSGiApp(bundle);
URI contextFilePath = Util.resolvePathAsLocalizedURI(contextFile, app.getBundle(), jettyHomePath);
//set up the single context file for this deployment
app.getProperties().put(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH, contextFilePath.toString());
-
+ app.setContextHandler(createContextHandler(app));
_appMap.put(app.getPath(), app);
- List apps = _bundleMap.get(bundle);
- if (apps == null)
- {
- apps = new ArrayList();
- _bundleMap.put(bundle, apps);
- }
+ List apps = _bundleMap.computeIfAbsent(bundle, b -> new ArrayList<>());
apps.add(app);
getDeploymentManager().addApp(app);
added = true;
}
- return added; //true if even 1 context from this bundle was added
+ return added; // true if even 1 context from this bundle was added
}
/**
@@ -212,11 +206,11 @@ public boolean bundleAdded(Bundle bundle) throws Exception
@Override
public boolean bundleRemoved(Bundle bundle) throws Exception
{
- List apps = _bundleMap.remove(bundle);
+ List apps = _bundleMap.remove(bundle);
boolean removed = false;
if (apps != null)
{
- for (App app : apps)
+ for (OSGiApp app : apps)
{
if (_appMap.remove(app.getPath()) != null)
{
diff --git a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/BundleWebAppProvider.java b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/BundleWebAppProvider.java
index 843e427551fb..d70bd829aec4 100644
--- a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/BundleWebAppProvider.java
+++ b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/BundleWebAppProvider.java
@@ -185,8 +185,9 @@ public boolean bundleAdded(Bundle bundle) throws Exception
//TODO : we don't know whether an app is actually deployed, as deploymentManager swallows all
//exceptions inside the impl of addApp. Need to send the Event and also register as a service
//only if the deployment succeeded
- OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle);
+ OSGiApp app = new OSGiApp(bundle);
app.setPathToResourceBase(staticResourcesLocation);
+ app.setContextHandler(createContextHandler(app));
_bundleMap.put(bundle, app);
getDeploymentManager().addApp(app);
return true;
@@ -196,7 +197,8 @@ public boolean bundleAdded(Bundle bundle) throws Exception
if (bundle.getEntry("/WEB-INF/web.xml") != null)
{
String base = ".";
- OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle);
+ OSGiApp app = new OSGiApp(bundle);
+ app.setContextHandler(createContextHandler(app));
app.setPathToResourceBase(base);
_bundleMap.put(bundle, app);
getDeploymentManager().addApp(app);
@@ -208,8 +210,9 @@ public boolean bundleAdded(Bundle bundle) throws Exception
{
//Could be a static webapp with no web.xml
String base = ".";
- OSGiApp app = new OSGiApp(getDeploymentManager(), this, bundle);
+ OSGiApp app = new OSGiApp(bundle);
app.setPathToResourceBase(base);
+ app.setContextHandler(createContextHandler(app));
_bundleMap.put(bundle, app);
getDeploymentManager().addApp(app);
return true;
diff --git a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiApp.java b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiApp.java
index 5f2fa1fad017..2e8b5d6a8e4d 100644
--- a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiApp.java
+++ b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiApp.java
@@ -21,10 +21,9 @@
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Objects;
+import java.util.Properties;
import org.eclipse.jetty.deploy.App;
-import org.eclipse.jetty.deploy.AppProvider;
-import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.osgi.util.BundleFileLocatorHelperFactory;
import org.eclipse.jetty.server.Deployable;
import org.eclipse.jetty.server.handler.ContextHandler;
@@ -42,11 +41,14 @@
*
* Base class representing info about a WebAppContext/ContextHandler to be deployed into jetty.
*/
-public class OSGiApp extends App
+public class OSGiApp implements App
{
private static final Logger LOG = LoggerFactory.getLogger(OSGiApp.class);
+ private final String _bundleName;
+ private final Path _bundlePath;
protected Bundle _bundle;
+ protected Properties _properties;
protected ServiceRegistration _registration;
protected ContextHandler _contextHandler;
protected String _pathToResourceBase;
@@ -114,21 +116,16 @@ private static String getContextPath(Bundle bundle)
return contextPath;
}
-
- /**
- * @param manager the DeploymentManager to which to deploy
- * @param provider the provider that discovered the context/webapp
- * @param bundle the bundle associated with the context/webapp
- */
- public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle)
+
+ public OSGiApp(Bundle bundle)
throws Exception
{
- super(manager, provider, getBundlePath(bundle));
-
+ _bundleName = bundle.getSymbolicName();
+ _bundlePath = getBundlePath(bundle);
_bundle = Objects.requireNonNull(bundle);
_bundleResource = getBundleAsResource(bundle);
-
- //copy selected bundle headers into the properties
+
+ // copy selected bundle headers into the properties
Dictionary headers = bundle.getHeaders();
Enumeration keys = headers.keys();
while (keys.hasMoreElements())
@@ -136,7 +133,9 @@ public OSGiApp(DeploymentManager manager, AppProvider provider, Bundle bundle)
String key = keys.nextElement();
String val = headers.get(key);
if (Deployable.ENVIRONMENT.equalsIgnoreCase(key) || OSGiWebappConstants.JETTY_ENVIRONMENT.equalsIgnoreCase(key))
+ {
getProperties().put(Deployable.ENVIRONMENT, val);
+ }
else if (Deployable.DEFAULTS_DESCRIPTOR.equalsIgnoreCase(key) || OSGiWebappConstants.JETTY_DEFAULT_WEB_XML_PATH.equalsIgnoreCase(key))
{
getProperties().put(Deployable.DEFAULTS_DESCRIPTOR, val);
@@ -155,17 +154,31 @@ else if (OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH.equalsIgnoreCase(key))
setContextPath(getContextPath(bundle));
}
+ @Override
+ public String getName()
+ {
+ return _bundleName;
+ }
+
+ public Path getPath()
+ {
+ return _bundlePath;
+ }
+
+ public Properties getProperties()
+ {
+ return _properties;
+ }
+
public Resource getBundleResource()
{
return _bundleResource;
}
@Override
- public ContextHandler getContextHandler() throws Exception
+ public ContextHandler getContextHandler()
{
- if (_contextHandler == null)
- _contextHandler = getAppProvider().createContextHandler(this);
- return _contextHandler;
+ return _contextHandler;
}
public void setContextHandler(ContextHandler contextHandler)
@@ -183,7 +196,6 @@ public void setPathToResourceBase(String path)
_pathToResourceBase = path;
}
- @Override
public String getContextPath()
{
return _contextPath;
diff --git a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiDeployer.java b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiDeployer.java
index 032cf76f6249..83569c8c1dd3 100644
--- a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiDeployer.java
+++ b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiDeployer.java
@@ -14,10 +14,12 @@
package org.eclipse.jetty.osgi;
import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.bindings.StandardDeployer;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.osgi.util.EventSender;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
/**
* OSGiDeployer
@@ -27,8 +29,7 @@
*/
public class OSGiDeployer extends StandardDeployer
{
-
- private Server _server;
+ private final Server _server;
public OSGiDeployer(Server server)
{
@@ -36,39 +37,44 @@ public OSGiDeployer(Server server)
}
@Override
- public void processBinding(Node node, App app) throws Exception
+ public void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
- //TODO how to NOT send this event if its not a webapp:
+ ContextHandler contextHandler = app.getContextHandler();
+ if (contextHandler == null)
+ return;
+
+ //TODO how to NOT send this event if its not a webapp:
//OSGi Enterprise Spec only wants an event sent if its a webapp bundle (ie not a ContextHandler)
if (!(app instanceof OSGiApp))
{
- doProcessBinding(node, app);
+ doProcessBinding(deploymentManager, node, app);
}
else
{
- EventSender.getInstance().send(EventSender.DEPLOYING_EVENT, ((OSGiApp)app).getBundle(), app.getContextPath());
+ String contextPath = contextHandler.getContextPath();
+ EventSender.getInstance().send(EventSender.DEPLOYING_EVENT, ((OSGiApp)app).getBundle(), contextPath);
try
{
- doProcessBinding(node, app);
+ doProcessBinding(deploymentManager, node, app);
((OSGiApp)app).registerAsOSGiService();
- EventSender.getInstance().send(EventSender.DEPLOYED_EVENT, ((OSGiApp)app).getBundle(), app.getContextPath());
+ EventSender.getInstance().send(EventSender.DEPLOYED_EVENT, ((OSGiApp)app).getBundle(), contextPath);
}
catch (Exception e)
{
- EventSender.getInstance().send(EventSender.FAILED_EVENT, ((OSGiApp)app).getBundle(), app.getContextPath());
+ EventSender.getInstance().send(EventSender.FAILED_EVENT, ((OSGiApp)app).getBundle(), contextPath);
throw e;
}
}
}
- protected void doProcessBinding(Node node, App app) throws Exception
+ protected void doProcessBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
ClassLoader old = Thread.currentThread().getContextClassLoader();
ClassLoader cl = (ClassLoader)_server.getAttribute(OSGiServerConstants.SERVER_CLASSLOADER);
Thread.currentThread().setContextClassLoader(cl);
try
{
- super.processBinding(node, app);
+ super.processBinding(deploymentManager, node, app);
}
finally
{
diff --git a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiUndeployer.java b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiUndeployer.java
index 9fa1a22b5cb6..dd08f3c7fc78 100644
--- a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiUndeployer.java
+++ b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/OSGiUndeployer.java
@@ -14,10 +14,12 @@
package org.eclipse.jetty.osgi;
import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.bindings.StandardUndeployer;
import org.eclipse.jetty.deploy.graph.Node;
import org.eclipse.jetty.osgi.util.EventSender;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandler;
/**
* OSGiUndeployer
@@ -27,7 +29,7 @@
*/
public class OSGiUndeployer extends StandardUndeployer
{
- private Server _server;
+ private final Server _server;
public OSGiUndeployer(Server server)
{
@@ -35,21 +37,27 @@ public OSGiUndeployer(Server server)
}
@Override
- public void processBinding(Node node, App app) throws Exception
+ public void processBinding(DeploymentManager deploymentManager, Node node, App app) throws Exception
{
- EventSender.getInstance().send(EventSender.UNDEPLOYING_EVENT, ((OSGiApp)app).getBundle(), app.getContextPath());
+ ContextHandler contextHandler = app.getContextHandler();
+ if (contextHandler == null)
+ return;
+
+ String contextPath = contextHandler.getContextPath();
+
+ EventSender.getInstance().send(EventSender.UNDEPLOYING_EVENT, ((OSGiApp)app).getBundle(), contextPath);
ClassLoader old = Thread.currentThread().getContextClassLoader();
ClassLoader cl = (ClassLoader)_server.getAttribute(OSGiServerConstants.SERVER_CLASSLOADER);
Thread.currentThread().setContextClassLoader(cl);
try
{
- super.processBinding(node, app);
+ super.processBinding(deploymentManager, node, app);
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
- EventSender.getInstance().send(EventSender.UNDEPLOYED_EVENT, ((OSGiApp)app).getBundle(), app.getContextPath());
+ EventSender.getInstance().send(EventSender.UNDEPLOYED_EVENT, ((OSGiApp)app).getBundle(), contextPath);
((OSGiApp)app).deregisterAsOSGiService();
}
}
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 232c43599a9a..ee94d116efe7 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
@@ -150,10 +150,16 @@ public Object addingService(ServiceReference sr)
{
for (AppProvider provider : deployer.get().getAppProviders())
{
- if (BundleContextProvider.class.isInstance(provider) && ENVIRONMENT.equalsIgnoreCase(provider.getEnvironmentName()))
- contextProvider = BundleContextProvider.class.cast(provider);
- if (BundleWebAppProvider.class.isInstance(provider) && ENVIRONMENT.equalsIgnoreCase(provider.getEnvironmentName()))
- webAppProvider = BundleWebAppProvider.class.cast(provider);
+ if (provider instanceof BundleContextProvider bundleContextProvider)
+ {
+ if (bundleContextProvider.getEnvironmentName().equalsIgnoreCase(ENVIRONMENT))
+ contextProvider = bundleContextProvider;
+ }
+ if (provider instanceof BundleWebAppProvider bundleWebAppProvider)
+ {
+ if (bundleWebAppProvider.getEnvironmentName().equalsIgnoreCase(ENVIRONMENT))
+ webAppProvider = bundleWebAppProvider;
+ }
}
if (contextProvider == null)
{
@@ -263,7 +269,7 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App
throws Exception
{
OSGiApp osgiApp = OSGiApp.class.cast(app);
- String jettyHome = (String)app.getDeploymentManager().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
+ String jettyHome = (String)provider.getDeploymentManager().getServer().getAttribute(OSGiServerConstants.JETTY_HOME);
Path jettyHomePath = (StringUtil.isBlank(jettyHome) ? null : Paths.get(jettyHome));
ContextHandler contextHandler = new ContextHandler();
@@ -272,7 +278,7 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App
contextHandler.setBaseResource(osgiApp.getBundleResource());
// provides access to core classes
- ClassLoader coreLoader = (ClassLoader)osgiApp.getDeploymentManager().getServer().getAttribute(OSGiServerConstants.SERVER_CLASSLOADER);
+ ClassLoader coreLoader = (ClassLoader)provider.getDeploymentManager().getServer().getAttribute(OSGiServerConstants.SERVER_CLASSLOADER);
if (LOG.isDebugEnabled())
LOG.debug("Core classloader = {}", coreLoader.getClass());
@@ -284,7 +290,7 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App
contextHandler.setClassLoader(classLoader);
//Apply any context xml file
- String tmp = osgiApp.getProperties().get(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH);
+ String tmp = osgiApp.getProperties().getProperty(OSGiWebappConstants.JETTY_CONTEXT_FILE_PATH);
final URI contextXmlURI = Util.resolvePathAsLocalizedURI(tmp, osgiApp.getBundle(), jettyHomePath);
if (contextXmlURI != null)
@@ -299,9 +305,9 @@ public ContextHandler createContextHandler(AbstractContextProvider provider, App
WebAppClassLoader.runWithServerClassAccess(() ->
{
Map