490 lines
17 KiB
Java
490 lines
17 KiB
Java
/*
|
|
* 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.
|
|
*
|
|
* $Header:$
|
|
*/
|
|
package org.apache.beehive.controls.system.ejb;
|
|
|
|
import java.io.IOException;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.net.URLConnection;
|
|
import java.util.Hashtable;
|
|
|
|
import javax.ejb.CreateException;
|
|
import javax.ejb.EJBObject;
|
|
import javax.ejb.FinderException;
|
|
import javax.ejb.Handle;
|
|
import javax.naming.InitialContext;
|
|
import javax.naming.NamingException;
|
|
import javax.naming.NameNotFoundException;
|
|
import javax.rmi.PortableRemoteObject;
|
|
|
|
import org.apache.beehive.controls.api.ControlException;
|
|
import org.apache.beehive.controls.api.bean.ControlImplementation;
|
|
import org.apache.beehive.controls.api.bean.Extensible;
|
|
import org.apache.beehive.controls.api.bean.ControlExtension;
|
|
import org.apache.beehive.controls.api.context.Context;
|
|
import org.apache.beehive.controls.api.context.ControlBeanContext;
|
|
import org.apache.beehive.controls.api.context.ControlBeanContext.LifeCycle;
|
|
import org.apache.beehive.controls.api.context.ResourceContext;
|
|
import org.apache.beehive.controls.api.context.ResourceContext.ResourceEvents;
|
|
import org.apache.beehive.controls.api.events.EventHandler;
|
|
import org.apache.commons.logging.LogFactory;
|
|
import org.apache.commons.logging.Log;
|
|
|
|
/**
|
|
* The Enterprise Java Bean Control implementation class
|
|
*/
|
|
@ControlImplementation
|
|
public abstract class EJBControlImpl
|
|
implements EJBControl, Extensible, java.io.Serializable {
|
|
|
|
static final long serialVersionUID = 1L;
|
|
private static final Log LOGGER = LogFactory.getLog(EJBControlImpl.class);
|
|
|
|
public static final int SESSION_BEAN = 1;
|
|
public static final int ENTITY_BEAN = 2;
|
|
|
|
public static final String JNDI_GLOBAL_PREFIX = "jndi:";
|
|
public static final String JNDI_APPSCOPED_PREFIX = "java:comp/env/";
|
|
|
|
@EventHandler(field = "context", eventSet = LifeCycle.class, eventName = "onCreate")
|
|
public void onCreate() {
|
|
|
|
if (LOGGER.isDebugEnabled()) {
|
|
LOGGER.debug("Enter: onCreate()");
|
|
}
|
|
|
|
EJBHome ejbHome = context.getControlPropertySet(EJBHome.class);
|
|
if (ejbHome == null)
|
|
throw new ControlException("No @EJBHome property is defined");
|
|
|
|
_jndiName = ejbHome.jndiName();
|
|
if (_jndiName == null || _jndiName.length() == 0) {
|
|
String ejbLink = ejbHome.ejbLink();
|
|
if (ejbLink.length() == 0) {
|
|
//
|
|
// Should be caught by the compiler
|
|
//
|
|
throw new ControlException("Either the jndiName() or ejbLink() member of @EJBHome must be defined.");
|
|
}
|
|
|
|
//
|
|
// Generate a unique local jndi name to associate w/ the link,
|
|
// based upon the local control service uri and control id
|
|
//
|
|
_jndiName = JNDI_APPSCOPED_PREFIX + EJBInfo.getEJBRefName(context.getControlInterface());
|
|
}
|
|
|
|
// Obtain the JCX interface and identify the home/remote
|
|
// interfaces.
|
|
EJBInfo beanInfo = new EJBInfo(context.getControlInterface());
|
|
_homeInterface = beanInfo._homeInterface;
|
|
_beanInterface = beanInfo._beanInterface;
|
|
_beanType = beanInfo._beanType.equals("Session") ? SESSION_BEAN : ENTITY_BEAN;
|
|
}
|
|
|
|
protected static boolean methodThrows(Method m, Class exceptionClass) {
|
|
Class [] exceptions = m.getExceptionTypes();
|
|
for (int j = 0; j < exceptions.length; j++)
|
|
if (exceptionClass.isAssignableFrom(exceptions[j]))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
protected boolean isHomeMethod(Method m) {
|
|
return m.getDeclaringClass().isAssignableFrom(_homeInterface);
|
|
}
|
|
|
|
/**
|
|
* Return true if the method is from the ControlBean.
|
|
* @param m Method to check.
|
|
*/
|
|
protected boolean isControlBeanMethod(Method m) {
|
|
return (m.getDeclaringClass().getAnnotation(ControlExtension.class) != null);
|
|
}
|
|
|
|
/**
|
|
* Map a control bean method to an EJB method.
|
|
*
|
|
* @param m The control bean method.
|
|
* @return The corresponding method of the EJB.
|
|
*/
|
|
protected Method mapControlBeanMethodToEJB(Method m) {
|
|
Method ejbMethod = findEjbMethod(m, _homeInterface);
|
|
if (ejbMethod == null) {
|
|
if (_beanInstance == null) {
|
|
_beanInstance = resolveBeanInstance();
|
|
if (_beanInstance == null) {
|
|
throw new ControlException("Unable to resolve bean instance");
|
|
}
|
|
}
|
|
ejbMethod = findEjbMethod(m, _beanInstance.getClass());
|
|
if (ejbMethod == null) {
|
|
throw new ControlException("Unable to map ejb control interface method to EJB method: " + m.getName());
|
|
}
|
|
}
|
|
return ejbMethod;
|
|
}
|
|
|
|
/**
|
|
* Find the method which has the same signature in the specified class.
|
|
*
|
|
* @param controlBeanMethod Method signature find.
|
|
* @param ejbInterface Class to search for method signature.
|
|
* @return Method from ejbInterface if found, null if not found.
|
|
*/
|
|
protected Method findEjbMethod(Method controlBeanMethod, Class ejbInterface) {
|
|
final String cbMethodName = controlBeanMethod.getName();
|
|
final Class cbMethodReturnType = controlBeanMethod.getReturnType();
|
|
final Class[] cbMethodParams = controlBeanMethod.getParameterTypes();
|
|
|
|
Method[] ejbMethods = ejbInterface.getMethods();
|
|
for (Method m : ejbMethods) {
|
|
if (!cbMethodName.equals(m.getName())
|
|
|| !cbMethodReturnType.equals(m.getReturnType())) {
|
|
continue;
|
|
}
|
|
|
|
Class[] params = m.getParameterTypes();
|
|
if (cbMethodParams.length == params.length) {
|
|
int i;
|
|
for (i = 0; i < cbMethodParams.length; i++) {
|
|
if (cbMethodParams[i] != params[i]) break;
|
|
}
|
|
if (i == cbMethodParams.length)
|
|
return m;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected static boolean isCreateMethod(Method m) {
|
|
return methodThrows(m, CreateException.class);
|
|
}
|
|
|
|
protected static boolean isFinderMethod(Method m) {
|
|
if (!m.getName().startsWith("find")) // EJB enforced pattern
|
|
return false;
|
|
return methodThrows(m, FinderException.class);
|
|
}
|
|
|
|
protected boolean isSelectorMethod(Method m) {
|
|
return isHomeMethod(m) && m.getReturnType().equals(_beanInterface);
|
|
}
|
|
|
|
static protected boolean isRemoveMethod(Method m) {
|
|
if (!m.getName().equals("remove") || (m.getParameterTypes().length != 0))
|
|
return false;
|
|
else return true;
|
|
}
|
|
|
|
protected Object homeNarrow(Object obj) {
|
|
if (javax.ejb.EJBHome.class.isAssignableFrom(_homeInterface))
|
|
return PortableRemoteObject.narrow(obj, _homeInterface);
|
|
else return obj;
|
|
}
|
|
|
|
protected Object beanNarrow(Object obj) {
|
|
if (javax.ejb.EJBObject.class.isAssignableFrom(_beanInterface))
|
|
return PortableRemoteObject.narrow(obj, _beanInterface);
|
|
else return obj;
|
|
}
|
|
|
|
/*
|
|
* This method is implemented by the appropriate bean type-specific
|
|
* control to provide auto create/find semantics for bean instances.
|
|
*
|
|
* IT SHOULD ALWAYS THROW A RUNTIME EXCEPTION WITH A TYPE-SPECIFIC
|
|
* ERROR MESSAGE IF RESOLUTION CANNOT TAKE PLACE. IT SHOULD _NEVER_
|
|
* HAVE A NON-EXCEPTED RETURN WHERE _beanInstance == null.
|
|
*/
|
|
abstract protected Object resolveBeanInstance();
|
|
|
|
//
|
|
// Is there is a cached EJB handle associated with this bean, then
|
|
// is it to restore the associate EJB object reference.
|
|
//
|
|
protected Object resolveBeanInstanceFromHandle() {
|
|
if (_beanHandle == null)
|
|
return null;
|
|
|
|
try {
|
|
return _beanHandle.getEJBObject();
|
|
}
|
|
catch (java.rmi.RemoteException re) {
|
|
throw new ControlException("Unable to convert EJB handle to object", re);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Attempts to save the contents of the current bean reference in persisted
|
|
// control state. Returns true if state could be saved, false otherwise
|
|
//
|
|
protected boolean saveBeanInstance() {
|
|
// Nothing to save == success
|
|
if (_beanInstance == null)
|
|
return true;
|
|
|
|
//
|
|
// Save using a bean handle, but handles only exist for remote objects.
|
|
//
|
|
if (_beanInstance instanceof EJBObject) {
|
|
try {
|
|
_beanHandle = ((EJBObject) _beanInstance).getHandle();
|
|
}
|
|
catch (java.rmi.RemoteException re) {
|
|
throw new ControlException("Unable to get bean instance from handle", re);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// This is called whenever a bean reference is being dropped, and is the
|
|
// provides an opportunity to reset cached state or release non-persisted
|
|
// resources associated with the instance.
|
|
//
|
|
protected void releaseBeanInstance(boolean alreadyRemoved) {
|
|
_beanInstance = null;
|
|
_beanHandle = null;
|
|
}
|
|
|
|
protected javax.naming.Context getInitialContext() throws NamingException {
|
|
if (_context == null) {
|
|
//If naming context information is provided, then use that to create the initial context
|
|
JNDIContextEnv env = context.getControlPropertySet(JNDIContextEnv.class);
|
|
String value = env.contextFactory();
|
|
if (value != null && value.length() > 0) {
|
|
Hashtable<String, String> ht = new Hashtable<String, String>();
|
|
ht.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, value);
|
|
value = env.providerURL();
|
|
if (value != null && value.length() > 0)
|
|
ht.put(javax.naming.Context.PROVIDER_URL, value);
|
|
value = env.principal();
|
|
if (value != null && value.length() > 0)
|
|
ht.put(javax.naming.Context.SECURITY_PRINCIPAL, value);
|
|
value = env.credentials();
|
|
if (value != null && value.length() > 0)
|
|
ht.put(javax.naming.Context.SECURITY_CREDENTIALS, value);
|
|
_context = new InitialContext(ht);
|
|
}
|
|
else {
|
|
_context = new InitialContext();
|
|
}
|
|
}
|
|
return _context;
|
|
|
|
}
|
|
|
|
@EventHandler(field = "resourceContext", eventSet = ResourceEvents.class, eventName = "onAcquire")
|
|
public void onAcquire() {
|
|
|
|
if (LOGGER.isDebugEnabled()) {
|
|
LOGGER.debug("Enter: onAquire()");
|
|
}
|
|
|
|
// Compute the home instance cache lookup key. The Service URI must
|
|
// be taken into account because different services use different
|
|
// class loaders. The JNDI home must be taken into account because
|
|
// it is possible to be a remote client of the same bean type on two
|
|
// different providers.
|
|
//
|
|
if (_homeInstance == null) {
|
|
// If JNDI name is an URL using a JNDI protocol
|
|
if (_jndiName.toLowerCase().startsWith(JNDI_GLOBAL_PREFIX)) {
|
|
|
|
try {
|
|
URL url = new URL(_jndiName);
|
|
URLConnection jndiConn = url.openConnection();
|
|
_homeInstance = jndiConn.getContent();
|
|
}
|
|
catch (MalformedURLException mue) {
|
|
throw new ControlException(_jndiName + " is not a valid JNDI URL", mue);
|
|
}
|
|
catch (IOException ioe) {
|
|
throw new ControlException("Error during JNDI lookup from " + _jndiName, ioe);
|
|
}
|
|
}
|
|
else {
|
|
|
|
try {
|
|
_homeInstance = lookupHomeInstance();
|
|
}
|
|
catch (NamingException ne) {
|
|
throw new ControlException("Error during JNDI lookup from " + _jndiName, ne);
|
|
}
|
|
}
|
|
|
|
if (!_homeInterface.isAssignableFrom(_homeInstance.getClass())) {
|
|
throw new ControlException("JNDI lookup of " + _jndiName + " failed to return an instance of " + _homeInterface);
|
|
}
|
|
}
|
|
}
|
|
|
|
@EventHandler(field = "resourceContext", eventSet = ResourceEvents.class, eventName = "onRelease")
|
|
public void onRelease() {
|
|
if (LOGGER.isDebugEnabled()) {
|
|
LOGGER.debug("Enter: onRelease()");
|
|
}
|
|
releaseBeanInstance(false);
|
|
}
|
|
|
|
//@EventHandler(field="context", eventSet=LifeCycle.class, eventName="onReset")
|
|
public void onReset() {
|
|
_lastException = null;
|
|
// other work in onRelease(), delivered prior to reset event
|
|
}
|
|
|
|
/**
|
|
* Extensible.invoke
|
|
* Handles all extended interface methods (i.e. EJB home and remote
|
|
* interface invocation)
|
|
*/
|
|
public Object invoke(Method m, Object[] args) throws Throwable {
|
|
Object retval = null;
|
|
|
|
if (isControlBeanMethod(m)) {
|
|
m = mapControlBeanMethodToEJB(m);
|
|
}
|
|
|
|
if (isHomeMethod(m)) {
|
|
try {
|
|
retval = m.invoke(_homeInstance, args);
|
|
}
|
|
catch (Exception e) {
|
|
Throwable t = e;
|
|
if (e instanceof InvocationTargetException)
|
|
t = ((InvocationTargetException) e).getTargetException();
|
|
_lastException = t;
|
|
throw t;
|
|
}
|
|
|
|
// If the method was successful and returns an instance of
|
|
// the bean interface class, then reset the target instance.
|
|
if (isSelectorMethod(m)) {
|
|
releaseBeanInstance(false);
|
|
retval = beanNarrow(retval);
|
|
_beanInstance = retval;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
// is remote / bean interface
|
|
else {
|
|
if (_beanInstance == null)
|
|
_beanInstance = resolveBeanInstance();
|
|
|
|
// By convention, the below cond should never be true. The bean
|
|
// type-specific resolve should throw an appropriate exception
|
|
// that is more specific. This is a safety net.
|
|
if (_beanInstance == null)
|
|
throw new ControlException("Unable to resolve bean instance");
|
|
|
|
try {
|
|
return m.invoke(_beanInstance, args);
|
|
}
|
|
catch (Exception e) {
|
|
Throwable t = e;
|
|
if (e instanceof InvocationTargetException)
|
|
t = ((InvocationTargetException) e).getTargetException();
|
|
_lastException = t;
|
|
|
|
throw t;
|
|
}
|
|
finally {
|
|
// Handle remove method properly
|
|
if (isRemoveMethod(m))
|
|
releaseBeanInstance(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* EJBControl.getEJBHomeInstance()
|
|
*/
|
|
public Object getEJBHomeInstance() {
|
|
return _homeInstance;
|
|
}
|
|
|
|
/**
|
|
* EJBControl.getEJBBeanInstance()
|
|
*/
|
|
public boolean hasEJBBeanInstance() {
|
|
return _beanInstance != null;
|
|
}
|
|
|
|
/**
|
|
* EJBControl.getEJBBeanInstance()
|
|
*/
|
|
public Object getEJBBeanInstance() {
|
|
return _beanInstance;
|
|
}
|
|
|
|
/**
|
|
* EJBControl.getEJBException()
|
|
*/
|
|
public Throwable getEJBException() {
|
|
return _lastException;
|
|
}
|
|
|
|
/**
|
|
* Do a JNDI lookup for the home instance of the ejb. Attempt the lookup
|
|
* first using an app scoped jndi prefix, if not successful, attempt without
|
|
* the prefix.
|
|
*
|
|
* @return HomeInstance object.
|
|
* @throws NamingException If HomeInstance cannot be found in JNDI registry.
|
|
*/
|
|
private Object lookupHomeInstance() throws NamingException {
|
|
|
|
javax.naming.Context ctx = getInitialContext();
|
|
|
|
try {
|
|
return ctx.lookup(_jndiName);
|
|
}
|
|
catch (NameNotFoundException nnfe) {
|
|
// attempt again without application scoping
|
|
if (!_jndiName.startsWith(JNDI_APPSCOPED_PREFIX)) {
|
|
throw nnfe;
|
|
}
|
|
}
|
|
return ctx.lookup(_jndiName.substring(JNDI_APPSCOPED_PREFIX.length()));
|
|
}
|
|
|
|
@Context
|
|
ControlBeanContext context;
|
|
|
|
@Context
|
|
ResourceContext resourceContext;
|
|
|
|
protected Class _controlInterface;
|
|
protected Class _homeInterface;
|
|
protected Class _beanInterface;
|
|
protected int _beanType;
|
|
protected String _jndiName;
|
|
protected Handle _beanHandle;
|
|
protected transient javax.naming.Context _context; // don't persist
|
|
protected transient Throwable _lastException; // don't persist
|
|
protected transient Object _beanInstance; // don't persist
|
|
protected transient Object _homeInstance; // don't persist
|
|
}
|