/* * 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.ivm; import org.apache.openejb.ApplicationException; import org.apache.openejb.BeanContext; import org.apache.openejb.BeanType; import org.apache.openejb.InterfaceType; import org.apache.openejb.InvalidateReferenceException; import org.apache.openejb.OpenEJBException; import org.apache.openejb.OpenEJBRuntimeException; import org.apache.openejb.ProxyInfo; import org.apache.openejb.SystemException; import org.apache.openejb.core.ServerFederation; import org.apache.openejb.core.entity.EntityEjbHomeHandler; import org.apache.openejb.core.managed.ManagedHomeHandler; import org.apache.openejb.core.singleton.SingletonEjbHomeHandler; import org.apache.openejb.core.stateful.StatefulEjbHomeHandler; import org.apache.openejb.core.stateless.StatelessEjbHomeHandler; import org.apache.openejb.spi.ApplicationServer; import org.apache.openejb.threads.task.CUCallable; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.openejb.util.proxy.LocalBeanProxyFactory; import org.apache.openejb.util.proxy.ProxyManager; import javax.ejb.AccessLocalException; import javax.ejb.EJBAccessException; import javax.ejb.EJBException; import javax.ejb.EJBHome; import javax.ejb.Handle; import java.io.ObjectStreamException; import java.io.Serializable; import java.lang.reflect.Method; import java.rmi.AccessException; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; public abstract class EjbHomeProxyHandler extends BaseEjbProxyHandler { public static final Logger logger = Logger.getInstance(LogCategory.OPENEJB, "org.apache.openejb.util.resources"); private final Map<String, MethodType> dispatchTable; private static enum MethodType { CREATE, FIND, HOME_HANDLE, META_DATA, REMOVE } public EjbHomeProxyHandler(final BeanContext beanContext, final InterfaceType interfaceType, final List<Class> interfaces, final Class mainInterface) { super(beanContext, null, interfaceType, interfaces, mainInterface); dispatchTable = new HashMap<String, MethodType>(); dispatchTable.put("create", MethodType.CREATE); dispatchTable.put("getEJBMetaData", MethodType.META_DATA); dispatchTable.put("getHomeHandle", MethodType.HOME_HANDLE); dispatchTable.put("remove", MethodType.REMOVE); if (interfaceType.isHome()) { final Class homeInterface = beanContext.getInterface(interfaceType); final Method[] methods = homeInterface.getMethods(); for (final Method method : methods) { if (method.getName().startsWith("create")) { dispatchTable.put(method.getName(), MethodType.CREATE); } else if (method.getName().startsWith("find")) { dispatchTable.put(method.getName(), MethodType.FIND); } } } } @Override public void invalidateReference() { throw new IllegalStateException("A home reference must never be invalidated!"); } protected static EjbHomeProxyHandler createHomeHandler(final BeanContext beanContext, final InterfaceType interfaceType, final List<Class> interfaces, final Class mainInterface) { switch (beanContext.getComponentType()) { case STATEFUL: return new StatefulEjbHomeHandler(beanContext, interfaceType, interfaces, mainInterface); case STATELESS: return new StatelessEjbHomeHandler(beanContext, interfaceType, interfaces, mainInterface); case SINGLETON: return new SingletonEjbHomeHandler(beanContext, interfaceType, interfaces, mainInterface); case MANAGED: return new ManagedHomeHandler(beanContext, interfaceType, interfaces, mainInterface); case CMP_ENTITY: case BMP_ENTITY: return new EntityEjbHomeHandler(beanContext, interfaceType, interfaces, mainInterface); default: throw new IllegalStateException("Component type does not support rpc interfaces: " + beanContext.getComponentType()); } } public static Object createHomeProxy(final BeanContext beanContext, final InterfaceType interfaceType) { return createHomeProxy(beanContext, interfaceType, null, interfaceType.isRemote() ? beanContext.getRemoteInterface() : beanContext.getLocalInterface()); } public static Object createHomeProxy(final BeanContext beanContext, final InterfaceType interfaceType, final List<Class> objectInterfaces, final Class mainInterface) { if (!interfaceType.isHome()) { throw new IllegalArgumentException("InterfaceType is not a Home type: " + interfaceType); } try { final EjbHomeProxyHandler handler = createHomeHandler(beanContext, interfaceType, objectInterfaces, mainInterface); final List<Class> proxyInterfaces = new ArrayList<Class>(2); final Class homeInterface = beanContext.getInterface(interfaceType); proxyInterfaces.add(homeInterface); proxyInterfaces.add(IntraVmProxy.class); if (BeanType.STATEFUL.equals(beanContext.getComponentType()) || BeanType.MANAGED.equals(beanContext.getComponentType())) { proxyInterfaces.add(BeanContext.Removable.class); } return ProxyManager.newProxyInstance(proxyInterfaces.toArray(new Class[proxyInterfaces.size()]), handler); } catch (final Exception e) { throw new OpenEJBRuntimeException("Can't create EJBHome stub" + e.getMessage(), e); } } public Object createProxy(final Object primaryKey, final Class mainInterface) { try { final InterfaceType objectInterfaceType = this.interfaceType.getCounterpart(); final BeanType type = getBeanContext().getComponentType(); final EjbObjectProxyHandler handler = newEjbObjectHandler(getBeanContext(), primaryKey, objectInterfaceType, getInterfaces(), mainInterface); // TODO Is it correct for ManagedBean injection via managed bean class? if ((InterfaceType.LOCALBEAN.equals(objectInterfaceType) || getBeanContext().getComponentType().equals(BeanType.MANAGED)) && !getBeanContext().isDynamicallyImplemented()) { return LocalBeanProxyFactory.constructProxy(handler.getBeanContext().get(BeanContext.ProxyClass.class).getProxy(), handler); } else { final List<Class> proxyInterfaces = new ArrayList<Class>(handler.getInterfaces().size() + 2); proxyInterfaces.addAll(handler.getInterfaces()); proxyInterfaces.add(Serializable.class); proxyInterfaces.add(IntraVmProxy.class); if (BeanType.STATEFUL.equals(type) || BeanType.MANAGED.equals(type)) { proxyInterfaces.add(BeanContext.Removable.class); } return ProxyManager.newProxyInstance(proxyInterfaces.toArray(new Class[proxyInterfaces.size()]), handler); } } catch (final IllegalAccessException iae) { throw new OpenEJBRuntimeException("Could not create IVM proxy for " + getInterfaces().get(0), iae); } } protected abstract EjbObjectProxyHandler newEjbObjectHandler(BeanContext beanContext, Object pk, InterfaceType interfaceType, List<Class> interfaces, Class mainInterface); @Override protected Object _invoke(final Object proxy, final Class interfce, final Method method, final Object[] args) throws Throwable { final String methodName = method.getName(); if (logger.isDebugEnabled()) { logger.debug("EjbHomeProxyHandler: invoking method " + methodName + " on " + deploymentID); } try { final Object retValue; final MethodType operation = dispatchTable.get(methodName); if (operation == null) { retValue = homeMethod(interfce, method, args, proxy); } else { switch (operation) { /*-- CREATE ------------- <HomeInterface>.create(<x>) ---*/ case CREATE: retValue = create(interfce, method, args, proxy); break; case FIND: retValue = findX(interfce, method, args, proxy); break; /*-- GET EJB METADATA ------ EJBHome.getEJBMetaData() ---*/ case META_DATA: retValue = getEJBMetaData(method, args, proxy); break; /*-- GET HOME HANDLE -------- EJBHome.getHomeHandle() ---*/ case HOME_HANDLE: retValue = getHomeHandle(method, args, proxy); break; /*-- REMOVE ------------------------ EJBHome.remove() ---*/ case REMOVE: { final Class type = method.getParameterTypes()[0]; /*-- HANDLE ------- EJBHome.remove(Handle handle) ---*/ if (Handle.class.isAssignableFrom(type)) { retValue = removeWithHandle(interfce, method, args, proxy); } else { /*-- PRIMARY KEY ----- EJBHome.remove(Object key) ---*/ retValue = removeByPrimaryKey(interfce, method, args, proxy); } break; } default: throw new OpenEJBRuntimeException("Inconsistent internal state: value " + operation + " for operation " + methodName); } } if (logger.isDebugEnabled()) { logger.debug("EjbHomeProxyHandler: finished invoking method " + method.getName() + ". Return value:" + retValue); } return retValue; /* * The ire is thrown by the container system and propagated by * the server to the stub. */ } catch (final RemoteException re) { if (interfaceType.isLocal()) { throw new EJBException(re.getMessage()).initCause(re.detail); } else { throw re; } } catch (final InvalidateReferenceException ire) { Throwable cause = ire.getRootCause(); if (cause instanceof RemoteException && interfaceType.isLocal()) { final RemoteException re = (RemoteException) cause; final Throwable detail = re.detail != null ? re.detail : re; cause = new EJBException(re.getMessage()).initCause(detail); } throw cause; /* * Application exceptions must be reported dirctly to the client. They * do not impact the viability of the proxy. */ } catch (final ApplicationException ae) { final Throwable exc = ae.getRootCause() != null ? ae.getRootCause() : ae; if (exc instanceof EJBAccessException) { if (interfaceType.isBusiness()) { throw exc; } else { if (interfaceType.isLocal()) { throw (AccessLocalException) new AccessLocalException(exc.getMessage()).initCause(exc); } else { try { throw new AccessException(exc.getMessage()).initCause(exc); } catch (final IllegalStateException vmbug) { // Sun JDK 1.5 bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4871783 // bug affects using initCause on any RemoteException subclasses in Sun 1.5_07 or lower throw new AccessException(exc.getMessage(), (Exception) exc); } } } } throw exc; /* * A system exception would be highly unusual and would indicate a sever * problem with the container system. */ } catch (final SystemException se) { if (interfaceType.isLocal()) { throw new EJBException("Container has suffered a SystemException").initCause(se.getRootCause()); } else { throw new RemoteException("Container has suffered a SystemException", se.getRootCause()); } } catch (final OpenEJBException oe) { if (interfaceType.isLocal()) { throw new EJBException("Unknown Container Exception").initCause(oe.getRootCause()); } else { throw new RemoteException("Unknown Container Exception", oe.getRootCause()); } } catch (final Throwable t) { logger.debug("EjbHomeProxyHandler: finished invoking method " + method.getName() + " with exception:" + t, t); throw t; } } /*-------------------------------------------------*/ /* Home interface methods */ /*-------------------------------------------------*/ protected Object homeMethod(final Class interfce, final Method method, final Object[] args, final Object proxy) throws Throwable { checkAuthorization(method); final BeanContext beanContext = getBeanContext(); if (beanContext.isAsynchronous(method)) { return beanContext.getModuleContext() .getAppContext() .getAsynchronousPool() .invoke(new CUCallable<Object>(new Callable<Object>() { @Override public Object call() throws Exception { try { return homeMethodInvoke(interfce, method, args); } catch (final ApplicationException ae) { logger.error("EjbHomeProxyHandler: Asynchronous call to '" + interfce.getSimpleName() + "' on '" + method.getName() + "' failed", ae); throw ae; } } }), method.getReturnType() == Void.TYPE); } else { return homeMethodInvoke(interfce, method, args); } } private Object homeMethodInvoke(final Class interfce, final Method method, final Object[] args) throws OpenEJBException { return container.invoke(deploymentID, interfaceType, interfce, method, args, null); } protected Object create(final Class interfce, final Method method, final Object[] args, final Object proxy) throws Throwable { if (container.getBeanContext(deploymentID) == null) { final BeanContext bc = getBeanContext(); synchronized (bc.getId()) { if (container.getBeanContext(deploymentID) == null) { container.deploy(bc); container.start(bc); } } } final ProxyInfo proxyInfo = (ProxyInfo) container.invoke(deploymentID, interfaceType, interfce, method, args, null); assert proxyInfo != null : "Container returned a null ProxyInfo: ContainerID=" + container.getContainerID(); return createProxy(proxyInfo.getPrimaryKey(), getMainInterface()); } protected abstract Object findX(Class interfce, Method method, Object[] args, Object proxy) throws Throwable; /*-------------------------------------------------*/ /* EJBHome methods */ /*-------------------------------------------------*/ protected Object getEJBMetaData(final Method method, final Object[] args, final Object proxy) throws Throwable { checkAuthorization(method); final IntraVmMetaData metaData = new IntraVmMetaData(getBeanContext().getHomeInterface(), getBeanContext().getRemoteInterface(), getBeanContext().getPrimaryKeyClass(), getBeanContext().getComponentType()); metaData.setEJBHome((EJBHome) proxy); return metaData; } protected Object getHomeHandle(final Method method, final Object[] args, final Object proxy) throws Throwable { checkAuthorization(method); return new IntraVmHandle(proxy); } @Override public ProxyInfo getProxyInfo() { if (getMainInterface() == null) { throw new IllegalStateException("no main interface"); } return new ProxyInfo(getBeanContext(), null, getBeanContext().getInterfaces(interfaceType), interfaceType, getMainInterface()); } @Override protected Object _writeReplace(final Object proxy) throws ObjectStreamException { /* * If the proxy is being copied between bean instances in a RPC * call we use the IntraVmArtifact */ if (IntraVmCopyMonitor.isIntraVmCopyOperation()) { return new IntraVmArtifact(proxy); /* * If the proxy is referenced by a stateful bean that is being * passivated by the container we allow this object to be serialized. */ } else if (IntraVmCopyMonitor.isStatefulPassivationOperation()) { return proxy; /* * If the proxy is being copied between class loaders * we allow this object to be serialized. */ } else if (IntraVmCopyMonitor.isCrossClassLoaderOperation()) { return proxy; /* * If the proxy is serialized outside the core container system, * we allow the application server to handle it. */ } else if (!interfaceType.isRemote()) { return proxy; } else { final ApplicationServer applicationServer = ServerFederation.getApplicationServer(); return applicationServer.getEJBHome(this.getProxyInfo()); } } protected Object removeWithHandle(final Class interfce, final Method method, final Object[] args, final Object proxy) throws Throwable { final IntraVmHandle handle = (IntraVmHandle) args[0]; final Object primKey = handle.getPrimaryKey(); EjbObjectProxyHandler stub; try { stub = (EjbObjectProxyHandler) ProxyManager.getInvocationHandler(handle.getEJBObject()); } catch (final IllegalArgumentException e) { stub = null; } container.invoke(deploymentID, interfaceType, interfce, method, args, primKey); /* * This operation takes care of invalidating all the EjbObjectProxyHanders associated with * the same RegistryId. See this.createProxy(). */ if (stub != null) { invalidateAllHandlers(stub.getRegistryId()); } return null; } protected abstract Object removeByPrimaryKey(Class interfce, Method method, Object[] args, Object proxy) throws Throwable; }