blob: 82c73ca1d24b3a23c318fcb2209340895efc3c49 [file] [log] [blame]
/**
* 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.core.ivm.naming.InitContextFactory;
import org.apache.openejb.config.ConfigurationFactory;
import org.apache.openejb.config.EjbModule;
import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.assembler.classic.ProxyFactoryInfo;
import org.apache.openejb.assembler.classic.TransactionServiceInfo;
import org.apache.openejb.assembler.classic.SecurityServiceInfo;
import org.apache.openejb.assembler.classic.StatelessSessionContainerInfo;
import org.apache.openejb.assembler.classic.EjbJarInfo;
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.apache.openejb.RpcContainer;
import org.apache.openejb.InterfaceType;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import javax.interceptor.Interceptors;
import javax.jws.WebService;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.WebServiceContext;
import javax.annotation.Resource;
import javax.ejb.SessionContext;
import java.util.ArrayList;
import java.util.Set;
import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.Arrays;
import java.lang.reflect.Method;
/**
* 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.
*
* 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.
*
* 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: 996774 $ $Date: 2010-09-14 10:43:38 +0300 (Tue, 14 Sep 2010) $
*/
public class JaxWsInvocationTest extends TestCase {
public void testWsInvocations() throws Exception {
System.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY, InitContextFactory.class.getName());
ConfigurationFactory config = new ConfigurationFactory();
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));
EjbJarInfo ejbJar = config.configureApplication(buildTestApp());
assembler.createApplication(ejbJar);
ContainerSystem containerSystem = SystemInstance.get().getComponent(ContainerSystem.class);
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.
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.
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
Object[] args = new Object[]{messageContext, wsProviderInterceptor};
// Let's grab the container as the Web Service Provider would do and
// perform an invocation
RpcContainer container = (RpcContainer) beanContext.getContainer();
Method echoMethod = EchoServiceEndpoint.class.getMethod("echo", String.class);
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(Call... expectedCalls) {
List expected = Arrays.asList(expectedCalls);
assertEquals(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() {
EjbJar ejbJar = new EjbJar();
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})
@WebService
public static class EchoBean {
@Resource
private SessionContext ctx;
@Resource
private WebServiceContext wsContext;
@AroundInvoke
public Object invoke(InvocationContext context) throws Exception {
/**
* For JAX-WS invocations context.getContextData() must return the
* JAX-WS MessageContex. 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 getContextData()
* for the duration of this call.
*/
MessageContext messageContext = (MessageContext)context.getContextData();
junit.framework.Assert.assertNotNull("message context should not be null", messageContext);
junit.framework.Assert.assertTrue("the Web Service Provider's message context should be used", messageContext instanceof FakeMessageContext);
// Try to get JAX-RPC context, should throw an exception since it's JAX-WS
try {
ctx.getMessageContext();
junit.framework.Assert.fail("Did not throw exception");
} catch (IllegalStateException e) {
// that's expected since it's JAX-WS
}
// test @Resource WebServiceContext injection
junit.framework.Assert.assertNotNull("web service context should not be null", wsContext);
junit.framework.Assert.assertEquals("msg context should be the smae", messageContext, wsContext.getMessageContext());
junit.framework.Assert.assertFalse("user in role 'foo'", wsContext.isUserInRole("foo"));
junit.framework.Assert.assertNotNull("user principal", wsContext.getUserPrincipal());
calls.add(Call.Bean_Invoke_BEFORE);
Object o = context.proceed();
calls.add(Call.Bean_Invoke_AFTER);
return o;
}
public String echo(String data){
calls.add(Call.Bean_Invoke);
return data;
}
}
@WebService
public static interface EchoServiceEndpoint {
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(InvocationContext context) throws Exception {
// Track this call so we can assert proper interceptor order
calls.add(Call.EjbInterceptor_Invoke_BEFORE);
Object o = context.proceed();
calls.add(Call.EjbInterceptor_Invoke_AFTER);
return o;
}
}
private static String join(String delimeter, List items) {
StringBuffer sb = new StringBuffer();
for (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-WS spec and supplied to us in the container.invoke method
* per the OpenEJB-WebServiceProvider agreement
*/
public static class FakeMessageContext implements MessageContext {
private Map map = new HashMap();
public void clear() {
map.clear();
}
public boolean containsKey(Object key) {
return map.containsKey(key);
}
public boolean containsValue(Object value) {
return map.containsValue(value);
}
public Set<Entry<String, Object>> entrySet() {
return map.entrySet();
}
public Object get(Object key) {
return map.get(key);
}
public boolean isEmpty() {
return map.isEmpty();
}
public Set<String> keySet() {
return map.keySet();
}
public Object put(String key, Object value) {
return map.put(key, value);
}
public void putAll(Map<? extends String, ? extends Object> t) {
map.putAll(t);
}
public Object remove(Object key) {
return map.remove(key);
}
public int size() {
return map.size();
}
public Collection<Object> values() {
return map.values();
}
public Scope getScope(String arg0) {
return null;
}
public void setScope(String arg0, Scope arg1) {
}
}
/**
* This object would be supplied by the Web Service Provider
* as per the OpenEJB-WebServiceProvider agreement and serves
* two purposes:
*
* 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)
*
* 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.
*
* 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.
*
* 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.
*
* 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:
*
* public Object <METHOD-NAME> (InvocationContext ctx) throws Exception
*
* 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(Object... args) {
this.args = args;
}
@AroundInvoke
public Object invoke(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);
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 (Exception e) {
// handler chain fault processing would happen here
throw e;
}
return returnValue;
}
}
}