Skip to content

Issue #12990 - Introduce static-deploy module #12998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d72b287
Issue #12990 - Restore static directory deployment for core-deploy
joakime Apr 11, 2025
a598469
Issue #12990 - Introduce static-deploy module
joakime Apr 15, 2025
c5cb76d
Fixing <dependency> reference
joakime Apr 15, 2025
81c1f51
Move ENVIRONMENT_COMPARATOR to DeploymentScanner and add test cases.
joakime Apr 16, 2025
3c7156b
Make ContextHandler implement Deployable
joakime Apr 16, 2025
cd2a996
Constructor StaticContextHandler with ResourceHandler
joakime Apr 16, 2025
6e9adbb
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/co…
joakime Apr 16, 2025
fa2e720
The CoreContextHandler cares about default context-path, but not in t…
joakime Apr 16, 2025
17ea7ed
Moving default-context-path from WebAppContext to ContextHandler so t…
joakime Apr 17, 2025
7351898
Using default contextPath from CoreContextHandler and StaticContextHa…
joakime Apr 17, 2025
518ea13
fixing ee11 DeploymentDefaultContextPathTest by ensuring ee11 exists …
joakime Apr 17, 2025
499f5b0
Handle static directory test properly
joakime Apr 17, 2025
5b80a94
Changes from review
joakime Apr 18, 2025
60065cc
More places to use new IO.asFile(Object)
joakime Apr 18, 2025
eb2c5dd
Fix naming initializeDefaultsComplete()
joakime Apr 18, 2025
0ee1529
Don't use Resource for IO.asFile
joakime Apr 18, 2025
3d5c525
Fixing isResourceHandlerAlreadyPresent returns
joakime Apr 18, 2025
0329896
Removing redundant constructors
joakime Apr 18, 2025
1f49310
Revert "Removing redundant constructors"
joakime Apr 18, 2025
cadf0a5
Changes from review
joakime Apr 22, 2025
8149809
Log warning instead of throwing
joakime Apr 23, 2025
532b422
Changes from review
joakime Apr 23, 2025
db8f04d
More work on StaticContextHandler testing
joakime Apr 23, 2025
f0a2f3b
Environment default based only on configured Environments for deploy
joakime Apr 23, 2025
b159daf
Merge remote-tracking branch 'origin/jetty-12.1.x' into fix/12.1.x/co…
joakime Apr 24, 2025
99174e4
Fixing 1 line description in new deploy modules
joakime Apr 24, 2025
660ae3f
Cleaning up javadoc for new handlers
joakime Apr 24, 2025
aaab35f
Rename attributes.
joakime Apr 28, 2025
860415b
Adding check of declared app environment to configured deployed envir…
joakime Apr 28, 2025
e3c7ce4
Merged branch 'jetty-12.1.x' into 'fix/12.1.x/coredeploy-static-direc…
sbordet Apr 29, 2025
65aaca2
Adding testcase for baseResource in property for static deploy
joakime Apr 29, 2025
40f0996
Merge remote-tracking branch 'origin/fix/12.1.x/coredeploy-static-dir…
joakime Apr 29, 2025
6ca1507
Adding two /environments/ examples as test cases
joakime Apr 29, 2025
32c5507
Fix testcase environment behavior
joakime Apr 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -127,6 +129,33 @@ public class DeploymentScanner extends ContainerLifeCycle implements Scanner.Bul
// old attributes prefix, now stripped.
private static final String ATTRIBUTE_PREFIX = "jetty.deploy.attribute.";

private static final Pattern EE_ENVIRONMENT_NAME_PATTERN = Pattern.compile("ee(\\d+)");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know we use "ee", but just in case, perhaps this should be

Suggested change
private static final Pattern EE_ENVIRONMENT_NAME_PATTERN = Pattern.compile("ee(\\d+)");
private static final Pattern EE_ENVIRONMENT_NAME_PATTERN = Pattern.compile("ee(\\d+)", Pattern.CASE_INSENSITIVE);


/**
* A comparator that ranks names matching EE_ENVIRONMENT_NAME_PATTERN higher than other names,
* EE names are compared by EE number, otherwise simple name comparison is used.
*/
protected static final Comparator<String> ENVIRONMENT_COMPARATOR = (e1, e2) ->
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparator will generate a lot of matchers, so perhaps take the returns where you can:

Suggested change
{
{
if (Objects.equals(e1, e2))
return 0;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better yet, why not just create an Index that is case insensitive and maps all the known environment names to an integer.

    Index<Integer> environmentOrdinals = new Index.Builder<Integer>()
        .caseSensitive(false)
        .with("static", 1)
        .with("core", 2)
        .with("ee9", 9)
        .with("ee10", 10)
        .with("ee11", 11)
        .build();

Copy link
Contributor Author

@joakime joakime Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a conversation with @sbordet and @lorban this will be fixed in a followup PR.

I have an approach that will push the weight of the tracked environments (the ones allowed to be deployed to) via a change to the DeploymentScanner.configureEnvironment(String name, int weight), putting the weights into the XMLs instead.

That way the weights are not hardcoded, and can be adjusted by any user, and even add new environments (with their own weights) without the need to modify code.

Matcher m1 = EE_ENVIRONMENT_NAME_PATTERN.matcher(e1);
Matcher m2 = EE_ENVIRONMENT_NAME_PATTERN.matcher(e2);

if (m1.matches())
{
if (m2.matches())
{
int n1 = Integer.parseInt(m1.group(1));
int n2 = Integer.parseInt(m2.group(1));
return Integer.compare(n2, n1);
}
return -1;
}
if (m2.matches())
return 1;

return e1.compareTo(e2);
};

private final Server server;
private final FilenameFilter filenameFilter;
private final List<Path> monitoredDirs = new CopyOnWriteArrayList<>();
Expand Down Expand Up @@ -292,7 +321,12 @@ void addScannerListener(Scanner.Listener listener)
*/
public EnvironmentConfig configureEnvironment(String name)
{
return new EnvironmentConfig(Environment.get(name));
Environment environment = Environment.get(name);
// Check to make sure that the Environment was created before jetty-deploy is involved.
// This is to ensure that the Environment ClassLoader is setup properly.
if (environment == null)
throw new IllegalStateException("Environment [" + name + "] does not exist.");
return new EnvironmentConfig(environment);
}

/**
Expand All @@ -314,7 +348,7 @@ public void setActionComparator(Comparator<DeployAction> actionComparator)
*
* <p>
* Falls back to {@link Environment#getAll()} list, and returns
* the first name returned after sorting with {@link Deployable#ENVIRONMENT_COMPARATOR}
* the first name returned after sorting.
* </p>
*
* @return the default environment name.
Expand All @@ -325,7 +359,7 @@ public String getDefaultEnvironmentName()
{
return Environment.getAll().stream()
.map(Environment::getName)
.max(Deployable.ENVIRONMENT_COMPARATOR)
.min(ENVIRONMENT_COMPARATOR)
.orElse(null);
}
return defaultEnvironmentName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ public void testRedeployViaNewEmptyProperties(WorkDir workDir) throws Exception
startJetty();

Path webappsDir = jetty.getJettyBasePath().resolve("webapps");
Files.createDirectory(webappsDir.resolve("simple"));
Files.writeString(webappsDir.resolve("simple/simple.txt"), "Simple Contents");
FS.ensureDirExists(webappsDir.resolve("simple/static"));
Files.writeString(webappsDir.resolve("simple/static/simple.txt"), "Simple Contents");
waitForDirectoryScan();
jetty.assertContextHandlerExists("/simple");
ContextHandler contextHandler = jetty.getContextHandler("/simple");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,22 @@
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;

import org.eclipse.jetty.deploy.DeploymentScanner.DeployAction;
import org.eclipse.jetty.deploy.DeploymentScanner.PathsApp;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.toolchain.test.ExtraMatchers;
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 org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.MatcherAssert.assertThat;
Expand Down Expand Up @@ -364,4 +369,24 @@ public void testActionListXmlAndWarWithXmlRemoved() throws IOException

deploymentScanner.pathsChanged(changeSet);
}

public static Stream<Arguments> envNameSorting()
{
return Stream.of(
Arguments.of(List.of("static", "core"), List.of("core", "static")),
Arguments.of(List.of("core", "static"), List.of("core", "static")),
Arguments.of(List.of("core", "ee11"), List.of("ee11", "core")),
Arguments.of(List.of("core", "ee11", "ee9", "ee10"), List.of("ee11", "ee10", "ee9", "core"))
);
}

@ParameterizedTest
@MethodSource("envNameSorting")
public void testEnvironmentNameSorting(List<String> input, List<String> expected)
{
List<String> sorted = input.stream()
.sorted(DeploymentScanner.ENVIRONMENT_COMPARATOR)
.toList();
assertThat(sorted, ExtraMatchers.ordered(expected));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://jetty.org/configure_10_0.dtd">

<!-- =============================================================== -->
<!-- Configure the "static" environment deployment defaults -->
<!-- =============================================================== -->
<Configure>
<Ref refid="deploymentScanner">
<Call name="configureEnvironment">
<Arg>static</Arg>
<Set name="contextHandlerClass">
<Property name="contextHandlerClass" default="org.eclipse.jetty.server.handler.StaticContextHandler" />
</Set>
</Call>
</Ref>
</Configure>
20 changes: 20 additions & 0 deletions jetty-core/jetty-server/src/main/config/modules/static-deploy.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[description]
# tag::description[]
Scans and deploys `static` contexts from `$JETTY_BASE/webapps/` directory.
# end::description[]

[tags]
deployment

[environment]
static

[depend]
deployment-scanner

[xml]
etc/jetty-static-deploy.xml

[ini-template]
## Default ContextHandler class for "static" environment deployments
# contextHandlerClass=org.eclipse.jetty.server.handler.StaticContextHandler
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
package org.eclipse.jetty.server;

import java.io.File;
import java.util.Comparator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Attributes;
Expand All @@ -28,33 +25,6 @@
*/
public interface Deployable
{
Pattern EE_ENVIRONMENT_NAME_PATTERN = Pattern.compile("ee(\\d+)");

/**
* A comparator that ranks names matching EE_ENVIRONMENT_NAME_PATTERN higher than other names,
* EE names are compared by EE number, otherwise simple name comparison is used.
*/
Comparator<String> ENVIRONMENT_COMPARATOR = (e1, e2) ->
{
Matcher m1 = EE_ENVIRONMENT_NAME_PATTERN.matcher(e1);
Matcher m2 = EE_ENVIRONMENT_NAME_PATTERN.matcher(e2);

if (m1.matches())
{
if (m2.matches())
{
int n1 = Integer.parseInt(m1.group(1));
int n2 = Integer.parseInt(m2.group(1));
return Integer.compare(n1, n2);
}
return 1;
}
if (m2.matches())
return -1;

return e1.compareTo(e2);
};

/**
* <p>Attribute key name: Temp Directory for context.</p>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public String getServerInfo()
*/
public void setTempDirectory(String temp)
{
setTempDirectory(new File(temp));
setTempDirectory(IO.asFile(temp));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.eclipse.jetty.server.AliasCheck;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Deployable;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Request;
Expand Down Expand Up @@ -71,7 +72,7 @@
* A {@link Handler} that scopes a request to a specific {@link Context}.
*/
@ManagedObject
public class ContextHandler extends Handler.Wrapper implements Attributes, AliasCheck
public class ContextHandler extends Handler.Wrapper implements Attributes, AliasCheck, Deployable
{
private static final Logger LOG = LoggerFactory.getLogger(ContextHandler.class);
private static final ThreadLocal<Context> __context = new ThreadLocal<>();
Expand Down Expand Up @@ -136,6 +137,7 @@ public static ContextHandler getContextHandler(Request request)

private String _displayName;
private String _contextPath = "/";
private boolean _defaultContextPath = true;
private boolean _rootContext = true;
private Resource _baseResource;
private ClassLoader _classLoader;
Expand Down Expand Up @@ -616,6 +618,46 @@ public boolean removeEventListener(EventListener listener)
return false;
}

@Override
public void initializeDefaults(Attributes attributes)
{
for (String keyName : attributes.getAttributeNameSet())
{
Object value = attributes.getAttribute(keyName);
if (LOG.isDebugEnabled())
LOG.debug("init {}: {}", keyName, value);

switch (keyName)
{
case Deployable.TEMP_DIR -> setTempDirectory(IO.asFile(value));
case Deployable.CONTEXT_PATH -> setContextPath((String)value);
case Deployable.DEFAULT_CONTEXT_PATH -> setDefaultContextPath((String)value);
default -> initializeDefault(keyName, value);
}
}
initializeDefaultComplete();
}

/**
* Called for each attribute key encountered during the
* {@link Deployable#initializeDefaults(Attributes)} processing.
*
* @param keyName the key name
* @param value the value
*/
protected void initializeDefault(String keyName, Object value)
{
}

/**
* Called after all attributes are processed via
* {@link Deployable#initializeDefaults(Attributes)}, to allow
* any kind of extra processing of the configuration.
*/
protected void initializeDefaultComplete()
{
}

protected ClassLoader enterScope(Request contextRequest)
{
ClassLoader lastLoader = Thread.currentThread().getContextClassLoader();
Expand Down Expand Up @@ -1122,6 +1164,22 @@ public void setContextPath(String contextPath)
throw new IllegalStateException(getState());
_contextPath = URIUtil.canonicalPath(Objects.requireNonNull(contextPath));
_rootContext = "/".equals(contextPath);
_defaultContextPath = false;
}

public void setDefaultContextPath(String contextPath)
{
// Don't set default context path, if context-path is set before init (like from XML)
if (isContextPathDefault())
{
setContextPath(contextPath);
_defaultContextPath = true;
}
}

public boolean isContextPathDefault()
{
return _defaultContextPath;
}

/**
Expand Down
Loading