From 5664e7739d35b5267bf147e5803493723c5437e2 Mon Sep 17 00:00:00 2001 From: Arjan Tijms Date: Fri, 17 Jan 2025 22:29:10 +0100 Subject: [PATCH] Give transactions source its own parent Do away with dependency on the EJB TCK and clean pom. --- tcks/apis/transactions/pom.xml | 194 +++++--- .../ts/tests/ejb30/common/helper/Helper.java | 207 +++++++++ .../ejb30/common/lite/EJBLiteClientBase.java | 433 +++++++++++++++++ .../common/lite/EJBLiteJsfClientBase.java | 437 ++++++++++++++++++ .../tests/ejb30/common/lite/NumberEnum.java | 54 +++ .../ts/tests/ejb30/common/lite/NumberIF.java | 29 ++ 6 files changed, 1302 insertions(+), 52 deletions(-) create mode 100644 tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/helper/Helper.java create mode 100644 tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/EJBLiteClientBase.java create mode 100644 tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/EJBLiteJsfClientBase.java create mode 100644 tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/NumberEnum.java create mode 100644 tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/NumberIF.java diff --git a/tcks/apis/transactions/pom.xml b/tcks/apis/transactions/pom.xml index b91b9e16a3..5b17b869d5 100644 --- a/tcks/apis/transactions/pom.xml +++ b/tcks/apis/transactions/pom.xml @@ -1,8 +1,7 @@ - + 4.0.0 - jakarta.tck + org.eclipse.ee4j project - 11.0.0-SNAPSHOT - ../../../pom.xml + 1.0.9 + - jta-tck - jar + jakarta.tck + transactions-tck + 2.0.0-SNAPSHOT Jakarta Transactions Jakarta Transactions - 1.9.1.Final - 11.0.0-M2 - 5.11.3 + UTF-8 + UTF-8 + + 11.0.0-M2 + 11.0.0-M1 @@ -44,7 +46,14 @@ org.junit junit-bom - ${junit.jupiter.version} + 5.11.4 + pom + import + + + org.jboss.arquillian + arquillian-bom + 1.9.3.Final pom import @@ -52,93 +61,174 @@ - - ${project.groupId} - libutil - - - jakarta.tck - common - ${project.version} - - - ${project.groupId} - ejb30 - 4.0.0-SNAPSHOT - + jakarta.transaction jakarta.transaction-api + 2.0.1 + + + jakarta.enterprise + jakarta.enterprise.cdi-api + 4.1.0 jakarta.inject jakarta.inject-api + 2.0.1.MR + + + jakarta.annotation + jakarta.annotation-api + 3.0.0 jakarta.ejb jakarta.ejb-api + 4.0.1 + - jakarta.annotation - jakarta.annotation-api + jakarta.servlet + jakarta.servlet-api + 6.1.0 - jakarta.enterprise - jakarta.enterprise.cdi-api + jakarta.servlet.jsp + jakarta.servlet.jsp-api + 4.0.0 - jakarta.tck.arquillian - arquillian-protocol-appclient - ${arquillian.jakarta.tck.version} + jakarta.faces + jakarta.faces-api + 4.1.2 + + - jakarta.tck.arquillian - arquillian-protocol-common - ${arquillian.jakarta.tck.version} + jakarta.tck + libutil + ${jakarta.tck.tools.version} - jakarta.tck.arquillian - arquillian-protocol-javatest - ${arquillian.jakarta.tck.version} + jakarta.tck + common + ${jakarta.tck.tools.version} + jakarta.tck.arquillian - arquillian-protocol-lib - ${arquillian.jakarta.tck.version} + arquillian-protocol-common + ${jakarta.tck.arquillian.version} jakarta.tck.arquillian tck-porting-lib - ${arquillian.jakarta.tck.version} + ${jakarta.tck.arquillian.version} + + org.junit.jupiter - junit-jupiter - ${junit.jupiter.version} + junit-jupiter-api - org.jboss.arquillian.junit - arquillian-junit-core - ${arquillian.junit} + org.jboss.arquillian.test + arquillian-test-api - org.jboss.arquillian.junit5 - arquillian-junit5-container + org.jboss.arquillian.container + arquillian-container-test-api org.jboss.arquillian.junit5 arquillian-junit5-core + + org.jboss.shrinkwrap + shrinkwrap-api + 1.2.6 + - + + + maven-enforcer-plugin + + + enforce-maven + + enforce + + + + + 3.8.6 + + + + + + + + + maven-compiler-plugin + 3.13.0 + + 17 + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + + + maven-javadoc-plugin + + none + + + + - maven-deploy-plugin + org.codehaus.mojo + flatten-maven-plugin + 1.6.0 - true + ossrh + + + + flatten + process-resources + + flatten + + + + + flatten.clean + clean + + clean + + + diff --git a/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/helper/Helper.java b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/helper/Helper.java new file mode 100644 index 0000000000..8be8b7a386 --- /dev/null +++ b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/helper/Helper.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2007, 2018 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/* + * $Id$ + */ +package com.sun.ts.tests.ejb30.common.helper; + +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sun.ts.lib.util.TestUtil; + +public final class Helper { + private static Logger logger = Logger.getLogger("com.sun.ts.tests.ejb30"); + + private Helper() { + } + + public static Logger getLogger() { + return logger; + } + + public static String compareResultList(final List expected, + final List actual) { + String reason; + if (expected.equals(actual)) { + reason = "Got expected result list: " + expected; + logger.info(reason); + } else { + reason = TestUtil.NEW_LINE + "Expecting result list: " + expected + + TestUtil.NEW_LINE + " , but actual: " + actual; + throw new RuntimeException(reason); + } + return reason; + } + + public static String compareResultList(final String[] expected, + final List actual) { + return compareResultList(Arrays.asList(expected), actual); + } + + /** + * Similar to JUnit Assert, but this class appends message to both true and + * false outcome. This class also makes use of autoboxing to handle + * primitives. + * + * @param messagePrefix + * @param expected + * @param actual + * @return a String showing a true outcome + * @throws java.lang.RuntimeException + * if comparison is false + */ + public static String assertEquals(final String messagePrefix, + final Object expected, final Object actual) throws RuntimeException { + final StringBuilder sb = new StringBuilder(); + assertEquals(messagePrefix, expected, actual, sb); + return sb.toString(); + } + + public static void assertEquals(final String messagePrefix, + final Object expected, final Object actual, final StringBuilder sb) + throws RuntimeException { + sb.append(TestUtil.NEW_LINE); + if (messagePrefix != null) { + sb.append(TestUtil.NEW_LINE).append(messagePrefix).append(" "); + } + if (equalsOrNot(expected, actual)) { + sb.append("Got the expected result:").append(actual).append("\t"); + } else { + sb.append("Expecting ").append(expected).append(", but actual ") + .append(actual); + throw new RuntimeException(sb.toString()); + } + } + + public static String assertNotEquals(final String messagePrefix, + final Object expected, final Object actual) throws RuntimeException { + final StringBuilder sb = new StringBuilder(); + assertNotEquals(messagePrefix, expected, actual, sb); + return sb.toString(); + } + + public static void assertNotEquals(final String messagePrefix, + final Object expected, final Object actual, final StringBuilder sb) + throws RuntimeException { + sb.append(TestUtil.NEW_LINE); + if (messagePrefix != null) { + sb.append(messagePrefix).append(" "); + } + if (!equalsOrNot(expected, actual)) { + sb.append("Got the expected NotEquals result. compareTo:") + .append(expected).append(", actual:").append(actual).append("\t"); + } else { + sb.append("Expecting NotEquals, but got equals. compareTo:") + .append(expected).append(", and actual: ").append(actual); + throw new RuntimeException(sb.toString()); + } + } + + public static void assertCloseEnough(final String messagePrefix, + long expected, long actual, long ignoreableDiff, StringBuilder sb) { + sb.append(TestUtil.NEW_LINE); + if (messagePrefix != null) { + sb.append(messagePrefix).append(" "); + } + long dif = Math.abs(actual - expected); + if (dif <= ignoreableDiff) { + sb.append("Got the expected result:"); + sb.append("the diff between expected " + expected + ", and actual " + + actual + " is " + dif + ", equals or less than the ignoreableDiff " + + ignoreableDiff); + } else { + throw new RuntimeException("the diff between expected " + expected + + ", and actual " + actual + " is " + dif + + ", greater than the ignoreableDiff " + ignoreableDiff); + } + } + + public static String assertGreaterThan(final String messagePrefix, long arg1, + long arg2) throws RuntimeException { + final StringBuilder sb = new StringBuilder(); + assertGreaterThan(messagePrefix, arg1, arg2, sb); + return sb.toString(); + } + + public static void assertGreaterThan(final String messagePrefix, long arg1, + long arg2, final StringBuilder sb) throws RuntimeException { + sb.append(TestUtil.NEW_LINE); + if (messagePrefix != null) { + sb.append(messagePrefix).append(" "); + } + if (arg1 > arg2) { + sb.append("Got the expected result:").append(arg1).append(">") + .append(arg2).append("\t"); + } else { + sb.append("Expecting ").append(arg1).append(">").append(arg2) + .append(", but failed"); + throw new RuntimeException(sb.toString()); + } + } + + public static void busyWait(long millis) { + if (logger.isLoggable(Level.FINER)) { + logger.finer("Begin busy wait " + millis + " milliseconds"); + } + long end = System.currentTimeMillis() + millis; + do { + } while (System.currentTimeMillis() < end); + if (logger.isLoggable(Level.FINER)) { + logger.finer("Finished busy wait " + millis + " milliseconds"); + } + } + + public static void preDestroy(Object obj) { + logger.info("In PreDestroy of " + obj); + } + + public static void main(String[] args) { + System.out.println(assertEquals("compare strings", "a", "a")); + System.out.println(assertEquals("compare primitives", 100, 100)); + int i = 0; + int j = 0; + System.out.println(assertEquals("compare declared primitives", i, j)); + + StringBuilder sb = new StringBuilder(); + sb.append("Check the following: "); + assertEquals("String:", "a", "a", sb); + assertEquals("int:", 5, 5, sb); + assertEquals("double:", 5.5, 5.5, sb); + assertEquals("long:", 9999L, 9999L, sb); + // compare different String objects + assertEquals("Object:", new String("st"), new String("st"), sb); + System.out.println(sb.toString()); + + busyWait(4000); + } + + private static boolean equalsOrNot(final Object expected, + final Object actual) { + boolean sameOrNot = false; + if (expected == null) { + if (actual == null) { + sameOrNot = true; + } + } else { + sameOrNot = expected.equals(actual); + } + return sameOrNot; + } +} diff --git a/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/EJBLiteClientBase.java b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/EJBLiteClientBase.java new file mode 100644 index 0000000000..639a301f3f --- /dev/null +++ b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/EJBLiteClientBase.java @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2008, 2020 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/* + * $Id$ + */ +package com.sun.ts.tests.ejb30.common.lite; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.naming.NamingException; + +import com.sun.ts.lib.harness.ServiceEETest; +import com.sun.ts.lib.util.TestUtil; +import com.sun.ts.tests.common.vehicle.ejbliteshare.EJBLiteClientIF; +import com.sun.ts.tests.ejb30.common.helper.Helper; + +import jakarta.ejb.EJBException; +import jakarta.ejb.embeddable.EJBContainer; + +public class EJBLiteClientBase extends ServiceEETest + implements EJBLiteClientIF { + + ///////////////////////////////////////////////////////////////////// + // properties that may communicate with runtime environment (vehicles) and + // may be configured as managed-property + ///////////////////////////////////////////////////////////////////// + /** + * status and reason indicate test status and may be configured as + * managed-property. They must be updated by subclass test clients, typically + * indirectly via invoking pass or fail method. + */ + private String status; + + private String reason; + + /** + * the current testName (without _from_ suffix). When running in + * vehicles, this field is initialized either by container (ejblitejsf + * vehicle) or by runner class. When running as standalone client, need to + * initialize it in setup method + */ + private String testName; + + /** + * If annotation injections on this class are supported in the runtime + * environment. For example, in a servlet vehicle, this class will be + * reflectively instantiated by the test app, and so any annotations will be + * ignored. + */ + private Boolean injectionSupported; + + /** + * used in portable global jndi name. + */ + private String moduleName; + + private EJBContainer container; + + private javax.naming.Context context; + + /** + * Maps the lookup name of EJB injections to their portable global jndi names. + */ + private Map jndiMapping = new HashMap(); + + /** + * Additional embeddable ejb modules that are outside current classpath. Its + * getter method (protected) is intended for subclass to retrieve it. Its + * setter method is declared in EJBLiteClientIF interface. + */ + private File[] additionalModules; + + ///////////////////////////////////////////////////////////////////// + // internal fields + ///////////////////////////////////////////////////////////////////// + /** a StringBuilder for reason property */ + private StringBuilder reasonBuffer; + + /** + * number of times this class has been used to run test. When used as vehicle, + * instances of this class (actually its concrete subclass) are either not + * shared across multiple tests (e.g. ejblitejsf, ejblitejsp vehicles), or + * internal state must be reset (e.g., ejbliteservlet vehicle). This field + * must equal either 0 or 1. + */ + private byte accessCount; + + /** + * indicates this class is now inside a vehicle, e.g., jsf managed bean, + * servlet, servlet filter, etc. This field is not initialized util + * this.runTestInVehicle() is called. + */ + private boolean inVehicle; + + ///////////////////////////////////////////////////////////////////// + // property accessors + ///////////////////////////////////////////////////////////////////// + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } + + /** + * Typically invoked by containers or vehicles to run the actual test method. + * This method must not be invoked more than once for each test. Subclass test + * client must not invoke this method. + */ + public String getStatus() { + if (accessCount == 0) { + runTestInVehicle(); + } else { + passOrFail(false, + "This class has been incorrectly shared across multiple tests: ", + this.toString(), ", timesCalled=", String.valueOf(accessCount)); + } + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + /** + * Similar to getStatus() method, and subclass test client should not invoke + * it. However, this method is used by both standalone and vehicle clients. + */ + public String getReason() { + if (reason == null) { + if (reasonBuffer == null) { + reason = "WARNING: both reason and reasonBuffer is null."; + } else { + reason = reasonBuffer.toString(); + } + } + return this.reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public String getModuleName() { + return moduleName; + } + + public void setModuleName(String mn) { + if (mn.startsWith("/")) { + this.moduleName = mn.substring(1); + } else { + this.moduleName = mn; + } + } + + public EJBContainer getContainer() { + return container; + } + + public void setContainer(EJBContainer container) { + this.container = container; + } + + public javax.naming.Context getContext() { + if (context != null) { + return context; + } + javax.naming.Context ctx = null; + try { + ctx = new javax.naming.InitialContext(); + } catch (NamingException e) { + throw new RuntimeException(e); + } + return ctx; + } + + public void setContext(javax.naming.Context context) { + this.context = context; + } + + ///////////////////////////////////////////////////////////////////// + // harness-required methods + ///////////////////////////////////////////////////////////////////// + /** + * setup method is invoked by harness prior to each test method. If + * getInjectionSupported() returns false, need to initialize those injected + * fields here. When running inside vehicles, setup(null, null) are called + * (see runTestInVehicle method) + */ + public void setup(String[] args, Properties p) { + if (!inVehicle && (testName == null) && p != null) { + String s = p.getProperty("testName"); // strip off _from_ + testName = s.substring(0, s.indexOf("_from_")); + String m = p.getProperty("vehicle_archive_name"); + setModuleName(m); + Helper.getLogger() + .fine("testName from test properties: " + s + + ", setting testName field to the short name: " + testName + + ". set moduleName to vehicle_archive_name property: " + m); + } + } + + /** + * cleanup method is invoked by harness after each test method. For vehicles, + * this methos is invoked only by servlet filter. + */ + public void cleanup() { + if (!inVehicle) { + Helper.getLogger() + .info("The following events occurred before test passed or failed: " + + getReason()); + } + nuke(); + } + + ///////////////////////////////////////////////////////////////////// + // EJBLiteClientIF methods + ///////////////////////////////////////////////////////////////////// + public void runTestInVehicle() { + inVehicle = true; + try { + setup(null, null); + Method testMethod = getClass().getMethod(testName); + testMethod.invoke(this, (Object[]) null); + passOrFail(true); + } catch (NoSuchMethodException e) { + passOrFail(false, "Failed to get test method " + testName, e.toString()); + } catch (IllegalAccessException e) { + passOrFail(false, + "Failed to access test method (is it public?) " + testName, + e.toString()); + } catch (InvocationTargetException e) { + // If it's EJBException with a cause, just include the cause in the + // message + String msg = "Failed with exception "; + Throwable root = e.getCause(); + if (root == null) { + root = e; + } else if (root instanceof EJBException) { + Throwable cause2 = root.getCause(); + if (cause2 != null) { + root = cause2; + msg = "Failed with EJBException caused by "; + } + } + passOrFail(false, msg, TestUtil.printStackTraceToString(root)); + } catch (Exception e) { + passOrFail(false, "Unexpected exception for test " + testName, + TestUtil.printStackTraceToString(e)); + } // finally { skip cleanup + } + + /** + * When running inside a vehicle, the vehicle runner class should call + * setInjectionSupported(true|false) to explicitly initialize it. Its default + * value is null. For ejblitejsf vehicle, it is set in faces-config.xml as a + * managed-property. For standalone client, injectionSupported field is not + * initialized. But if ejb spec requires injection support, we need to + * initialize it from a property in ts.jte. + */ + public Boolean getInjectionSupported() { + return injectionSupported; + } + + public void setInjectionSupported(Boolean injectionSupported) { + this.injectionSupported = injectionSupported; + } + + public Map getJndiMapping() { + return jndiMapping; + } + + public void setContextClassLoader() { + } + + public Map getContainerInitProperties() { + return null; + } + + public void setAdditionalModules(File[] additionalModules) { + this.additionalModules = additionalModules; + } + + ///////////////////////////////////////////////////////////////////// + // convenience methods + ///////////////////////////////////////////////////////////////////// + + /** + * In embeddable usage, only the portable global jndi name lookup is + * supported. In JavaEE or web environment, the following lookupName format + * can be used: + * + * x will be expanded to java:comp/env/x java:comp/env/x + * java:global/app-name/module-name/bean-name!FQN + * java:app/module-name/bean-name!FQN java:module/bean-name!FQN + * + * beanName and beanInterface is to be used in embed mode to create a + * java:global name + */ + protected Object lookup(String lookupName, String beanName, + Class beanInterface) { + String nameNormalized = lookupName; + + if (container == null) { + // in JavaEE or Web environment + if (!lookupName.startsWith("java:")) { + nameNormalized = JAVA_COMP_ENV_PREFIX + lookupName; + } + } else { + // embaddable usage, only look up with portable global jndi name + if (!lookupName.startsWith(JAVA_GLOBAL_PREFIX)) { + if (beanName == null) { + // type-level @EJB injections are stored in jndiMapping + String s = getJndiMapping().get(lookupName); + if (s == null) { + s = getJndiMapping().get(JAVA_COMP_ENV_PREFIX + lookupName); + } + + if (s == null) { + throw new RuntimeException(String.format( + "Lookup name (%s) does not start with %s, beanName is %s, and not in jndiMapping %s", + lookupName, JAVA_GLOBAL_PREFIX, beanName, getJndiMapping())); + } + nameNormalized = s; + Helper.getLogger() + .info("Retrieved the global jndi name from jndiMapping: " + + lookupName + " : " + nameNormalized); + } else { + nameNormalized = JAVA_GLOBAL_PREFIX + EJBEMBED_JAR_NAME_BASE + "/" + + beanName; + if (beanInterface != null) { + nameNormalized = nameNormalized + "!" + beanInterface.getName(); + } + } + } + } + try { + return getContext().lookup(nameNormalized); + } catch (javax.naming.NamingException e) { + throw new RuntimeException(e); + } + } + + protected File[] getAdditionalModules() { + return additionalModules; + } + + protected StringBuilder getReasonBuffer() { + if (reasonBuffer == null) { + reasonBuffer = new StringBuilder(); // single-threaded access + } + return reasonBuffer; + } + + protected void appendReason(Object... oo) { + for (Object o : oo) { + getReasonBuffer().append(o).append(System.getProperty("line.separator")); + } + } + + protected void assertEquals(final String messagePrefix, final Object expected, + final Object actual) throws RuntimeException { + Helper.assertEquals(messagePrefix, expected, actual, getReasonBuffer()); + } + + protected void assertNotEquals(final String messagePrefix, + final Object expected, final Object actual) throws RuntimeException { + Helper.assertNotEquals(messagePrefix, expected, actual, getReasonBuffer()); + } + + protected void assertGreaterThan(final String messagePrefix, long arg1, + long arg2) throws RuntimeException { + Helper.assertGreaterThan(messagePrefix, arg1, arg2, getReasonBuffer()); + } + + /** + * A central place to clear up state. Note that we cannot call it in cleanup() + * since vehicle tests (at least jsf) test status is written to jsp page after + * cleanup method. We cannot call it in setup either, since in vehicle tests + * containers inject to these fields upon instantiate and we don't want to + * remove these field values. In vehicle tests this test is instantiated once + * for each test, and so it should not matter if we leave the state behind. + * When running in standalone, this test client should also be instantiated + * once per test. + */ + private void nuke() { + accessCount = 0; + injectionSupported = null; + status = null; + reason = null; + testName = null; + inVehicle = false; + reasonBuffer = null; + + moduleName = null; + container = null; + context = null; + jndiMapping = null; + additionalModules = null; + } + + private void passOrFail(boolean passOrFail, String... why) { + this.status = (passOrFail ? TEST_PASSED : TEST_FAILED) + "testName=" + + testName; + appendReason((Object[]) why); + if (reasonBuffer != null) { + this.reason = reasonBuffer.toString(); + } + this.accessCount++; + } + +} diff --git a/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/EJBLiteJsfClientBase.java b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/EJBLiteJsfClientBase.java new file mode 100644 index 0000000000..eab3f9fb1c --- /dev/null +++ b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/EJBLiteJsfClientBase.java @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package com.sun.ts.tests.ejb30.common.lite; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.naming.NamingException; + +import com.sun.ts.lib.harness.ServiceEETest; +import com.sun.ts.lib.util.TestUtil; +import com.sun.ts.tests.common.vehicle.ejbliteshare.EJBLiteClientIF; +import com.sun.ts.tests.ejb30.common.helper.Helper; + +import jakarta.ejb.EJBException; +import jakarta.ejb.embeddable.EJBContainer; + +public class EJBLiteJsfClientBase extends ServiceEETest + implements EJBLiteClientIF { + + ///////////////////////////////////////////////////////////////////// + // properties that may communicate with runtime environment (vehicles) and + // may be configured as managed-property + ///////////////////////////////////////////////////////////////////// + /** + * status and reason indicate test status and may be configured as + * managed-property. They must be updated by subclass test clients, typically + * indirectly via invoking pass or fail method. + */ + private String status; + + private String reason; + + /** + * the current testName (without _from_ suffix). When running in + * vehicles, this field is initialized either by container (ejblitejsf + * vehicle) or by runner class. When running as standalone client, need to + * initialize it in setup method + */ + @jakarta.inject.Inject + @jakarta.faces.annotation.ManagedProperty("#{param.testName}") + private String testName; + + /** + * If annotation injections on this class are supported in the runtime + * environment. For example, in a servlet vehicle, this class will be + * reflectively instantiated by the test app, and so any annotations will be + * ignored. + */ + private Boolean injectionSupported; + + /** + * used in portable global jndi name. + */ + @jakarta.inject.Inject + @jakarta.faces.annotation.ManagedProperty("#{facesContext.externalContext.requestContextPath}") + private String moduleName; + + private EJBContainer container; + + private javax.naming.Context context; + + /** + * Maps the lookup name of EJB injections to their portable global jndi names. + */ + private Map jndiMapping = new HashMap(); + + /** + * Additional embeddable ejb modules that are outside current classpath. Its + * getter method (protected) is intended for subclass to retrieve it. Its + * setter method is declared in EJBLiteClientIF interface. + */ + private File[] additionalModules; + + ///////////////////////////////////////////////////////////////////// + // internal fields + ///////////////////////////////////////////////////////////////////// + /** a StringBuilder for reason property */ + private StringBuilder reasonBuffer; + + /** + * number of times this class has been used to run test. When used as vehicle, + * instances of this class (actually its concrete subclass) are either not + * shared across multiple tests (e.g. ejblitejsf, ejblitejsp vehicles), or + * internal state must be reset (e.g., ejbliteservlet vehicle). This field + * must equal either 0 or 1. + */ + private byte accessCount; + + /** + * indicates this class is now inside a vehicle, e.g., jsf managed bean, + * servlet, servlet filter, etc. This field is not initialized util + * this.runTestInVehicle() is called. + */ + private boolean inVehicle; + + ///////////////////////////////////////////////////////////////////// + // property accessors + ///////////////////////////////////////////////////////////////////// + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } + + /** + * Typically invoked by containers or vehicles to run the actual test method. + * This method must not be invoked more than once for each test. Subclass test + * client must not invoke this method. + */ + public String getStatus() { + if (accessCount == 0) { + runTestInVehicle(); + } else { + passOrFail(false, + "This class has been incorrectly shared across multiple tests: ", + this.toString(), ", timesCalled=", String.valueOf(accessCount)); + } + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + /** + * Similar to getStatus() method, and subclass test client should not invoke + * it. However, this method is used by both standalone and vehicle clients. + */ + public String getReason() { + if (reason == null) { + if (reasonBuffer == null) { + reason = "WARNING: both reason and reasonBuffer is null."; + } else { + reason = reasonBuffer.toString(); + } + } + return this.reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public String getModuleName() { + if (moduleName.startsWith("/")) { + return moduleName.substring(1); + } + return moduleName; + } + + public void setModuleName(String mn) { + if (mn.startsWith("/")) { + this.moduleName = mn.substring(1); + } else { + this.moduleName = mn; + } + } + + public EJBContainer getContainer() { + return container; + } + + public void setContainer(EJBContainer container) { + this.container = container; + } + + public javax.naming.Context getContext() { + if (context != null) { + return context; + } + javax.naming.Context ctx = null; + try { + ctx = new javax.naming.InitialContext(); + } catch (NamingException e) { + throw new RuntimeException(e); + } + return ctx; + } + + public void setContext(javax.naming.Context context) { + this.context = context; + } + + ///////////////////////////////////////////////////////////////////// + // harness-required methods + ///////////////////////////////////////////////////////////////////// + /** + * setup method is invoked by harness prior to each test method. If + * getInjectionSupported() returns false, need to initialize those injected + * fields here. When running inside vehicles, setup(null, null) are called + * (see runTestInVehicle method) + */ + public void setup(String[] args, Properties p) { + if (!inVehicle && (testName == null) && p != null) { + String s = p.getProperty("testName"); // strip off _from_ + testName = s.substring(0, s.indexOf("_from_")); + String m = p.getProperty("vehicle_archive_name"); + setModuleName(m); + Helper.getLogger() + .fine("testName from test properties: " + s + + ", setting testName field to the short name: " + testName + + ". set moduleName to vehicle_archive_name property: " + m); + } + } + + /** + * cleanup method is invoked by harness after each test method. For vehicles, + * this methos is invoked only by servlet filter. + */ + public void cleanup() { + if (!inVehicle) { + Helper.getLogger() + .info("The following events occurred before test passed or failed: " + + getReason()); + } + nuke(); + } + + ///////////////////////////////////////////////////////////////////// + // EJBLiteClientIF methods + ///////////////////////////////////////////////////////////////////// + public void runTestInVehicle() { + inVehicle = true; + try { + setup(null, null); + Method testMethod = getClass().getMethod(testName); + testMethod.invoke(this, (Object[]) null); + passOrFail(true); + } catch (NoSuchMethodException e) { + passOrFail(false, "Failed to get test method " + testName, e.toString()); + } catch (IllegalAccessException e) { + passOrFail(false, + "Failed to access test method (is it public?) " + testName, + e.toString()); + } catch (InvocationTargetException e) { + // If it's EJBException with a cause, just include the cause in the + // message + String msg = "Failed with exception "; + Throwable root = e.getCause(); + if (root == null) { + root = e; + } else if (root instanceof EJBException) { + Throwable cause2 = root.getCause(); + if (cause2 != null) { + root = cause2; + msg = "Failed with EJBException caused by "; + } + } + passOrFail(false, msg, TestUtil.printStackTraceToString(root)); + } catch (Exception e) { + passOrFail(false, "Unexpected exception for test " + testName, + TestUtil.printStackTraceToString(e)); + } // finally { skip cleanup + } + + /** + * When running inside a vehicle, the vehicle runner class should call + * setInjectionSupported(true|false) to explicitly initialize it. Its default + * value is null. For ejblitejsf vehicle, it is set in faces-config.xml as a + * managed-property. For standalone client, injectionSupported field is not + * initialized. But if ejb spec requires injection support, we need to + * initialize it from a property in ts.jte. + */ + public Boolean getInjectionSupported() { + return injectionSupported; + } + + public void setInjectionSupported(Boolean injectionSupported) { + this.injectionSupported = injectionSupported; + } + + public Map getJndiMapping() { + return jndiMapping; + } + + public void setContextClassLoader() { + } + + public Map getContainerInitProperties() { + return null; + } + + public void setAdditionalModules(File[] additionalModules) { + this.additionalModules = additionalModules; + } + + ///////////////////////////////////////////////////////////////////// + // convenience methods + ///////////////////////////////////////////////////////////////////// + + /** + * In embeddable usage, only the portable global jndi name lookup is + * supported. In JavaEE or web environment, the following lookupName format + * can be used: + * + * x will be expanded to java:comp/env/x java:comp/env/x + * java:global/app-name/module-name/bean-name!FQN + * java:app/module-name/bean-name!FQN java:module/bean-name!FQN + * + * beanName and beanInterface is to be used in embed mode to create a + * java:global name + */ + protected Object lookup(String lookupName, String beanName, + Class beanInterface) { + String nameNormalized = lookupName; + + if (container == null) { + // in JavaEE or Web environment + if (!lookupName.startsWith("java:")) { + nameNormalized = JAVA_COMP_ENV_PREFIX + lookupName; + } + } else { + // embaddable usage, only look up with portable global jndi name + if (!lookupName.startsWith(JAVA_GLOBAL_PREFIX)) { + if (beanName == null) { + // type-level @EJB injections are stored in jndiMapping + String s = getJndiMapping().get(lookupName); + if (s == null) { + s = getJndiMapping().get(JAVA_COMP_ENV_PREFIX + lookupName); + } + + if (s == null) { + throw new RuntimeException(String.format( + "Lookup name (%s) does not start with %s, beanName is %s, and not in jndiMapping %s", + lookupName, JAVA_GLOBAL_PREFIX, beanName, getJndiMapping())); + } + nameNormalized = s; + Helper.getLogger() + .info("Retrieved the global jndi name from jndiMapping: " + + lookupName + " : " + nameNormalized); + } else { + nameNormalized = JAVA_GLOBAL_PREFIX + EJBEMBED_JAR_NAME_BASE + "/" + + beanName; + if (beanInterface != null) { + nameNormalized = nameNormalized + "!" + beanInterface.getName(); + } + } + } + } + try { + return getContext().lookup(nameNormalized); + } catch (javax.naming.NamingException e) { + throw new RuntimeException(e); + } + } + + protected File[] getAdditionalModules() { + return additionalModules; + } + + protected StringBuilder getReasonBuffer() { + if (reasonBuffer == null) { + reasonBuffer = new StringBuilder(); // single-threaded access + } + return reasonBuffer; + } + + protected void appendReason(Object... oo) { + for (Object o : oo) { + getReasonBuffer().append(o).append(System.getProperty("line.separator")); + } + } + + protected void assertEquals(final String messagePrefix, final Object expected, + final Object actual) throws RuntimeException { + Helper.assertEquals(messagePrefix, expected, actual, getReasonBuffer()); + } + + protected void assertNotEquals(final String messagePrefix, + final Object expected, final Object actual) throws RuntimeException { + Helper.assertNotEquals(messagePrefix, expected, actual, getReasonBuffer()); + } + + protected void assertGreaterThan(final String messagePrefix, long arg1, + long arg2) throws RuntimeException { + Helper.assertGreaterThan(messagePrefix, arg1, arg2, getReasonBuffer()); + } + + /** + * A central place to clear up state. Note that we cannot call it in cleanup() + * since vehicle tests (at least jsf) test status is written to jsp page after + * cleanup method. We cannot call it in setup either, since in vehicle tests + * containers inject to these fields upon instantiate and we don't want to + * remove these field values. In vehicle tests this test is instantiated once + * for each test, and so it should not matter if we leave the state behind. + * When running in standalone, this test client should also be instantiated + * once per test. + */ + private void nuke() { + accessCount = 0; + injectionSupported = null; + status = null; + reason = null; + testName = null; + inVehicle = false; + reasonBuffer = null; + + moduleName = null; + container = null; + context = null; + jndiMapping = null; + additionalModules = null; + } + + private void passOrFail(boolean passOrFail, String... why) { + this.status = (passOrFail ? TEST_PASSED : TEST_FAILED) + "testName=" + + testName; + appendReason((Object[]) why); + if (reasonBuffer != null) { + this.reason = reasonBuffer.toString(); + } + this.accessCount++; + } + +} diff --git a/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/NumberEnum.java b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/NumberEnum.java new file mode 100644 index 0000000000..5089fb49cf --- /dev/null +++ b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/NumberEnum.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2008, 2018 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/* + * $Id$ + */ +package com.sun.ts.tests.ejb30.common.lite; + +import java.util.EnumSet; + +public enum NumberEnum implements NumberIF { + + ZERO(0), ONE(1), TWO(2), THREE(3), FOUR(4), FIVE(5), OTHERS(6); + + private int number; + + private NumberEnum(int n) { + number = n; + } + + public int getNumber() { + return number; + } + + public static NumberEnum getEnumFor(int n) { + for (NumberEnum e : EnumSet.allOf(NumberEnum.class)) { + if (e.number == n) { + return e; + } + } + return OTHERS; + } + + public int add(int toAdd) { + return toAdd + number; + } + + public NumberIF add(NumberIF toAdd) { + return getEnumFor(toAdd.add(number)); + } +} diff --git a/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/NumberIF.java b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/NumberIF.java new file mode 100644 index 0000000000..262674349a --- /dev/null +++ b/tcks/apis/transactions/src/main/java/com/sun/ts/tests/ejb30/common/lite/NumberIF.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2008, 2018 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +/* + * $Id$ + */ + +package com.sun.ts.tests.ejb30.common.lite; + +public interface NumberIF extends java.io.Serializable { + public int add(int toAdd); + + public NumberIF add(NumberIF toAdd); + + public int getNumber(); +}