001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.controls.system.ejb;
020:
021: import java.io.IOException;
022: import java.lang.reflect.InvocationTargetException;
023: import java.lang.reflect.Method;
024: import java.net.MalformedURLException;
025: import java.net.URL;
026: import java.net.URLConnection;
027: import java.util.Hashtable;
028:
029: import javax.ejb.CreateException;
030: import javax.ejb.EJBObject;
031: import javax.ejb.FinderException;
032: import javax.ejb.Handle;
033: import javax.naming.InitialContext;
034: import javax.naming.NamingException;
035: import javax.naming.NameNotFoundException;
036: import javax.rmi.PortableRemoteObject;
037:
038: import org.apache.beehive.controls.api.ControlException;
039: import org.apache.beehive.controls.api.bean.ControlImplementation;
040: import org.apache.beehive.controls.api.bean.Extensible;
041: import org.apache.beehive.controls.api.bean.ControlExtension;
042: import org.apache.beehive.controls.api.context.Context;
043: import org.apache.beehive.controls.api.context.ControlBeanContext;
044: import org.apache.beehive.controls.api.context.ControlBeanContext.LifeCycle;
045: import org.apache.beehive.controls.api.context.ResourceContext;
046: import org.apache.beehive.controls.api.context.ResourceContext.ResourceEvents;
047: import org.apache.beehive.controls.api.events.EventHandler;
048: import org.apache.commons.logging.LogFactory;
049: import org.apache.commons.logging.Log;
050:
051: /**
052: * The Enterprise Java Bean Control implementation class
053: */
054: @ControlImplementation
055: public abstract class EJBControlImpl implements EJBControl, Extensible,
056: java.io.Serializable {
057:
058: static final long serialVersionUID = 1L;
059: private static final Log LOGGER = LogFactory
060: .getLog(EJBControlImpl.class);
061:
062: public static final int SESSION_BEAN = 1;
063: public static final int ENTITY_BEAN = 2;
064:
065: public static final String JNDI_GLOBAL_PREFIX = "jndi:";
066: public static final String JNDI_APPSCOPED_PREFIX = "java:comp/env/";
067:
068: @EventHandler(field="context",eventSet=LifeCycle.class,eventName="onCreate")
069: public void onCreate() {
070:
071: if (LOGGER.isDebugEnabled()) {
072: LOGGER.debug("Enter: onCreate()");
073: }
074:
075: EJBHome ejbHome = context.getControlPropertySet(EJBHome.class);
076: if (ejbHome == null)
077: throw new ControlException(
078: "No @EJBHome property is defined");
079:
080: _jndiName = ejbHome.jndiName();
081: if (_jndiName == null || _jndiName.length() == 0) {
082: String ejbLink = ejbHome.ejbLink();
083: if (ejbLink.length() == 0) {
084: //
085: // Should be caught by the compiler
086: //
087: throw new ControlException(
088: "Either the jndiName() or ejbLink() member of @EJBHome must be defined.");
089: }
090:
091: //
092: // Generate a unique local jndi name to associate w/ the link,
093: // based upon the local control service uri and control id
094: //
095: _jndiName = JNDI_APPSCOPED_PREFIX
096: + EJBInfo.getEJBRefName(context
097: .getControlInterface());
098: }
099:
100: // Obtain the JCX interface and identify the home/remote
101: // interfaces.
102: EJBInfo beanInfo = new EJBInfo(context.getControlInterface());
103: _homeInterface = beanInfo._homeInterface;
104: _beanInterface = beanInfo._beanInterface;
105: _beanType = beanInfo._beanType.equals("Session") ? SESSION_BEAN
106: : ENTITY_BEAN;
107: }
108:
109: protected static boolean methodThrows(Method m, Class exceptionClass) {
110: Class[] exceptions = m.getExceptionTypes();
111: for (int j = 0; j < exceptions.length; j++)
112: if (exceptionClass.isAssignableFrom(exceptions[j]))
113: return true;
114: return false;
115: }
116:
117: protected boolean isHomeMethod(Method m) {
118: return m.getDeclaringClass().isAssignableFrom(_homeInterface);
119: }
120:
121: /**
122: * Return true if the method is from the ControlBean.
123: * @param m Method to check.
124: */
125: protected boolean isControlBeanMethod(Method m) {
126: return (m.getDeclaringClass().getAnnotation(
127: ControlExtension.class) != null);
128: }
129:
130: /**
131: * Map a control bean method to an EJB method.
132: *
133: * @param m The control bean method.
134: * @return The corresponding method of the EJB.
135: */
136: protected Method mapControlBeanMethodToEJB(Method m) {
137: Method ejbMethod = findEjbMethod(m, _homeInterface);
138: if (ejbMethod == null) {
139: if (_beanInstance == null) {
140: _beanInstance = resolveBeanInstance();
141: if (_beanInstance == null) {
142: throw new ControlException(
143: "Unable to resolve bean instance");
144: }
145: }
146: ejbMethod = findEjbMethod(m, _beanInstance.getClass());
147: if (ejbMethod == null) {
148: throw new ControlException(
149: "Unable to map ejb control interface method to EJB method: "
150: + m.getName());
151: }
152: }
153: return ejbMethod;
154: }
155:
156: /**
157: * Find the method which has the same signature in the specified class.
158: *
159: * @param controlBeanMethod Method signature find.
160: * @param ejbInterface Class to search for method signature.
161: * @return Method from ejbInterface if found, null if not found.
162: */
163: protected Method findEjbMethod(Method controlBeanMethod,
164: Class ejbInterface) {
165: final String cbMethodName = controlBeanMethod.getName();
166: final Class cbMethodReturnType = controlBeanMethod
167: .getReturnType();
168: final Class[] cbMethodParams = controlBeanMethod
169: .getParameterTypes();
170:
171: Method[] ejbMethods = ejbInterface.getMethods();
172: for (Method m : ejbMethods) {
173: if (!cbMethodName.equals(m.getName())
174: || !cbMethodReturnType.equals(m.getReturnType())) {
175: continue;
176: }
177:
178: Class[] params = m.getParameterTypes();
179: if (cbMethodParams.length == params.length) {
180: int i;
181: for (i = 0; i < cbMethodParams.length; i++) {
182: if (cbMethodParams[i] != params[i])
183: break;
184: }
185: if (i == cbMethodParams.length)
186: return m;
187: }
188: }
189: return null;
190: }
191:
192: protected static boolean isCreateMethod(Method m) {
193: return methodThrows(m, CreateException.class);
194: }
195:
196: protected static boolean isFinderMethod(Method m) {
197: if (!m.getName().startsWith("find")) // EJB enforced pattern
198: return false;
199: return methodThrows(m, FinderException.class);
200: }
201:
202: protected boolean isSelectorMethod(Method m) {
203: return isHomeMethod(m)
204: && m.getReturnType().equals(_beanInterface);
205: }
206:
207: static protected boolean isRemoveMethod(Method m) {
208: if (!m.getName().equals("remove")
209: || (m.getParameterTypes().length != 0))
210: return false;
211: else
212: return true;
213: }
214:
215: protected Object homeNarrow(Object obj) {
216: if (javax.ejb.EJBHome.class.isAssignableFrom(_homeInterface))
217: return PortableRemoteObject.narrow(obj, _homeInterface);
218: else
219: return obj;
220: }
221:
222: protected Object beanNarrow(Object obj) {
223: if (javax.ejb.EJBObject.class.isAssignableFrom(_beanInterface))
224: return PortableRemoteObject.narrow(obj, _beanInterface);
225: else
226: return obj;
227: }
228:
229: /*
230: * This method is implemented by the appropriate bean type-specific
231: * control to provide auto create/find semantics for bean instances.
232: *
233: * IT SHOULD ALWAYS THROW A RUNTIME EXCEPTION WITH A TYPE-SPECIFIC
234: * ERROR MESSAGE IF RESOLUTION CANNOT TAKE PLACE. IT SHOULD _NEVER_
235: * HAVE A NON-EXCEPTED RETURN WHERE _beanInstance == null.
236: */
237: abstract protected Object resolveBeanInstance();
238:
239: //
240: // Is there is a cached EJB handle associated with this bean, then
241: // is it to restore the associate EJB object reference.
242: //
243: protected Object resolveBeanInstanceFromHandle() {
244: if (_beanHandle == null)
245: return null;
246:
247: try {
248: return _beanHandle.getEJBObject();
249: } catch (java.rmi.RemoteException re) {
250: throw new ControlException(
251: "Unable to convert EJB handle to object", re);
252: }
253: }
254:
255: //
256: // Attempts to save the contents of the current bean reference in persisted
257: // control state. Returns true if state could be saved, false otherwise
258: //
259: protected boolean saveBeanInstance() {
260: // Nothing to save == success
261: if (_beanInstance == null)
262: return true;
263:
264: //
265: // Save using a bean handle, but handles only exist for remote objects.
266: //
267: if (_beanInstance instanceof EJBObject) {
268: try {
269: _beanHandle = ((EJBObject) _beanInstance).getHandle();
270: } catch (java.rmi.RemoteException re) {
271: throw new ControlException(
272: "Unable to get bean instance from handle", re);
273: }
274:
275: return true;
276: }
277: return false;
278: }
279:
280: //
281: // This is called whenever a bean reference is being dropped, and is the
282: // provides an opportunity to reset cached state or release non-persisted
283: // resources associated with the instance.
284: //
285: protected void releaseBeanInstance(boolean alreadyRemoved) {
286: _beanInstance = null;
287: _beanHandle = null;
288: }
289:
290: protected javax.naming.Context getInitialContext()
291: throws NamingException {
292: if (_context == null) {
293: //If naming context information is provided, then use that to create the initial context
294: JNDIContextEnv env = context
295: .getControlPropertySet(JNDIContextEnv.class);
296: String value = env.contextFactory();
297: if (value != null && value.length() > 0) {
298: Hashtable<String, String> ht = new Hashtable<String, String>();
299: ht.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
300: value);
301: value = env.providerURL();
302: if (value != null && value.length() > 0)
303: ht.put(javax.naming.Context.PROVIDER_URL, value);
304: value = env.principal();
305: if (value != null && value.length() > 0)
306: ht.put(javax.naming.Context.SECURITY_PRINCIPAL,
307: value);
308: value = env.credentials();
309: if (value != null && value.length() > 0)
310: ht.put(javax.naming.Context.SECURITY_CREDENTIALS,
311: value);
312: _context = new InitialContext(ht);
313: } else {
314: _context = new InitialContext();
315: }
316: }
317: return _context;
318:
319: }
320:
321: @EventHandler(field="resourceContext",eventSet=ResourceEvents.class,eventName="onAcquire")
322: public void onAcquire() {
323:
324: if (LOGGER.isDebugEnabled()) {
325: LOGGER.debug("Enter: onAquire()");
326: }
327:
328: // Compute the home instance cache lookup key. The Service URI must
329: // be taken into account because different services use different
330: // class loaders. The JNDI home must be taken into account because
331: // it is possible to be a remote client of the same bean type on two
332: // different providers.
333: //
334: if (_homeInstance == null) {
335: // If JNDI name is an URL using a JNDI protocol
336: if (_jndiName.toLowerCase().startsWith(JNDI_GLOBAL_PREFIX)) {
337:
338: try {
339: URL url = new URL(_jndiName);
340: URLConnection jndiConn = url.openConnection();
341: _homeInstance = jndiConn.getContent();
342: } catch (MalformedURLException mue) {
343: throw new ControlException(_jndiName
344: + " is not a valid JNDI URL", mue);
345: } catch (IOException ioe) {
346: throw new ControlException(
347: "Error during JNDI lookup from "
348: + _jndiName, ioe);
349: }
350: } else {
351:
352: try {
353: _homeInstance = lookupHomeInstance();
354: } catch (NamingException ne) {
355: throw new ControlException(
356: "Error during JNDI lookup from "
357: + _jndiName, ne);
358: }
359: }
360:
361: if (!_homeInterface.isAssignableFrom(_homeInstance
362: .getClass())) {
363: throw new ControlException("JNDI lookup of "
364: + _jndiName
365: + " failed to return an instance of "
366: + _homeInterface);
367: }
368: }
369: }
370:
371: @EventHandler(field="resourceContext",eventSet=ResourceEvents.class,eventName="onRelease")
372: public void onRelease() {
373: if (LOGGER.isDebugEnabled()) {
374: LOGGER.debug("Enter: onRelease()");
375: }
376: releaseBeanInstance(false);
377: }
378:
379: //@EventHandler(field="context", eventSet=LifeCycle.class, eventName="onReset")
380: public void onReset() {
381: _lastException = null;
382: // other work in onRelease(), delivered prior to reset event
383: }
384:
385: /**
386: * Extensible.invoke
387: * Handles all extended interface methods (i.e. EJB home and remote
388: * interface invocation)
389: */
390: public Object invoke(Method m, Object[] args) throws Throwable {
391: Object retval = null;
392:
393: if (isControlBeanMethod(m)) {
394: m = mapControlBeanMethodToEJB(m);
395: }
396:
397: if (isHomeMethod(m)) {
398: try {
399: retval = m.invoke(_homeInstance, args);
400: } catch (Exception e) {
401: Throwable t = e;
402: if (e instanceof InvocationTargetException)
403: t = ((InvocationTargetException) e)
404: .getTargetException();
405: _lastException = t;
406: throw t;
407: }
408:
409: // If the method was successful and returns an instance of
410: // the bean interface class, then reset the target instance.
411: if (isSelectorMethod(m)) {
412: releaseBeanInstance(false);
413: retval = beanNarrow(retval);
414: _beanInstance = retval;
415: }
416:
417: return retval;
418: }
419: // is remote / bean interface
420: else {
421: if (_beanInstance == null)
422: _beanInstance = resolveBeanInstance();
423:
424: // By convention, the below cond should never be true. The bean
425: // type-specific resolve should throw an appropriate exception
426: // that is more specific. This is a safety net.
427: if (_beanInstance == null)
428: throw new ControlException(
429: "Unable to resolve bean instance");
430:
431: try {
432: return m.invoke(_beanInstance, args);
433: } catch (Exception e) {
434: Throwable t = e;
435: if (e instanceof InvocationTargetException)
436: t = ((InvocationTargetException) e)
437: .getTargetException();
438: _lastException = t;
439:
440: throw t;
441: } finally {
442: // Handle remove method properly
443: if (isRemoveMethod(m))
444: releaseBeanInstance(true);
445: }
446: }
447: }
448:
449: /**
450: * EJBControl.getEJBHomeInstance()
451: */
452: public Object getEJBHomeInstance() {
453: return _homeInstance;
454: }
455:
456: /**
457: * EJBControl.getEJBBeanInstance()
458: */
459: public boolean hasEJBBeanInstance() {
460: return _beanInstance != null;
461: }
462:
463: /**
464: * EJBControl.getEJBBeanInstance()
465: */
466: public Object getEJBBeanInstance() {
467: return _beanInstance;
468: }
469:
470: /**
471: * EJBControl.getEJBException()
472: */
473: public Throwable getEJBException() {
474: return _lastException;
475: }
476:
477: /**
478: * Do a JNDI lookup for the home instance of the ejb. Attempt the lookup
479: * first using an app scoped jndi prefix, if not successful, attempt without
480: * the prefix.
481: *
482: * @return HomeInstance object.
483: * @throws NamingException If HomeInstance cannot be found in JNDI registry.
484: */
485: private Object lookupHomeInstance() throws NamingException {
486:
487: javax.naming.Context ctx = getInitialContext();
488:
489: try {
490: return ctx.lookup(_jndiName);
491: } catch (NameNotFoundException nnfe) {
492: // attempt again without application scoping
493: if (!_jndiName.startsWith(JNDI_APPSCOPED_PREFIX)) {
494: throw nnfe;
495: }
496: }
497: return ctx.lookup(_jndiName.substring(JNDI_APPSCOPED_PREFIX
498: .length()));
499: }
500:
501: @Context
502: ControlBeanContext context;
503:
504: @Context
505: ResourceContext resourceContext;
506:
507: protected Class _controlInterface;
508: protected Class _homeInterface;
509: protected Class _beanInterface;
510: protected int _beanType;
511: protected String _jndiName;
512: protected Handle _beanHandle;
513: protected transient javax.naming.Context _context; // don't persist
514: protected transient Throwable _lastException; // don't persist
515: protected transient Object _beanInstance; // don't persist
516: protected transient Object _homeInstance; // don't persist
517: }
|