/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.core.stateless; import junit.framework.TestCase; import org.apache.openejb.BeanContext; import org.apache.openejb.InterfaceType; import org.apache.openejb.OpenEJB; import org.apache.openejb.RpcContainer; import org.apache.openejb.assembler.classic.Assembler; import org.apache.openejb.assembler.classic.EjbJarInfo; import org.apache.openejb.assembler.classic.ProxyFactoryInfo; import org.apache.openejb.assembler.classic.SecurityServiceInfo; import org.apache.openejb.assembler.classic.StatelessSessionContainerInfo; import org.apache.openejb.assembler.classic.TransactionServiceInfo; import org.apache.openejb.config.ConfigurationFactory; import org.apache.openejb.config.EjbModule; import org.apache.openejb.core.ivm.naming.InitContextFactory; import org.apache.openejb.jee.EjbJar; import org.apache.openejb.jee.StatelessBean; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.spi.ContainerSystem; import org.junit.AfterClass; import javax.annotation.Resource; import javax.ejb.SessionContext; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptors; import javax.interceptor.InvocationContext; import javax.xml.rpc.handler.MessageContext; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; /** * The point of this test case is to verify that OpenEJB is accurately performing * it's part of a WebServiceProvider to OpenEJB invocation as it relates to JAX-RPC. * <p/> * In the agreement between OpenEJB and the Web Service Provider, the Web Service Provider * must supply the MessageContext and an Interceptor as the arguments of the standard * container.invoke method call. * <p/> * OpenEJB must ensure the MessageContext is exposed via the SessionContext.getMessageContext * and ensure that the interceptor is added to the chain just after the other interceptors and * before the bean method itself is invoked. * * @version $Rev$ $Date$ */ public class JaxRpcInvocationTest extends TestCase { @AfterClass public static void afterClass() throws Exception { OpenEJB.destroy(); } public void testWebServiceInvocations() throws Exception { System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY, InitContextFactory.class.getName()); final ConfigurationFactory config = new ConfigurationFactory(); final Assembler assembler = new Assembler(); assembler.createProxyFactory(config.configureService(ProxyFactoryInfo.class)); assembler.createTransactionManager(config.configureService(TransactionServiceInfo.class)); assembler.createSecurityService(config.configureService(SecurityServiceInfo.class, "PseudoSecurityService", null, "PseudoSecurityService", null)); assembler.createContainer(config.configureService(StatelessSessionContainerInfo.class)); final EjbJarInfo ejbJar = config.configureApplication(buildTestApp()); assembler.createApplication(ejbJar); final ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class); final BeanContext beanContext = containerSystem.getBeanContext("EchoBean"); assertNotNull(beanContext); assertEquals("ServiceEndpointInterface", EchoServiceEndpoint.class, beanContext.getServiceEndpointInterface()); // OK, Now let's fake a web serivce invocation coming from any random // web service provider. The web serivce provider needs supply // the MessageContext and an interceptor to do the marshalling as // the arguments of the standard container.invoke signature. // So let's create a fake message context. final MessageContext messageContext = new FakeMessageContext(); // Now let's create a fake interceptor as would be supplied by the // web service provider. Instead of writing "fake" marshalling // code that would pull the arguments from the soap message, we'll // just give it the argument values directly. final Object wsProviderInterceptor = new FakeWsProviderInterceptor("Hello world"); // Ok, now we have the two arguments expected on a JAX-RPC Web Service // invocation as per the OpenEJB-specific agreement between OpenEJB // and the Web Service Provider final Object[] args = new Object[]{messageContext, wsProviderInterceptor}; // Let's grab the container as the Web Service Provider would do and // perform an invocation final RpcContainer container = (RpcContainer) beanContext.getContainer(); final Method echoMethod = EchoServiceEndpoint.class.getMethod("echo", String.class); final String value = (String) container.invoke("EchoBean", InterfaceType.SERVICE_ENDPOINT, echoMethod.getDeclaringClass(), echoMethod, args, null); assertCalls(Call.values()); calls.clear(); assertEquals("Hello world", value); } private void assertCalls(final Call... expectedCalls) { final List expected = Arrays.asList(expectedCalls); assertEquals("Interceptor call stack", join("\n", expected), join("\n", calls)); } public static enum Call { WebServiceProvider_Invoke_BEFORE, EjbInterceptor_Invoke_BEFORE, Bean_Invoke_BEFORE, Bean_Invoke, Bean_Invoke_AFTER, EjbInterceptor_Invoke_AFTER, WebServiceProvider_Invoke_AFTER, } public static List<Call> calls = new ArrayList<Call>(); public EjbModule buildTestApp() { final EjbJar ejbJar = new EjbJar(); final StatelessBean bean = ejbJar.addEnterpriseBean(new StatelessBean(EchoBean.class)); bean.setServiceEndpoint(EchoServiceEndpoint.class.getName()); return new EjbModule(this.getClass().getClassLoader(), this.getClass().getSimpleName(), "test", ejbJar, null); } @Interceptors({PlainEjbInterceptor.class}) public static class EchoBean { @Resource private SessionContext ctx; @AroundInvoke public Object invoke(final InvocationContext context) throws Exception { /** * For JAX-RPC invocations the JAX-RPC MessageContex must be * available in the javax.ejb.SessionContext via the getMessageContext * method. As per the agreement between OpenEJB and the Web Service Provider * the MessageContex should have been passed into the container.invoke method * and the container should then ensure it's available via the SessionContext * for the duration of this call. */ final MessageContext messageContext = ctx.getMessageContext(); org.junit.Assert.assertNotNull("message context should not be null", messageContext); org.junit.Assert.assertTrue("the Web Service Provider's message context should be used", messageContext instanceof FakeMessageContext); calls.add(Call.Bean_Invoke_BEFORE); final Object o = context.proceed(); calls.add(Call.Bean_Invoke_AFTER); return o; } public String echo(final String data) { calls.add(Call.Bean_Invoke); return data; } } public static interface EchoServiceEndpoint extends java.rmi.Remote { String echo(String data); } /** * This interceptor is here to ensure that the container * still invokes interceptors normally for web serivce * invocations and to also guarantee that the Web Service * Provider's interceptor (which is a special OpenEJB concept) * is invoked *after* all the normal ejb interceptors. */ public static class PlainEjbInterceptor { @AroundInvoke public Object invoke(final InvocationContext context) throws Exception { // Track this call so we can assert proper interceptor order calls.add(Call.EjbInterceptor_Invoke_BEFORE); final Object o = context.proceed(); calls.add(Call.EjbInterceptor_Invoke_AFTER); return o; } } private static String join(final String delimeter, final List items) { final StringBuilder sb = new StringBuilder(); for (final Object item : items) { sb.append(item.toString()).append(delimeter); } return sb.toString(); } /** * This object would be implemented by the Web Service Provider per * the JAX-RPC spec and supplied to us in the container.invoke method * per the OpenEJB-WebServiceProvider agreement */ public static class FakeMessageContext implements MessageContext { public void setProperty(final String string, final Object object) { } public Object getProperty(final String string) { return null; } public void removeProperty(final String string) { } public boolean containsProperty(final String string) { return false; } public Iterator getPropertyNames() { return null; } } /** * This object would be supplied by the Web Service Provider * as per the OpenEJB-WebServiceProvider agreement and serves * two purposes: * <p/> * 1. Executing the Handler Chain (as required by * the JAX-RPC specification) in the context of the EJB Container * (as required by the EJB and J2EE WebServices specifications) * <p/> * 2. Unmarshalling the method arguments from the SOAP message * after the handlers in the Handler Chain have had a chance * to modify the argument values via the SAAJ tree. * <p/> * The Interceptor instance given to OpenEJB is constructed * and created by the Web Service Provider and should contain * all the data it requires to complete it's part of the agreement. * <p/> * OpenEJB will not perform any injection on this object and * the interceptor will be discarded so that the Web Service * Provider may pass in a new Interceptor instance on every * web service invocation. * <p/> * The Web Service Provider may pass in any object to serve * the roll of the Interceptor as long as it has an @AroundInvoke * method using the method signature: * <p/> * public Object <METHOD-NAME> (InvocationContext ctx) throws Exception * <p/> * Unlike typical EJB Interceptor around invoke methods, the @AroundInvoke * annotation must be used and is not optional, and the method must be public. */ public static class FakeWsProviderInterceptor { /** * These would normally come from the soap message */ private final Object[] args; public FakeWsProviderInterceptor(final Object... args) { this.args = args; } @AroundInvoke public Object invoke(final InvocationContext invocationContext) throws Exception { // The interceptor of the web serivce must set the // arguments it marshalls from the soap message into // the InvocationContext so we can invoke the bean. invocationContext.setParameters(args); final Object returnValue; try { // Track this call so we can assert proper interceptor order calls.add(Call.WebServiceProvider_Invoke_BEFORE); // handler chain "before advice" would happen here returnValue = invocationContext.proceed(); // handler chain "after advice" would happen here // Track this call so we can assert proper interceptor order calls.add(Call.WebServiceProvider_Invoke_AFTER); } catch (final Exception e) { // handler chain fault processing would happen here throw e; } return returnValue; } } }