diff --git a/dev/com.ibm.websphere.appserver.features/visibility/protected/com.ibm.websphere.appserver.containerServices-1.0.feature b/dev/com.ibm.websphere.appserver.features/visibility/protected/com.ibm.websphere.appserver.containerServices-1.0.feature index bf33072f67e5..739d5c21e831 100644 --- a/dev/com.ibm.websphere.appserver.features/visibility/protected/com.ibm.websphere.appserver.containerServices-1.0.feature +++ b/dev/com.ibm.websphere.appserver.features/visibility/protected/com.ibm.websphere.appserver.containerServices-1.0.feature @@ -20,7 +20,7 @@ IBM-Process-Types: server, \ com.ibm.ws.javaee.version, \ com.ibm.ws.serialization -jars=com.ibm.websphere.appserver.spi.containerServices; location:=dev/spi/ibm/ --files=dev/spi/ibm/javadoc/com.ibm.websphere.appserver.spi.containerServices_4.0-javadoc.zip +-files=dev/spi/ibm/javadoc/com.ibm.websphere.appserver.spi.containerServices_4.1-javadoc.zip kind=ga edition=core WLP-Activation-Type: parallel diff --git a/dev/com.ibm.websphere.appserver.spi.containerServices/bnd.bnd b/dev/com.ibm.websphere.appserver.spi.containerServices/bnd.bnd index c48b95a54d14..61e7f6818636 100644 --- a/dev/com.ibm.websphere.appserver.spi.containerServices/bnd.bnd +++ b/dev/com.ibm.websphere.appserver.spi.containerServices/bnd.bnd @@ -1,5 +1,5 @@ #******************************************************************************* -# Copyright (c) 2017 IBM Corporation and others. +# Copyright (c) 2017, 2025 IBM Corporation and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License 2.0 # which accompanies this distribution, and is available at @@ -11,7 +11,7 @@ # IBM Corporation - initial API and implementation #******************************************************************************* -include= ~../cnf/resources/bnd/bundle.props -bVersion: 4.0 +bVersion: 4.1 Bundle-Name: WebSphere Container Services SPI Bundle-Description: WebSphere Container Services SPI, version ${bVersion} diff --git a/dev/com.ibm.ws.container.service/src/com/ibm/ws/container/service/naming/EJBLocalNamingHelper.java b/dev/com.ibm.ws.container.service/src/com/ibm/ws/container/service/naming/EJBLocalNamingHelper.java index e1494327afe0..a38b29fdf309 100644 --- a/dev/com.ibm.ws.container.service/src/com/ibm/ws/container/service/naming/EJBLocalNamingHelper.java +++ b/dev/com.ibm.ws.container.service/src/com/ibm/ws/container/service/naming/EJBLocalNamingHelper.java @@ -1,10 +1,10 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 IBM Corporation and others. + * Copyright (c) 2019, 2025 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 * * Contributors: @@ -12,8 +12,10 @@ *******************************************************************************/ package com.ibm.ws.container.service.naming; +import java.util.Collection; import java.util.List; +import javax.naming.NameClassPair; import javax.naming.NamingException; /** @@ -39,35 +41,47 @@ public interface EJBLocalNamingHelper { * If the implementer knows about the JNDI resource requested it should * return the object instance to be returned on the lookup. * - * @param name - * String form of the jndi name, excluding the namespace prefix - * e.g. for the resource "ejblocal:/.jar/#" this name - * would be "/.jar/#" + * @param name String form of the jndi name, excluding the namespace prefix + * e.g. for the resource "ejblocal:/.jar/#" this name + * would be "/.jar/#" * @return the object instance to be returned on the lookup. - * @throws NamingException - * is thrown when an implementation knows about the the JNDI - * resource, but encounters a problem obtaining an instance - * to return. + * @throws NamingException is thrown when an implementation knows about the the JNDI + * resource, but encounters a problem obtaining an instance + * to return. */ public Object getObjectInstance(String name) throws NamingException; + /** + * This method is called by JNDI during "list()" or "listBindings()" to + * provide the names and objects bound into a context. + * + * @param context Sub-context within the namespace where the list operation + * is being performed, e.g. for a list of the "ejblocal:" + * context this context would be "". Must be the empty + * string for the root context. + * @param name String form of the jndi name, excluding the namespace prefix + * and the sub-context (if any), e.g. for the resource + * "ejblocal:/.jar" this name would be + * "/.jar". "" corresponds to the context root, + * and null is not valid. + * @return a Collection of NameClassPair objects + * + * @throws NamingException is thrown if the name specified identifies an object rather than a context. + */ + public Collection listInstances(String context, String name) throws NamingException; + /** * This method adds a JNDI lookup string binding reference. * - * @param binding - * The EJBBinding Object that represents a reference to a bean - * @param name - * String form of the jndi name, excluding the namespace prefix - * e.g. for the resource "ejblocal:/.jar/#" this name - * would be "/.jar/#" - * @param isSimpleName - * Flag used to force creation of an AmbiguousEJBReference if an - * ambiguous simple name binding is detected - * @param isDefaultBinding - * Flag used if this is a default binding + * @param binding The EJBBinding Object that represents a reference to a bean + * @param name String form of the jndi name, excluding the namespace prefix + * e.g. for the resource "ejblocal:/.jar/#" this name + * would be "/.jar/#" + * @param isSimpleName Flag used to force creation of an AmbiguousEJBReference if an + * ambiguous simple name binding is detected + * @param isDefaultBinding Flag used if this is a default binding * @return false if the binding is an AmbiguousEJBReference - * @throws NamingException - * is thrown if the binding is ambiguous and customBindingsOnErr is FAIL. + * @throws NamingException is thrown if the binding is ambiguous and customBindingsOnErr is FAIL. */ boolean bind(EJBBinding binding, String name, boolean isSimpleName, boolean isDefaultBinding) throws NamingException; @@ -75,7 +89,7 @@ public interface EJBLocalNamingHelper { * Unbind the names from the ejblocal: name space. * * @param names List of names to remove from the - * application name space. + * application name space. */ public void removeBindings(List names); } diff --git a/dev/com.ibm.ws.container.service/src/com/ibm/ws/container/service/naming/package-info.java b/dev/com.ibm.ws.container.service/src/com/ibm/ws/container/service/naming/package-info.java index ddb20a56ecf1..6b7b3f1d174a 100644 --- a/dev/com.ibm.ws.container.service/src/com/ibm/ws/container/service/naming/package-info.java +++ b/dev/com.ibm.ws.container.service/src/com/ibm/ws/container/service/naming/package-info.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011, 2020 IBM Corporation and others. + * Copyright (c) 2011, 2025 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at @@ -10,8 +10,8 @@ * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ -/** @version 2.0 */ -@org.osgi.annotation.versioning.Version("2.0") +/** @version 2.1 */ +@org.osgi.annotation.versioning.Version("2.1") @TraceOptions(traceGroup = "Naming") package com.ibm.ws.container.service.naming; diff --git a/dev/com.ibm.ws.ejbcontainer.bindings_fat/test-applications/EJB3DefBndWeb.war/src/com/ibm/ws/ejbcontainer/bindings/defbnd/web/DefaultBindingsServlet.java b/dev/com.ibm.ws.ejbcontainer.bindings_fat/test-applications/EJB3DefBndWeb.war/src/com/ibm/ws/ejbcontainer/bindings/defbnd/web/DefaultBindingsServlet.java index 2a5b354754c3..735f0d811238 100644 --- a/dev/com.ibm.ws.ejbcontainer.bindings_fat/test-applications/EJB3DefBndWeb.war/src/com/ibm/ws/ejbcontainer/bindings/defbnd/web/DefaultBindingsServlet.java +++ b/dev/com.ibm.ws.ejbcontainer.bindings_fat/test-applications/EJB3DefBndWeb.war/src/com/ibm/ws/ejbcontainer/bindings/defbnd/web/DefaultBindingsServlet.java @@ -1,10 +1,10 @@ /******************************************************************************* - * Copyright (c) 2007, 2019 IBM Corporation and others. + * Copyright (c) 2007, 2025 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 * * Contributors: @@ -14,7 +14,20 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.naming.Binding; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NameClassPair; +import javax.naming.NameNotFoundException; +import javax.naming.NamingEnumeration; +import javax.naming.NotContextException; import javax.servlet.annotation.WebServlet; import org.junit.Test; @@ -49,6 +62,8 @@ *
  • testRemoteBI lookup and call remote business interface using component name. *
  • testLocalCI lookup and call local component view interface using component name. *
  • testRemoteCI lookup and call remote component view interface using component name. + *
  • testGlobalSubContexts lookup of java:global sub contexts + *
  • testEJBLocalSubContexts lookup of ejblocal: sub-contexts * *
    Data Sources * @@ -57,6 +72,71 @@ @WebServlet("/DefaultBindingsServlet") public class DefaultBindingsServlet extends FATServlet { + private static final Map GLOBAL_ROOT_LIST = initGlobalRootList(); + private static final Map GLOBAL_APP_LIST = initGlobalAppList(); + private static final Map GLOBAL_MOD_LIST = initGlobalModuleList(); + + private static final Map LOCAL_ROOT_LIST = initLocalRootList(); + private static final Map LOCAL_APP_LIST = initLocalAppList(); + private static final Map LOCAL_MOD_LIST = initLocalModuleList(); + + // NameClassPair list returned for the java:global root context + private static final Map initGlobalRootList() { + Map list = new HashMap(); + list.put("EJB2XDefBndTestApp", "javax.naming.Context"); + list.put("EJB3DefBndTestApp", "javax.naming.Context"); + return Collections.unmodifiableMap(list); + } + + // NameClassPair list returned for the java:global/ context + private static final Map initGlobalAppList() { + Map list = new HashMap(); + list.put("EJB3DefBndBean", "javax.naming.Context"); + return Collections.unmodifiableMap(list); + } + + // NameClassPair list returned for the java:global// context + private static final Map initGlobalModuleList() { + Map list = new HashMap(); + list.put("TestBean!com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalHome", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalHome"); + list.put("TestComponentBean!com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestRemoteComponentHome", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestRemoteComponentHome"); + list.put("TestBean!com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalBusiness", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalBusiness"); + list.put("TestBean!com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.RemoteBusiness", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.RemoteBusiness"); + list.put("TestBean!com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestRemoteHome", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestRemoteHome"); + list.put("TestComponentBean!com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.RemoteComponentBusiness", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.RemoteComponentBusiness"); + list.put("TestComponentBean!com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalComponentHome", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalComponentHome"); + list.put("TestComponentBean!com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalComponentBusiness", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalComponentBusiness"); + return Collections.unmodifiableMap(list); + } + + // NameClassPair list returned for the ejblocal: root context + private static final Map initLocalRootList() { + Map list = new HashMap(); + list.put("EJB3DefBndTestApp", "javax.naming.Context"); + list.put("com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalBusiness", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalBusiness"); + list.put("com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalComponentHome", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalComponentHome"); + list.put("my", "javax.naming.Context"); + list.put("ejb", "javax.naming.Context"); + list.put("com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalHome", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalHome"); + list.put("com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalComponentBusiness", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalComponentBusiness"); + return Collections.unmodifiableMap(list); + } + + // NameClassPair list returned for the ejblocal:/ context + private static final Map initLocalAppList() { + Map list = new HashMap(); + list.put("EJB3DefBndBean.jar", "javax.naming.Context"); + return Collections.unmodifiableMap(list); + } + + // NameClassPair list returned for the ejblocal:// context + private static final Map initLocalModuleList() { + Map list = new HashMap(); + list.put("TestBean#com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalHome", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.TestLocalHome"); + list.put("TestBean#com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalBusiness", "com.ibm.ws.ejbcontainer.bindings.defbnd.ejb.LocalBusiness"); + return Collections.unmodifiableMap(list); + } + /** * lookup and call local business interface using short name. */ @@ -160,4 +240,456 @@ public void testRemoteCI() throws Exception { String str = bean.getString(); assertEquals("3 ---> getString() returned unexpected value", "Success", str); } + + private void testSubContexts(String rootContext, String rootSeparator, String application, String module, String beanName, String interfaceSeparator, + String interfaceName, Map RootList, Map AppList, Map ModList) throws Exception { + String appContext = rootContext + rootSeparator + application; + String modContext = rootContext + rootSeparator + application + "/" + module; + String jndiName = application + "/" + module + "/" + beanName + interfaceSeparator + interfaceName; + String jndiNameModule = module + "/" + beanName + interfaceSeparator + interfaceName; + String jndiNameBean = beanName + interfaceSeparator + interfaceName; + + // --------------------------------------------------------------------- + // test root context + // --------------------------------------------------------------------- + + Context rootctx = (Context) new InitialContext().lookup(rootContext); + assertNotNull("1 --> lookup " + rootContext + " was null", rootctx); + + rootctx = (Context) rootctx.lookup(""); + assertNotNull("2 --> lookup empty in " + rootContext + " was null", rootctx); + + try { + rootctx = (Context) rootctx.lookup("/"); + fail("3 --> lookup / in " + rootContext + " did not fail : " + rootctx); + } catch (NameNotFoundException ex) { + assertTrue("3 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + rootContext + "/")); + } + + NamingEnumeration list = rootctx.list(""); + assertNotNull("4 --> list empty for " + rootContext + " was null", list); + verifyList(list, RootList); + + try { + list = rootctx.list("/"); + fail("5 --> list / for " + rootContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("5 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: /")); + } + + list = rootctx.list(application); + assertNotNull("6 --> list " + application + " for " + rootContext + " was null", list); + verifyList(list, AppList); + + try { + list = rootctx.list(application + "/"); + fail("7 --> list " + application + "/ for " + rootContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("7 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + application + "/")); + } + + list = rootctx.list(application + "/" + module); + assertNotNull("8 --> list " + application + "/" + module + " for " + rootContext + " was null", list); + verifyList(list, ModList); + + try { + list = rootctx.list(application + "/" + module + "/"); + fail("9 --> list " + application + "/" + module + "/ for " + rootContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("9 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + application + "/" + module + "/")); + } + + NamingEnumeration bindings = rootctx.listBindings(""); + assertNotNull("10 --> listBindings empty for " + rootContext + " was null", bindings); + verifyBindings(bindings, RootList, rootContext + rootSeparator); + + try { + bindings = rootctx.listBindings("/"); + fail("11 --> listBindings / for " + rootContext + " did not fail : " + bindings); + } catch (NameNotFoundException ex) { + assertTrue("11 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: /")); + } + + bindings = rootctx.listBindings(application); + assertNotNull("12 --> listBindings " + application + " for " + rootContext + " was null", bindings); + verifyBindings(bindings, AppList, appContext + "/"); + + // verify using a returned binding to lookup a bean + boolean beanFromListBindingFound = false; + bindings = rootctx.listBindings(application); + while (bindings.hasMore()) { + Binding binding = bindings.next(); + String foundName = binding.getName(); + if (foundName.equals(module)) { + Context foundContext = (Context) binding.getObject(); + LocalBusiness bean = (LocalBusiness) foundContext.lookup(jndiNameBean); + assertNotNull("13 --> lookup " + jndiNameBean + " in listBinding context " + module + " was null", bean); + assertEquals("14 --> getString() returned unexpected value", "Success", bean.getString()); + + String nameInNameSpace = foundContext.getNameInNamespace(); + bean = (LocalBusiness) new InitialContext().lookup(nameInNameSpace + "/" + jndiNameBean); + assertNotNull("15 --> lookup " + nameInNameSpace + "/" + jndiNameBean + " in InitialContext was null", bean); + assertEquals("16 --> getString() returned unexpected value", "Success", bean.getString()); + + beanFromListBindingFound = true; + break; + } + } + assertTrue("17 --> bean not found in " + module + " context from listBindings", beanFromListBindingFound); + + try { + bindings = rootctx.listBindings(application + "/"); + fail("18 --> listBindings " + application + "/ for " + rootContext + " did not fail : " + bindings); + } catch (NameNotFoundException ex) { + assertTrue("18 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + application + "/")); + } + + bindings = rootctx.listBindings(application + "/" + module); + assertNotNull("19 --> listBindings " + application + "/" + module + " for " + rootContext + " was null", bindings); + verifyBindings(bindings, ModList, modContext + "/"); + + try { + bindings = rootctx.listBindings(application + "/" + module + "/"); + fail("20 --> listBindings " + application + "/" + module + "/ for " + rootContext + " did not fail : " + bindings); + } catch (NameNotFoundException ex) { + assertTrue("20 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + application + "/" + module + "/")); + } + + LocalBusiness bean = (LocalBusiness) rootctx.lookup(jndiName); + assertNotNull("21 --> lookup " + jndiName + " in " + rootContext + " was null", bean); + assertEquals("22 --> getString() returned unexpected value", "Success", bean.getString()); + + // --------------------------------------------------------------------- + // test application context + // --------------------------------------------------------------------- + + Context appctx = (Context) rootctx.lookup(application); + assertNotNull("23 --> lookup " + application + " for " + rootContext + " was null", appctx); + + // java:global does not support root context lookup; ejblocal: does + if ("java:global".equals(rootContext)) { + try { + appctx = (Context) rootctx.lookup(appContext); + fail("24 --> lookup " + appContext + " for " + rootContext + " did not fail : " + appctx); + } catch (NameNotFoundException ex) { + assertTrue("24 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + rootContext + rootSeparator + appContext)); + } + } else { + appctx = (Context) rootctx.lookup(appContext); + assertNotNull("24 --> lookup " + appContext + " for " + rootContext + " was null", appctx); + } + + try { + appctx = (Context) rootctx.lookup(appContext + "/"); + fail("25 --> lookup " + appContext + "/ for " + appContext + "/ did not fail : " + appctx); + } catch (NameNotFoundException ex) { + if ("java:global".equals(rootContext)) { + assertTrue("25 --> Expected exception text not present : " + ex, + ex.toString().contains("NameNotFoundException: " + rootContext + rootSeparator + appContext + "/")); + } else { + // ejblocal: lookup strips off an extra ejblocal: prefix + assertTrue("25 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + appContext + "/")); + } + } + + appctx = (Context) appctx.lookup(""); + assertNotNull("26 --> lookup empty for " + appContext + " was null", appctx); + + try { + appctx = (Context) appctx.lookup("/"); + fail("27 --> lookup / for " + rootContext + "/" + application + " did not fail : " + appctx); + } catch (NameNotFoundException ex) { + assertTrue("27 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + appContext + "/")); + } + + list = appctx.list(""); + assertNotNull("28 --> list empty for " + appContext + " was null", list); + verifyList(list, AppList); + + try { + list = appctx.list("/"); + fail("29 --> list / for " + appContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("29 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: /")); + } + + try { + list = appctx.list(application); + fail("30 --> list " + application + " for " + appContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("30 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + application)); + } + + list = appctx.list(module); + assertNotNull("31 --> list " + module + " for " + appContext + " was null", list); + verifyList(list, ModList); + + try { + list = appctx.list(module + "/"); + fail("32 --> list " + module + "/ for " + appContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("32 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + module + "/")); + } + + bindings = appctx.listBindings(""); + assertNotNull("33 --> listBindings empty for " + appContext + " was null", bindings); + verifyBindings(bindings, AppList, appContext + "/"); + + try { + bindings = appctx.listBindings("/"); + fail("34--> listBindings / for " + appContext + " did not fail : " + bindings); + } catch (NameNotFoundException ex) { + assertTrue("34 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: /")); + } + + try { + bindings = appctx.listBindings(application); + fail("35 --> listBindings " + application + " for " + appContext + " did not fail : " + bindings); + } catch (NameNotFoundException ex) { + assertTrue("35 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + application)); + } + + bindings = appctx.listBindings(module); + assertNotNull("36 --> listBindings " + module + " for " + appContext + " was null", bindings); + verifyBindings(bindings, ModList, modContext + "/"); + + try { + bindings = appctx.listBindings(module + "/"); + fail("37 --> listBindings " + module + "/ for " + appContext + " did not fail : " + bindings); + } catch (NameNotFoundException ex) { + assertTrue("37 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + module + "/")); + } + + try { + bean = (LocalBusiness) appctx.lookup(jndiName); + fail("38 --> lookup " + jndiName + " for " + appContext + " did not fail : " + bean); + } catch (NameNotFoundException ex) { + assertTrue("38 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + appContext + "/" + jndiName)); + } + + bean = (LocalBusiness) appctx.lookup(jndiNameModule); + assertNotNull("39 --> lookup " + jndiNameModule + " in " + appContext + " was null", bean); + assertEquals("40 --> getString() returned unexpected value", "Success", bean.getString()); + + try { + list = appctx.list(jndiNameModule); + fail("41 --> list " + jndiNameModule + " for " + appContext + " did not fail : " + list); + } catch (NotContextException ex) { + assertTrue("41 --> Expected exception text not present : " + ex, ex.toString().contains("NotContextException: " + appContext + "/" + jndiNameModule)); + } + + try { + bindings = appctx.listBindings(jndiNameModule); + fail("42 --> listBindings " + jndiNameModule + " for " + modContext + " did not fail : " + bindings); + } catch (NotContextException ex) { + assertTrue("42 --> Expected exception text not present : " + ex, ex.toString().contains("NotContextException: " + appContext + "/" + jndiNameModule)); + } + + // --------------------------------------------------------------------- + // test module context + // --------------------------------------------------------------------- + + Context modctx = (Context) rootctx.lookup(application + "/" + module); + assertNotNull("43--> lookup " + application + "/" + module + " for " + rootContext + " was null", modctx); + + modctx = (Context) appctx.lookup(module); + assertNotNull("44 --> lookup " + module + " for " + appContext + " was null", modctx); + + // java:global does not support root context lookup; ejblocal: does + if ("java:global".equals(rootContext)) { + try { + modctx = (Context) rootctx.lookup(modContext); + fail("45 --> lookup " + modContext + " for " + rootContext + " did not fail : " + modctx); + } catch (NameNotFoundException ex) { + assertTrue("45 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + rootContext + rootSeparator + modContext)); + } + } else { + modctx = (Context) rootctx.lookup(modContext); + assertNotNull("45 --> lookup " + modContext + " for " + rootContext + " was null", modctx); + } + + modctx = (Context) modctx.lookup(""); + assertNotNull("46 --> lookup empty for " + modContext + " was null", modctx); + + try { + modctx = (Context) modctx.lookup("/"); + fail("47 --> lookup / for " + modContext + " did not fail : " + modctx); + } catch (NameNotFoundException ex) { + assertTrue("47 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + modContext + "/")); + } + + list = modctx.list(""); + assertNotNull("48 --> list empty for " + modContext + " was null", list); + verifyList(list, ModList); + + try { + list = modctx.list("/"); + fail("49 --> list / for " + modContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("49 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: /")); + } + + try { + list = modctx.list(application); + fail("50 --> list " + application + " for " + modContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("50 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + application)); + } + + try { + list = modctx.list(module); + fail("51 --> list " + module + " for " + modContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("51 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + module)); + } + + bindings = modctx.listBindings(""); + assertNotNull("52 --> listBindings empty for " + modContext + " was null", bindings); + verifyBindings(bindings, ModList, modContext + "/"); + + try { + bindings = modctx.listBindings("/"); + fail("53 --> listBindings / for " + modContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("53 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: /")); + } + + try { + bindings = modctx.listBindings(application); + fail("54 --> listBindings " + application + " for " + modContext + " did not fail : " + list); + } catch (NameNotFoundException ex) { + assertTrue("54 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + application)); + } + + try { + bean = (LocalBusiness) modctx.lookup(jndiName); + fail("55 --> lookup " + jndiName + " for " + modContext + " did not fail : " + bean); + } catch (NameNotFoundException ex) { + assertTrue("55 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + modContext + "/" + jndiName)); + } + + try { + bean = (LocalBusiness) modctx.lookup(jndiNameModule); + fail("56 --> lookup " + jndiNameModule + " for " + modContext + " did not fail : " + bean); + } catch (NameNotFoundException ex) { + assertTrue("56 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + modContext + "/" + jndiNameModule)); + } + + bean = (LocalBusiness) modctx.lookup(jndiNameBean); + assertNotNull("57 --> lookup " + jndiNameBean + " in " + modContext + " was null", bean); + assertEquals("58 --> getString() returned unexpected value", "Success", bean.getString()); + + try { + bean = (LocalBusiness) modctx.lookup("/" + jndiNameBean); + fail("59 --> lookup /" + jndiNameBean + " for " + modContext + " did not fail : " + bean); + } catch (NameNotFoundException ex) { + assertTrue("59 --> Expected exception text not present : " + ex, ex.toString().contains("NameNotFoundException: " + modContext + "/" + "/" + jndiNameBean)); + } + + try { + list = modctx.list(jndiNameBean); + fail("60 --> list " + jndiNameBean + " for " + modContext + " did not fail : " + list); + } catch (NotContextException ex) { + assertTrue("60 --> Expected exception text not present : " + ex, ex.toString().contains("NotContextException: " + modContext + "/" + jndiNameBean)); + } + + try { + bindings = modctx.listBindings(jndiNameBean); + fail("61 --> listBindings " + jndiNameBean + " for " + modContext + " did not fail : " + bindings); + } catch (NotContextException ex) { + assertTrue("61 --> Expected exception text not present : " + ex, ex.toString().contains("NotContextException: " + modContext + "/" + jndiNameBean)); + } + } + + private void verifyList(NamingEnumeration foundList, Map expectedList) throws Exception { + Map expected = new HashMap(expectedList); + int index = 0; + while (foundList.hasMore()) { + NameClassPair pair = foundList.next(); + System.out.println("list " + index + " : " + pair.getName() + ", " + pair.getClassName()); + index++; + String foundName = pair.getName(); + String foundClass = pair.getClassName(); + String expectedClass = expected.get(foundName); + assertNotNull(" --> Context.list contained unexpected entry : " + foundName + ", " + foundClass, expectedClass); + assertEquals(" --> Context.list contained entry with incorrect type : " + foundName, expectedClass, foundClass); + expected.remove(foundName); + } + if (expected.size() > 0) { + String notFound = ""; + for (String expectedKey : expected.keySet()) { + notFound = expectedKey + ", " + notFound; + } + fail(" --> Context.list did not contain all expected names : " + notFound); + } + } + + private void verifyBindings(NamingEnumeration foundBindings, Map expectedBindings, String namespacePrefix) throws Exception { + Map expected = new HashMap(expectedBindings); + int index = 0; + while (foundBindings.hasMore()) { + Binding binding = foundBindings.next(); + System.out.println("binding " + index + " : " + binding.getName() + ", " + binding.getClassName() + ", " + binding.isRelative()); + index++; + String foundName = binding.getName(); + String foundClass = binding.getClassName(); + String expectedClass = expected.get(foundName); + assertNotNull(" --> Context.listBinding contained unexpected entry : " + foundName + ", " + foundClass, expectedClass); + assertEquals(" --> Context.listBinding contained entry with incorrect type : " + foundName, expectedClass, foundClass); + assertTrue(" --> Context.listBinding contained entry with isRelative false", binding.isRelative()); + expected.remove(foundName); + + if (foundClass.equals(LocalBusiness.class.getName())) { + LocalBusiness bean = (LocalBusiness) binding.getObject(); + System.out.println(" testing bean obtained from listBindings : " + bean); + assertNotNull(" --> Context.listBinding for LocalBusiness did not return bean", bean); + assertEquals(" --> getString() returned unexpected value", "Success", bean.getString()); + } else if (foundClass.equals(Context.class.getName())) { + System.out.println(" testing context obtained from listBindings : " + binding.getName() + ", " + binding.getObject()); + assertNotNull(" --> Context.listBinding did not return the Context for a binding : " + binding.getName(), binding.getObject()); + String nameInNamespace = ((Context) binding.getObject()).getNameInNamespace(); + assertEquals(" --> Context.listBinding returned context.getNameInNamespace not correct", namespacePrefix + binding.getName(), nameInNamespace); + } + } + if (expected.size() > 0) { + String notFound = ""; + for (String expectedKey : expected.keySet()) { + notFound = expectedKey + ", " + notFound; + } + fail(" --> Context.list did not contain all expected names : " + notFound); + } + + } + + /** + * lookup of java:global sub contexts. + */ + @Test + public void testGlobalSubContexts() throws Exception { + String rootContext = "java:global"; + String rootSeparator = "/"; + String application = "EJB3DefBndTestApp"; + String module = "EJB3DefBndBean"; + String beanName = "TestBean"; + String interfaceSeparator = "!"; + String interfaceName = LocalBusiness.class.getName(); + + testSubContexts(rootContext, rootSeparator, application, module, beanName, interfaceSeparator, interfaceName, GLOBAL_ROOT_LIST, GLOBAL_APP_LIST, GLOBAL_MOD_LIST); + } + + /** + * lookup of ejblocal: sub contexts. + */ + @Test + public void testEJBLocalSubContexts() throws Exception { + String rootContext = "ejblocal:"; + String rootSeparator = ""; + String application = "EJB3DefBndTestApp"; + String module = "EJB3DefBndBean.jar"; + String beanName = "TestBean"; + String interfaceSeparator = "#"; + String interfaceName = LocalBusiness.class.getName(); + + testSubContexts(rootContext, rootSeparator, application, module, beanName, interfaceSeparator, interfaceName, LOCAL_ROOT_LIST, LOCAL_APP_LIST, LOCAL_MOD_LIST); + } } diff --git a/dev/com.ibm.ws.ejbcontainer/src/com/ibm/ws/ejbcontainer/osgi/internal/naming/EJBLocalNamingHelperImpl.java b/dev/com.ibm.ws.ejbcontainer/src/com/ibm/ws/ejbcontainer/osgi/internal/naming/EJBLocalNamingHelperImpl.java index bdc58b5189b4..4a4ac54b9f92 100644 --- a/dev/com.ibm.ws.ejbcontainer/src/com/ibm/ws/ejbcontainer/osgi/internal/naming/EJBLocalNamingHelperImpl.java +++ b/dev/com.ibm.ws.ejbcontainer/src/com/ibm/ws/ejbcontainer/osgi/internal/naming/EJBLocalNamingHelperImpl.java @@ -1,10 +1,10 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 IBM Corporation and others. + * Copyright (c) 2019, 2025 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 * * Contributors: @@ -12,12 +12,17 @@ *******************************************************************************/ package com.ibm.ws.ejbcontainer.osgi.internal.naming; +import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import javax.naming.Context; +import javax.naming.NameClassPair; import javax.naming.NamingException; +import javax.naming.NotContextException; import org.osgi.framework.ServiceReference; import org.osgi.service.component.annotations.Component; @@ -44,6 +49,7 @@ public class EJBLocalNamingHelperImpl extends EJBNamingInstancer implements EJBL private static final TraceComponent tc = Tr.register(EJBLocalNamingHelperImpl.class, "EJBContainer", "com.ibm.ejs.container.container"); private final HashMap EJBLocalBindings = new HashMap(); + private HashMap> EJBLocalContextsCache = null; private final ReentrantReadWriteLock javaColonLock = new ReentrantReadWriteLock(); @@ -67,6 +73,58 @@ public Object getObjectInstance(String name) throws NamingException { return initializeEJB(binding, "ejblocal:" + name); } + @Override + public Collection listInstances(String context, String name) throws NamingException { + String contextName = context.isEmpty() ? name : (context + "/" + name); + // append a "/" to ensure search is over a context; unless searching root + String contextKey = contextName + (name.isEmpty() ? "" : "/"); + boolean root = contextKey.isEmpty(); + int contextIndex = root ? 0 : contextKey.lastIndexOf("/") + 1; + + Lock readLock = javaColonLock.readLock(); + readLock.lock(); + + Collection instances = null; + try { + instances = EJBLocalContextsCache != null ? EJBLocalContextsCache.get(contextName) : null; + if (instances != null) { + return instances; + } + + String instanceName = null; + String instanceType = null; + + Map allInstances = new HashMap(); + for (Map.Entry entry : EJBLocalBindings.entrySet()) { + String key = entry.getKey(); + if (root || key.startsWith(contextKey)) { + int instanceIndex = key.indexOf("/", contextIndex + 1); + if (instanceIndex == -1) { + instanceName = key.substring(contextIndex); + instanceType = entry.getValue().interfaceName; + } else { + instanceName = key.substring(contextIndex, instanceIndex); + instanceType = Context.class.getName(); + } + allInstances.put(instanceName, new NameClassPair(instanceName, instanceType)); + } else if (key.equals(contextName)) { + throw new NotContextException("ejblocal:" + contextName); + } + } + instances = allInstances.values(); + if (instances.size() > 0) { + if (EJBLocalContextsCache == null) { + EJBLocalContextsCache = new HashMap>(); + } + EJBLocalContextsCache.put(contextName, instances); + } + } finally { + readLock.unlock(); + } + + return instances; + } + @Reference(service = EJBHomeRuntime.class, cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) @@ -167,6 +225,9 @@ public void unbind(String name) { writeLock.lock(); try { + if (EJBLocalContextsCache != null) { + EJBLocalContextsCache.clear(); + } EJBLocalBindings.remove(name); } finally { writeLock.unlock(); @@ -185,6 +246,9 @@ public void removeBindings(List names) { writeLock.lock(); try { + if (EJBLocalContextsCache != null) { + EJBLocalContextsCache.clear(); + } for (String name : names) { if (isTraceOn && tc.isDebugEnabled()) { Tr.debug(tc, "unbinding: " + name); diff --git a/dev/com.ibm.ws.jndi.ejb/src/com/ibm/ws/jndi/ejb/EJBLocalURLContext.java b/dev/com.ibm.ws.jndi.ejb/src/com/ibm/ws/jndi/ejb/EJBLocalURLContext.java index fe04e2586fbe..c23ce85a84ff 100644 --- a/dev/com.ibm.ws.jndi.ejb/src/com/ibm/ws/jndi/ejb/EJBLocalURLContext.java +++ b/dev/com.ibm.ws.jndi.ejb/src/com/ibm/ws/jndi/ejb/EJBLocalURLContext.java @@ -1,10 +1,10 @@ /******************************************************************************* - * Copyright (c) 2019 IBM Corporation and others. + * Copyright (c) 2019, 2025 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-2.0/ - * + * * SPDX-License-Identifier: EPL-2.0 * * Contributors: @@ -12,6 +12,7 @@ *******************************************************************************/ package com.ibm.ws.jndi.ejb; +import java.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; @@ -31,6 +32,7 @@ import com.ibm.websphere.ras.Tr; import com.ibm.websphere.ras.TraceComponent; import com.ibm.ws.container.service.naming.EJBLocalNamingHelper; +import com.ibm.ws.ffdc.annotation.FFDCIgnore; import com.ibm.ws.jndi.WSContextBase; import com.ibm.ws.jndi.WSName; import com.ibm.wsspi.kernel.service.utils.ConcurrentServiceReferenceSet; @@ -50,22 +52,34 @@ public class EJBLocalURLContext extends WSContextBase implements Context { private final Map environment = new ConcurrentHashMap(); private final ConcurrentServiceReferenceSet helperServices; + // The sub-context, if this context represents a sub-context of ejblocal: + private final String subContext; + /** * Constructor for use by the EJBLocalURLContextFactory. * * @param env - * Map of environment parameters for this Context + * Map of environment parameters for this Context */ @SuppressWarnings("unchecked") public EJBLocalURLContext(Hashtable environment, ConcurrentServiceReferenceSet helperServices) { this.environment.putAll((Map) environment); this.helperServices = helperServices; + this.subContext = ""; } // Copy constructor for when the lookup string is blank or just ejblocal namespace public EJBLocalURLContext(EJBLocalURLContext copy) { this.environment.putAll(copy.environment); this.helperServices = copy.helperServices; + this.subContext = copy.subContext; + } + + // Copy constructor for when the lookup string is a sub-context of the ejblocal namespace + public EJBLocalURLContext(EJBLocalURLContext copy, String subContext) { + this.environment.putAll(copy.environment); + this.helperServices = copy.helperServices; + this.subContext = subContext; } /** @@ -99,7 +113,7 @@ public void close() throws NamingException { */ @Override public String getNameInNamespace() throws NamingException { - return "ejblocal:"; + return "ejblocal:" + subContext; } /** @@ -122,11 +136,12 @@ public Object removeFromEnvironment(String s) throws NamingException { * Throws NameNotFoundException if no matching Object is found. * * @param n - * {@inheritDoc} + * {@inheritDoc} * @return {@inheritDoc} * @throws NamingException {@inheritDoc} */ @Override + @FFDCIgnore({ NameNotFoundException.class }) protected Object lookup(WSName name) throws NamingException { final boolean isTraceOn = TraceComponent.isAnyTracingEnabled(); @@ -158,6 +173,12 @@ protected Object lookup(WSName name) throws NamingException { return new InitialContext().lookup(lookup); } + if (lookup.equals("/")) { + // avoid message text with double slash at end + String nameStr = "ejblocal:" + subContext + lookup; + throw new NameNotFoundException(NameNotFoundException.class.getName() + ": " + nameStr); + } + Object toReturn = null; /** @@ -165,10 +186,18 @@ protected Object lookup(WSName name) throws NamingException { * ejblocal: in front, but they can also look us up from the initial context * with ejblocal: in front. Our helper service stores the binding without * the namespace context in front so just remove it if present. + * + * otherwise if ejblocal: is not in front and this context represents a + * sub-context of ejblocal, then prepend the sub-context. */ + String listModified = lookup; String lookupModified = lookup; - if (lookupModified.startsWith("ejblocal:")) + if (lookupModified.startsWith("ejblocal:")) { lookupModified = lookupModified.substring(9); + listModified = lookupModified; + } else if (!subContext.isEmpty()) { + lookupModified = subContext + "/" + lookupModified; + } for (Iterator it = helperServices.getServices(); it.hasNext();) { EJBLocalNamingHelper helperService = it.next(); @@ -182,8 +211,29 @@ protected Object lookup(WSName name) throws NamingException { } } + /** + * if they are doing a lookup of a sub-context, then just look for any instances + * bound under that sub-contex; if found, return a new context for that sub-context. + */ + if (toReturn == null) { + try { + if (list(listModified).hasMore()) { + toReturn = new EJBLocalURLContext(this, lookupModified); + if (isTraceOn && tc.isDebugEnabled()) { + Tr.debug(tc, "NamingHelper found sub-contexts: " + lookupModified + ", " + toReturn); + } + } + } catch (NameNotFoundException ex) { + // normal when a sub-context is not found; ignore and report meaningful exception below + if (isTraceOn && tc.isDebugEnabled()) { + Tr.debug(tc, "NamingHelper failed to find sub-contexts: " + ex); + } + } + } + if (toReturn == null) { - throw new NameNotFoundException(NameNotFoundException.class.getName() + ": " + name.toString()); + String nameStr = "ejblocal:" + lookupModified; + throw new NameNotFoundException(nameStr); } return toReturn; @@ -211,7 +261,26 @@ protected void unbind(WSName name) throws NamingException { @Override protected NamingEnumeration list(WSName name) throws NamingException { - throw new OperationNotSupportedException(); + final boolean isTraceOn = TraceComponent.isAnyTracingEnabled(); + HashSet allInstances = new HashSet(); + for (Iterator it = helperServices.getServices(); it.hasNext();) { + EJBLocalNamingHelper helperService = it.next(); + Collection instances = helperService.listInstances(subContext, name.toString()); + + if (instances != null) { + if (isTraceOn && tc.isDebugEnabled()) { + Tr.debug(tc, "NamingHelper found instances: " + instances); + } + allInstances.addAll(instances); + } + } + + // If nothing found, report name not found, unless listing the root context + if (allInstances.isEmpty() && !(subContext.isEmpty() && name.isEmpty())) { + throw new NameNotFoundException(name.toString()); + } + + return new EJBNamingEnumeration(allInstances); } @Override