Skip to content
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

Fix demo-spec webapp failures #10178

Merged
merged 2 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IO.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,21 @@
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.GatheringByteChannel;
import java.nio.charset.Charset;
import java.nio.file.CopyOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -218,6 +223,37 @@ public static void copyDir(File from, File to) throws IOException
}
}

/**
* Copy the contents of a directory from one directory to another.
*
* @param srcDir the source directory
* @param destDir the destination directory
* @throws IOException if unable to copy the file
*/
public static void copyDir(Path srcDir, Path destDir, CopyOption... copyOptions) throws IOException
{
if (!Files.isDirectory(Objects.requireNonNull(srcDir)))
throw new IllegalArgumentException("Source is not a directory: " + srcDir);
if (!Files.isDirectory(Objects.requireNonNull(destDir)))
throw new IllegalArgumentException("Dest is not a directory: " + destDir);

try (Stream<Path> sourceStream = Files.walk(srcDir, 20))
{
Iterator<Path> iterFiles = sourceStream
.filter(Files::isRegularFile)
.iterator();
while (iterFiles.hasNext())
{
Path sourceFile = iterFiles.next();
URI relativeSrc = srcDir.toUri().relativize(sourceFile.toUri());
Path destFile = destDir.resolve(relativeSrc.toASCIIString());
if (!Files.exists(destFile.getParent()))
Files.createDirectories(destFile.getParent());
Files.copy(sourceFile, destFile, copyOptions);
}
}
}

public static void copyFile(File from, File to) throws IOException
{
try (InputStream in = new FileInputStream(from);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ public void onStartup(Set<Class<?>> classes, ServletContext context)
if (context.getAttribute("org.example.Foo") != null)
throw new IllegalStateException("FooInitializer on Startup already called");

context.setAttribute("org.example.Foo", new ArrayList<Class>(classes));
ServletRegistration.Dynamic reg = context.addServlet("AnnotationTest", "org.example.AnnotationTest");
context.setAttribute("org.example.Foo", new ArrayList<>(classes));
ServletRegistration.Dynamic reg = context.addServlet("AnnotationTest", "org.example.test.AnnotationTest");
Comment on lines -86 to +87
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was the bug that caused all of the varying failures.

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should refer to the class, rather than the name of the class?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The AnnotationTest class is in the jetty-ee10-demo-spec-webapp project.
The above line in FooInitializer is in the jetty-ee10-demo-container-initializer project (It cannot see the AnnotationTest class)

context.setAttribute("org.example.AnnotationTest.complete", (reg == null));
context.addListener(new FooListener());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,20 +207,30 @@
<groupId>org.eclipse.jetty.ee10.demos</groupId>
<artifactId>jetty-ee10-demo-container-initializer</artifactId>
</dependency>

<!-- deliberately old version to test classloading -->
<!-- TODO uncomment and update the following once 9.4.19 is released with a fix for #3726
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-webapp</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-annotations</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee10.demos</groupId>
<artifactId>jetty-ee10-demo-mock-resources</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>9.4.19.vXXXXXXXX</version>
<exclusions>
<exclusion>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
</exclusion>
</exclusions>
<artifactId>jetty-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
-->
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//
// ========================================================================
// 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.ee10.demos;

import java.io.IOException;
import java.net.URI;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;

import org.eclipse.jetty.client.ContentResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.ee10.webapp.WebAppContext;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenPaths;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory;
import org.example.MockDataSource;
import org.example.MockUserTransaction;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.fail;

@ExtendWith(WorkDirExtension.class)
public class SpecWebAppTest
{
private Server server;
private HttpClient client;

@BeforeEach
public void setup(WorkDir workDir) throws Exception
{
server = new Server();

ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);

Path webappDir = prepareWebAppDir(workDir);

WebAppContext webapp = new WebAppContext();
ResourceFactory resourceFactory = ResourceFactory.of(webapp);
webapp.setContextPath("/");
webapp.setWarResource(resourceFactory.newResource(webappDir));
webapp.setAttribute(
"org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
".*/jakarta.servlet-api-[^/]*\\.jar$|.*/[^/]*taglibs.*\\.jar$");

HashLoginService hashLoginService = new HashLoginService();
hashLoginService.setName("Test Realm");
Path realmFile = MavenPaths.findTestResourceFile("ee10-demo-realm.properties");
Resource realmResource = ResourceFactory.of(server).newResource(realmFile);
hashLoginService.setConfig(realmResource);
SecurityHandler securityHandler = webapp.getSecurityHandler();
securityHandler.setLoginService(hashLoginService);

new org.eclipse.jetty.ee10.plus.jndi.Resource(webapp, "jdbc/mydatasource", new MockDataSource());
new org.eclipse.jetty.ee10.plus.jndi.Transaction("ee10", new MockUserTransaction());

server.setHandler(webapp);
server.start();

client = new HttpClient();
client.start();
}

private Path prepareWebAppDir(WorkDir workDir) throws IOException
{
Path webappDir = workDir.getEmptyPathDir();
Path srcWebapp = MavenPaths.projectBase().resolve("src/main/webapp");
IO.copyDir(srcWebapp, webappDir);

Path webappClassesDir = webappDir.resolve("WEB-INF/classes");
FS.ensureDirExists(webappClassesDir);
Path classesDir = MavenPaths.projectBase().resolve("target/classes");
IO.copyDir(classesDir, webappClassesDir);

Path libDir = webappDir.resolve("WEB-INF/lib");
FS.ensureDirExists(libDir);
copyDependency("jetty-ee10-demo-container-initializer", libDir);
copyDependency("jetty-ee10-demo-web-fragment", libDir);

return webappDir;
}

private void copyDependency(String depName, Path libDir) throws IOException
{
Path depPath = MavenPaths.projectBase().resolve("../" + depName).normalize();
if (!Files.isDirectory(depPath))
fail("Dependency not found: " + depPath);
Path outputJar = libDir.resolve(depName + ".jar");
Map<String, String> env = new HashMap<>();
env.put("create", "true");

URI uri = URI.create("jar:" + outputJar.toUri().toASCIIString());
try (FileSystem fs = FileSystems.newFileSystem(uri, env))
{
Path root = fs.getPath("/");
IO.copyDir(depPath.resolve("target/classes"), root);
Copy link
Member

@olamy olamy Aug 2, 2023

Choose a reason for hiding this comment

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

uhm this assume you are running full build
this should use dependency:unpack or dependency:copy but not rely on the availability of build directory (this is breaking incremental/cache/partial build support :( )

Copy link
Member

Choose a reason for hiding this comment

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

fixed in a branch with this change b37aacf

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Submit PR with that change.

IO.copyDir(depPath.resolve("src/main/resources"), root, StandardCopyOption.REPLACE_EXISTING);
}
}

@AfterEach
public void teardown()
{
LifeCycle.stop(client);
LifeCycle.stop(server);
}

@Test
public void testNoFailures() throws InterruptedException, ExecutionException, TimeoutException
{
ContentResponse response = client.newRequest(server.getURI().resolve("/test/"))
.followRedirects(false)
.send();

assertThat("response status", response.getStatus(), is(HttpStatus.OK_200));
// Look for 0 entries that fail.
assertThat("response", response.getContentAsString(), not(containsString(">FAIL<")));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#
# This file defines users passwords and roles for a HashUserRealm
#
# The format is
# <username>: <password>[,<rolename> ...]
#
# Passwords may be clear text, obfuscated or checksummed. The class
# org.eclipse.util.Password should be used to generate obfuscated
# passwords or password checksums
#
# If DIGEST Authentication is used, the password must be in a recoverable
# format, either plain text or OBF:.
#
jetty: MD5:164c88b302622e17050af52c89945d44,user
admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user
other: OBF:1xmk1w261u9r1w1c1xmq,user
plain: plain,user
user: password,user

# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password
digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Jetty Logging using jetty-slf4j-impl
org.eclipse.jetty.LEVEL=INFO
#org.eclipse.jetty.STACKS=true
#org.eclipse.jetty.ee10.annotations.LEVEL=DEBUG
#org.eclipse.jetty.STACKS=false
#org.eclipse.jetty.io.LEVEL=DEBUG
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.ee10.servlets.LEVEL=DEBUG
#org.eclipse.jetty.alpn.LEVEL=DEBUG
#org.eclipse.jetty.jmx.LEVEL=DEBUG