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

TCK Tracking: Jakarta EE 10 Core Profile #6799 #6885

Merged
merged 1 commit into from
Aug 9, 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
2 changes: 1 addition & 1 deletion dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
<version.lib.opentracing.grpc>0.2.1</version.lib.opentracing.grpc>
<version.lib.opentracing.tracerresolver>0.1.8</version.lib.opentracing.tracerresolver>
<version.lib.perfmark-api>0.25.0</version.lib.perfmark-api>
<version.lib.parsson>1.0.2</version.lib.parsson>
<version.lib.parsson>1.1.2</version.lib.parsson>
<version.lib.postgresql>42.4.3</version.lib.postgresql>
<version.lib.prometheus>0.16.0</version.lib.prometheus>
<version.lib.reactivestreams>1.0.4</version.lib.reactivestreams>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,20 @@ private void processAnnotatedType(@Observes @WithAnnotations(ConfigProperties.cl
private <X> void harvestConfigPropertyInjectionPointsFromEnabledObserverMethod(@Observes ProcessObserverMethod<?, X> event,
BeanManager beanManager) {
AnnotatedMethod<X> annotatedMethod = event.getAnnotatedMethod();
List<AnnotatedParameter<X>> annotatedParameters = annotatedMethod.getParameters();
if (annotatedParameters != null) {
for (AnnotatedParameter<?> annotatedParameter : annotatedParameters) {
if ((annotatedParameter != null)
&& !annotatedParameter.isAnnotationPresent(Observes.class)) {
InjectionPoint injectionPoint = beanManager.createInjectionPoint(annotatedParameter);
Set<Annotation> qualifiers = injectionPoint.getQualifiers();
assert qualifiers != null;
for (Annotation qualifier : qualifiers) {
if (qualifier instanceof ConfigProperty) {
ips.add(injectionPoint);
break;
if (annotatedMethod != null) {
List<AnnotatedParameter<X>> annotatedParameters = annotatedMethod.getParameters();
if (annotatedParameters != null) {
for (AnnotatedParameter<?> annotatedParameter : annotatedParameters) {
if ((annotatedParameter != null)
&& !annotatedParameter.isAnnotationPresent(Observes.class)) {
InjectionPoint injectionPoint = beanManager.createInjectionPoint(annotatedParameter);
Set<Annotation> qualifiers = injectionPoint.getQualifiers();
assert qualifiers != null;
for (Annotation qualifier : qualifiers) {
if (qualifier instanceof ConfigProperty) {
ips.add(injectionPoint);
break;
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2022 Oracle and/or its affiliates.
* Copyright (c) 2018, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,6 +36,7 @@
* is empty)</li>
* <li>replaceConfigSourcesWithMp: (Optional) defaults to false: whether to replace config sources with microprofile if it
* exists</li>
* <li>inWebContainer: defaults to false: sets web app context root, load WEB-INF/beans.xml and find any jakarta.ws.rs.core.Application in the webapp classes</li>
* </ul>
*/
public class HelidonContainerConfiguration implements ContainerConfiguration {
Expand All @@ -45,6 +46,7 @@ public class HelidonContainerConfiguration implements ContainerConfiguration {
private boolean deleteTmp = true;
private boolean useRelativePath = false;
private boolean useParentClassloader = true;
private boolean inWebContainer = false;
private final List<Consumer<ConfigBuilder>> builderConsumers = new ArrayList<>();

/**
Expand Down Expand Up @@ -104,6 +106,14 @@ public void setUseParentClassloader(boolean useParentClassloader) {
this.useParentClassloader = useParentClassloader;
}

public boolean isInWebContainer() {
return inWebContainer;
}

public void setInWebContainer(boolean inWebContainer) {
this.inWebContainer = inWebContainer;
}

@Override
public void validate() throws ConfigurationException {
if ((port <= 0) || (port > Short.MAX_VALUE)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2022 Oracle and/or its affiliates.
* Copyright (c) 2018, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,7 +17,6 @@
package io.helidon.microprofile.arquillian;

import java.lang.reflect.Method;
import java.util.Optional;

import jakarta.enterprise.context.control.RequestContextController;
import jakarta.enterprise.context.spi.CreationalContext;
Expand All @@ -29,6 +28,7 @@
import org.jboss.arquillian.core.spi.LoadableExtension;
import org.jboss.arquillian.test.spi.TestEnricher;
import org.jboss.arquillian.testenricher.cdi.CDIInjectionEnricher;
import org.testng.annotations.Test;

/**
* An arquillian LoadableExtension defining the {@link HelidonDeployableContainer}.
Expand All @@ -44,6 +44,8 @@ class HelidonContainerExtension implements LoadableExtension {
*/
static class HelidonCDIInjectionEnricher extends CDIInjectionEnricher {

private static final String ARQUILLIAN_DATA_PROVIDER = "ARQUILLIAN_DATA_PROVIDER";
private static final Object[] EMPTY = new Object[0];
private BeanManager beanManager;
private RequestContextController requestContextController;

Expand Down Expand Up @@ -78,11 +80,11 @@ public RequestContextController getRequestContextController() {

@Override
public Object[] resolve(Method method) {
return Optional.ofNullable(method.getAnnotation(org.testng.annotations.Test.class))
.filter(test -> !test.dataProvider().isEmpty())
// Don't resolve TestNG data providers parameters as cdi beans
.map(unused -> new Object[0])
.orElseGet(() -> super.resolve(method));
Test test = method.getAnnotation(org.testng.annotations.Test.class);
if (test != null && !ARQUILLIAN_DATA_PROVIDER.equals(test.dataProvider())) {
return EMPTY;
}
return super.resolve(method);
}

private static CDI<Object> cdi() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
Expand All @@ -45,12 +47,17 @@
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import io.helidon.config.mp.MpConfigSources;

import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.enterprise.inject.spi.DefinitionException;
import jakarta.enterprise.util.AnnotationLiteral;
import jakarta.ws.rs.core.Application;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.config.spi.ConfigBuilder;
Expand All @@ -64,9 +71,13 @@
import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.ArchivePaths;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
* Implementation of DeployableContainer for launching Helidon microprofile server.
Expand All @@ -77,6 +88,7 @@
* <li>A temporary directory is created</li>
* <li>The WebArchive contents are written to the temporary directory</li>
* <li>beans.xml is created in WEB-INF/classes if not present</li>
* <li>WEB-INF/beans.xml will be moved to WEB-INF/classes/META-INF if present</li>
* <li>The server is started with WEB-INF/classes and all libraries in WEB-INF/libon the classpath.</li>
* </ol>
*
Expand Down Expand Up @@ -180,19 +192,26 @@ public ProtocolMetaData deploy(Archive<?> archive) throws DeploymentException {

Path rootDir = context.deployDir.resolve("");
if (isJavaArchive) {
ensureBeansXml(rootDir);
ensureBeansXml(rootDir, null);
classPath.add(rootDir);
} else {
// Prepare the launcher files
Path webInfDir = context.deployDir.resolve("WEB-INF");
Path classesDir = webInfDir.resolve("classes");
Path libDir = webInfDir.resolve("lib");
ensureBeansXml(classesDir);
ensureBeansXml(classesDir, webInfDir);
addServerClasspath(classPath, classesDir, libDir, rootDir);
if (containerConfig.isInWebContainer()) {
context.rootContext = archive.getName().split("\\.")[0];
if (!loadApplicationFromWebXml(context, webInfDir)) {
// Search Application in classes
loadApplicationFromClasses(context, archive);
}
}
}

startServer(context, classPath.toArray(new Path[0]));
} catch (IOException e) {
} catch (IOException | SAXException | ParserConfigurationException e) {
LOGGER.log(Level.INFO, "Failed to start container", e);
throw new DeploymentException("Failed to copy the archive assets into the deployment directory", e);
} catch (InvocationTargetException e) {
Expand Down Expand Up @@ -221,6 +240,78 @@ public ProtocolMetaData deploy(Archive<?> archive) throws DeploymentException {
return new ProtocolMetaData();
}

private boolean loadApplicationFromClasses(RunContext context, Archive<?> archive)
throws ClassNotFoundException, ReflectiveOperationException {
ArchivePath classes = ArchivePaths.create("WEB-INF", "classes");
org.jboss.shrinkwrap.api.Node root = archive.getContent().get(classes);
Collection<Class<Application>> applications = new HashSet<>();
if (root != null) {
deepApplicationFind(root, applications);
context.applications.addAll(applications);
}
return !context.applications.isEmpty();
}

private void deepApplicationFind(org.jboss.shrinkwrap.api.Node parent,
Collection<Class<Application>> applications) throws ClassNotFoundException {
for (org.jboss.shrinkwrap.api.Node child : parent.getChildren()) {
if (child.getChildren().isEmpty()) {
String name = child.toString();
if (name.endsWith(".class")) {
name = name.replaceFirst("\\.class", "").replaceFirst("/WEB-INF/classes/", "").replaceAll("/", ".");
Class<?> clazz = Class.forName(name);
if (Application.class.isAssignableFrom(clazz)) {
applications.add((Class<Application>) clazz);
}
}
} else {
deepApplicationFind(child, applications);
}
}
}

private boolean loadApplicationFromWebXml(RunContext context, Path webInfDir) throws IOException,
ParserConfigurationException, SAXException, ReflectiveOperationException {
Path webXml = webInfDir.resolve("web.xml");
if (Files.exists(webXml)) {
try (InputStream inputStream = Files.newInputStream(webXml)) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(inputStream);
NodeList nodes = doc.getElementsByTagName("init-param");
for (int i = 0; i < nodes.getLength(); i++) {
NodeList childs = nodes.item(i).getChildNodes();
Class<Application> application = application(childs);
if (application != null) {
context.applications.add(application);
return true;
}
}
}
}
return false;
}

private Class<Application> application(NodeList childs) throws ClassNotFoundException {
boolean isApp = false;
String appName = null;
for (int j = 0; j < childs.getLength(); j++) {
org.w3c.dom.Node element = childs.item(j);
String name = element.getNodeName();
String value = element.getTextContent();
if ("param-name".equals(name) && "jakarta.ws.rs.Application".equals(value)) {
isApp = true;
} else if ("param-value".equals(name)) {
appName = value;
}
}
if (isApp) {
return (Class<Application>) Class.forName(appName);
} else {
return null;
}
}

static Optional<Exception> lookForSupressedDeploymentException(Throwable t) {
if (t == null) {
return Optional.empty();
Expand Down Expand Up @@ -290,6 +381,18 @@ void startServer(RunContext context, Path[] classPath)
// META-INF/microprofile-config.properties (such as JWT-Auth)
ConfigBuilder builder = ConfigProviderResolver.instance()
.getBuilder();
// Add root context path per Application
if (context.rootContext != null && !context.applications.isEmpty()) {
containerConfig.addConfigBuilderConsumer(configBuilder -> {
Map<String, String> properties = new HashMap<>();
for (Class<Application> app : context.applications) {
String key = app.getName() + ".routing-path.path";
String value = "/" + context.rootContext;
properties.put(key, value);
}
configBuilder.withSources(MpConfigSources.create(properties));
});
}
// we must use the default configuration to support profiles (and test them correctly in config TCK)
// we may need to have a custom configuration for TCKs that do require workarounds
/*
Expand Down Expand Up @@ -376,17 +479,24 @@ void addServerClasspath(List<Path> classpath, Path classesDir, Path libDir, Path
classpath.add(rootDir);
}

private void ensureBeansXml(Path classesDir) throws IOException {
private void ensureBeansXml(Path classesDir, Path webinfDir) throws IOException {
Path beansPath = classesDir.resolve("META-INF/beans.xml");
Path metaInfPath = beansPath.getParent();
if (null != metaInfPath) {
Files.createDirectories(metaInfPath);
}
if (containerConfig.isInWebContainer() && webinfDir != null) {
// In case exists WEB-INF/beans.xml, then move it to classes/META-INF/beans.xml
Path webInfBeansPath = webinfDir.resolve("beans.xml");
if (Files.exists(webInfBeansPath)) {
Files.move(webInfBeansPath, beansPath);
return;
}
}
if (Files.exists(beansPath)) {
return;
}
try (InputStream beanXmlTemplate = HelidonDeployableContainer.class.getResourceAsStream("/templates/beans.xml")) {
Path metaInfPath = beansPath.getParent();
if (null != metaInfPath) {
Files.createDirectories(metaInfPath);
}

if (null == beanXmlTemplate) {
Files.write(beansPath, new byte[0]);
} else {
Expand Down Expand Up @@ -531,6 +641,8 @@ private static class RunContext {
private Object runner;
// existing class loader
private ClassLoader oldClassLoader;
private String rootContext;
private Set<Class<Application>> applications = new HashSet<>();
}

static class HelidonContainerClassloader extends ClassLoader implements Closeable {
Expand Down Expand Up @@ -576,8 +688,10 @@ public Enumeration<URL> getResources(String name) throws IOException {
}
}
}

return Collections.enumeration(result);
// Give priority to WebApp resources (for example ServiceLoader provided by WebApp)
List<URL> toRevert = new ArrayList<URL>(result);
Collections.reverse(toRevert);
return Collections.enumeration(toRevert);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Copyright (c) 2018, 2022 Oracle and/or its affiliates.
Copyright (c) 2018, 2023 Oracle and/or its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -19,8 +19,8 @@
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/beans_3_0.xsd"
version="3.0"
https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd"
version="4.0"
bean-discovery-mode="all">
</beans>

Loading