001: /**
002: * JOnAS: Java(TM) Open Application Server
003: * Copyright (C) 1999-2005 Bull S.A.
004: * Contact: jonas-team@objectweb.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: JStatelessFactory.java 10088 2007-03-21 08:33:00Z durieuxp $
023: * --------------------------------------------------------------------------
024: */package org.objectweb.jonas_ejb.container;
025:
026: import java.lang.reflect.InvocationTargetException;
027: import java.rmi.RemoteException;
028: import java.util.ArrayList;
029: import java.util.List;
030: import java.util.ListIterator;
031:
032: import javax.ejb.EJBException;
033: import javax.ejb.SessionBean;
034: import javax.ejb.TimedObject;
035: import javax.ejb.Timer;
036: import javax.ejb.TimerService;
037: import javax.naming.Context;
038: import javax.transaction.Status;
039: import javax.transaction.SystemException;
040: import javax.transaction.Transaction;
041:
042: import org.objectweb.jonas_timer.TraceTimer;
043:
044: import org.objectweb.jonas_ejb.deployment.api.SessionStatelessDesc;
045:
046: import org.objectweb.util.monolog.api.BasicLevel;
047:
048: /**
049: * This class is a factory for a Session Stateless Bean.
050: * @author Philippe Durieux
051: */
052: public class JStatelessFactory extends JSessionFactory {
053:
054: /**
055: * instance pool management (list of available JSessionContext objects)
056: * Contexts are pooled only for Stateless Session beans because for Stateful
057: * sessions a newInstance() is required by the spec.
058: * We don't need synchronizedList here, because anyway, we
059: * synchronize already everywhere.
060: */
061: protected List bctxlist = new ArrayList();
062:
063: // service endpoint home
064: protected JServiceEndpointHome sehome = null;
065:
066: protected int instanceCount = 0;
067:
068: // initial value for pool size
069: protected int minPoolSize = 0;
070:
071: // nb max of instances in pool
072: protected int maxCacheSize = 0;
073:
074: private static final int MAX_NB_RETRY = 2;
075:
076: /**
077: * constructor
078: * @param dd Session Stateless Deployment Descriptor
079: * @param cnt Container where the bean is defined
080: */
081: public JStatelessFactory(SessionStatelessDesc dd, JContainer cont) {
082: super (dd, cont);
083: if (TraceEjb.isDebugIc()) {
084: TraceEjb.interp.log(BasicLevel.DEBUG, "");
085: }
086:
087: // Create the ServiceEndpointHome if defined in DD
088: Class sehomeclass = null;
089: String clname = dd.getFullWrpSEHomeName();
090: if (clname != null) {
091: try {
092: sehomeclass = cont.getClassLoader().loadClass(clname);
093: } catch (ClassNotFoundException e) {
094: throw new EJBException(ejbname + " Cannot load "
095: + clname, e);
096: }
097: if (TraceEjb.isDebugIc()) {
098: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname + ": "
099: + clname + " loaded");
100: }
101: try {
102: // new JServiceEndpointHome(dd, this)
103: int nbp = 2;
104: Class[] ptype = new Class[nbp];
105: Object[] pobj = new Object[nbp];
106: ptype[0] = org.objectweb.jonas_ejb.deployment.api.SessionStatelessDesc.class;
107: pobj[0] = (Object) dd;
108: ptype[1] = org.objectweb.jonas_ejb.container.JStatelessFactory.class;
109: pobj[1] = (Object) this ;
110: sehome = (JServiceEndpointHome) sehomeclass
111: .getConstructor(ptype).newInstance(pobj);
112: } catch (Exception e) {
113: throw new EJBException(ejbname
114: + " Cannot create serviceEndpointHome ", e);
115: }
116: // register it in JNDI
117: try {
118: sehome.register();
119: } catch (Exception e) {
120: throw new EJBException(ejbname
121: + " Cannot register serviceEndpointHome ", e);
122: }
123: }
124:
125: isStateful = false;
126: maxCacheSize = dd.getCacheMax();
127: minPoolSize = dd.getPoolMin();
128: if (maxCacheSize > 0 && TraceEjb.isDebugSwapper()) {
129: TraceEjb.swapper.log(BasicLevel.DEBUG, " maxCacheSize = "
130: + maxCacheSize);
131: }
132: singleswitch = (timeout == 0) && dd.isSingleton();
133: }
134:
135: /**
136: * Init pool of instances.
137: */
138: public void initInstancePool() {
139: JStatelessSwitch ss = null;
140: // Create a SessionSwitch if needed
141: if (minPoolSize != 0 || singleswitch) {
142: try {
143: ss = (JStatelessSwitch) createEJB();
144: } catch (RemoteException e) {
145: TraceEjb.logger.log(BasicLevel.ERROR, ejbname
146: + " cannot create new session", e);
147: throw new EJBException(ejbname
148: + " Cannot create session for pool: ", e);
149: }
150: }
151: // pre-allocate a set of JSessionContext (bean instances)
152: if (minPoolSize != 0) {
153: TraceEjb.interp.log(BasicLevel.INFO,
154: "pre-allocate a set of " + minPoolSize
155: + " stateless session instances");
156: Context bnctx = setComponentContext(); // for createNewInstance
157: // Set also the EJB classloader as context classloader
158: ClassLoader old = Thread.currentThread()
159: .getContextClassLoader();
160: Thread.currentThread().setContextClassLoader(
161: this .myClassLoader());
162: try {
163: synchronized (bctxlist) {
164: for (int i = 0; i < minPoolSize; i++) {
165: JSessionContext ctx = null;
166: try {
167: // must pass a session switch (for ejbCreate)
168: ctx = createNewInstance(ss);
169: if (ctx != null) {
170: bctxlist.add(ctx);
171: } else {
172: // Should never go here.
173: TraceEjb.logger.log(BasicLevel.ERROR,
174: ejbname + " null instance");
175: }
176: } catch (Exception e) {
177: TraceEjb.logger
178: .log(
179: BasicLevel.ERROR,
180: ejbname
181: + " cannot create new instance",
182: e);
183: throw new EJBException(
184: ejbname
185: + " Cannot init pool of instances ",
186: e);
187: }
188: }
189: }
190: } finally {
191: // reset the context classloader
192: Thread.currentThread().setContextClassLoader(old);
193:
194: // reset java:comp/env
195: resetComponentContext(bnctx);
196: }
197: }
198: // Remove Temporary session switch
199: if (ss != null && !singleswitch) {
200: ss.stopTimer();
201: ss.noLongerUsed();
202: }
203: }
204:
205: public JServiceEndpointHome getSEHome() {
206: return sehome;
207: }
208:
209: // ---------------------------------------------------------------
210: // BeanFactory implementation
211: // ---------------------------------------------------------------
212:
213: /**
214: * @return the Instance pool size for this Ejb
215: */
216: public int getPoolSize() {
217: return bctxlist.size();
218: }
219:
220: /**
221: * Reduce number of instances in memory in the free list we reduce to the
222: * minPoolSize
223: */
224: public void reduceCache() {
225: // reduce the pool to the minPoolSize
226: synchronized (bctxlist) {
227: while (bctxlist.size() > minPoolSize) {
228: ListIterator i = bctxlist.listIterator();
229: if (i.hasNext()) {
230: i.next();
231: i.remove();
232: instanceCount--;
233: }
234: }
235: }
236: }
237:
238: // ---------------------------------------------------------------
239: // other public methods
240: // ---------------------------------------------------------------
241:
242: /**
243: * Obtains the TimerService associated for this Bean
244: * @return a JTimerService instance.
245: */
246: public TimerService getTimerService() {
247: if (myTimerService == null) {
248: // TODO : Check that instance implements TimedObject ?
249: myTimerService = new JTimerService(this );
250: }
251: return myTimerService;
252: }
253:
254: /**
255: * Creates a new Session Stateless
256: * @return the new JSessionSwitch
257: */
258: public JSessionSwitch createNewSession() throws RemoteException {
259: if (TraceEjb.isDebugIc()) {
260: TraceEjb.interp.log(BasicLevel.DEBUG, "");
261: }
262: JStatelessSwitch bs = new JStatelessSwitch(this );
263: return bs;
264: }
265:
266: /**
267: * @return a SessionContext for Stateless Session Bean
268: */
269: public JSessionContext getJContext(JSessionSwitch ss) {
270: if (TraceEjb.isDebugIc()) {
271: TraceEjb.interp.log(BasicLevel.DEBUG, "");
272: }
273: JStatelessContext bctx = null;
274:
275: // try to find a free context in the pool
276: synchronized (bctxlist) {
277: try {
278: ListIterator i = bctxlist.listIterator();
279: if (i.hasNext()) {
280: bctx = (JStatelessContext) i.next();
281: i.remove();
282: if (bctx != null) { // workaround for an old bug.
283: bctx.initSessionContext(ss);
284: } else {
285: TraceEjb.interp.log(BasicLevel.ERROR,
286: "null elt in bctxlist");
287: }
288: }
289: } catch (IndexOutOfBoundsException ex) {
290: // pool is empty
291: }
292: }
293:
294: if (bctx == null) {
295: // create a new one if pool empty
296: try {
297: bctx = createNewInstance(ss);
298: } catch (Exception e) {
299: TraceEjb.logger.log(BasicLevel.ERROR, "exception:" + e);
300: throw new EJBException("Cannot create a new instance",
301: e);
302: }
303: }
304: if (maxCacheSize != 0 && TraceEjb.isDebugSwapper()) {
305: TraceEjb.swapper.log(BasicLevel.DEBUG, "nb instances "
306: + getCacheSize());
307: }
308: return bctx;
309: }
310:
311: /**
312: * Called after each method call
313: * @param ctx the Session Context
314: */
315: public void releaseJContext(JContext ctx) {
316: if (ctx == null) {
317: TraceEjb.swapper.log(BasicLevel.ERROR, "null ctx!");
318: return;
319: }
320: JStatelessContext bctx = (JStatelessContext) ctx;
321:
322: // if the max number of instances is reached
323: // we can reduce the number of instances
324: synchronized (bctxlist) {
325: if (maxCacheSize != 0 && instanceCount > maxCacheSize) {
326: if (TraceEjb.isDebugSwapper()) {
327: TraceEjb.swapper.log(BasicLevel.DEBUG,
328: "too many instances :" + instanceCount
329: + ", max=" + maxCacheSize);
330: }
331: instanceCount--;
332: } else {
333: bctxlist.add(bctx);
334: }
335: }
336: if (maxCacheSize != 0 && TraceEjb.isDebugSwapper()) {
337: TraceEjb.swapper.log(BasicLevel.DEBUG, "nb instances "
338: + getCacheSize());
339: }
340: }
341:
342: /**
343: * Notify a timeout for this bean
344: * @param timer timer whose expiration caused this notification.
345: */
346: public void notifyTimeout(Timer timer) {
347: if (stopped) {
348: TraceEjb.mdb.log(BasicLevel.WARN, "Container stopped");
349: return;
350: }
351: if (TraceTimer.isDebug()) {
352: TraceTimer.logger.log(BasicLevel.DEBUG, "");
353: }
354:
355: // Get a JStatelessSwitch (similar to createEJB)
356: // In fact, we don't need the Local and Remote objects here.
357: // This object is just needed to initilize the SessionContext.
358: JSessionSwitch bs = null;
359: if (sessionList.size() > 0) {
360: if (TraceEjb.isDebugIc()) {
361: TraceEjb.interp.log(BasicLevel.DEBUG,
362: "get a JStatelessSwitch from the pool");
363: }
364: bs = (JSessionSwitch) sessionList.remove(0);
365: // must re-export the remote object in the Orb since EJBObjects
366: // to avoids a fail when object is put in the pool.
367: JSessionRemote remote = bs.getRemote();
368: if (remote != null) {
369: if (!remote.exportObject()) {
370: TraceEjb.logger.log(BasicLevel.ERROR,
371: "bad JSessionSwitch found in pool.");
372: }
373: }
374: } else {
375: if (TraceEjb.isDebugIc()) {
376: TraceEjb.interp.log(BasicLevel.DEBUG,
377: "create a new JStatelessSwitch");
378: }
379: try {
380: bs = new JStatelessSwitch(this );
381: } catch (RemoteException e) {
382: TraceEjb.logger.log(BasicLevel.ERROR,
383: "Notify Timeout - Unexpected : ", e);
384: return;
385: }
386: }
387: //EJBInvocation ejbInv = new EJBInvocation();
388: //ejbInv.methodPermissionSignature = getEjbTimeoutSignature();
389: //ejbInv.arguments = new Object [] {timer};
390:
391: for (int nbretry = 0; nbretry < MAX_NB_RETRY; nbretry++) {
392: RequestCtx rctx = preInvoke(getTimerTxAttribute());
393: JSessionContext bctx = null;
394: try {
395: bctx = bs.getICtx(rctx.currTx);
396: TimedObject instance = (TimedObject) bctx.getInstance();
397: //ejbInv.bean = (EnterpriseBean) instance;
398: checkSecurity(null);
399: instance.ejbTimeout(timer);
400:
401: // Must release bctx (not done any longer in timeoutExpired)
402: //bctx.setRemoved(); ???
403: releaseJContext(bctx);
404:
405: if (rctx.currTx == null
406: || (rctx.currTx.getStatus() != Status.STATUS_MARKED_ROLLBACK)) {
407: break;
408: }
409: } catch (EJBException e) {
410: rctx.sysExc = e;
411: throw e;
412: } catch (RuntimeException e) {
413: rctx.sysExc = e;
414: throw new EJBException(
415: "RuntimeException thrown by an enterprise Bean",
416: e);
417: } catch (Error e) {
418: rctx.sysExc = e;
419: throw new EJBException(
420: "Error thrown by an enterprise Bean" + e);
421: } catch (RemoteException e) {
422: rctx.sysExc = e;
423: throw new EJBException("Remote Exception raised:", e);
424: } catch (SystemException e) {
425: rctx.sysExc = e;
426: throw new EJBException(
427: "Cannot get transaction status:", e);
428: } finally {
429: postInvoke(rctx);
430: }
431: }
432:
433: // release everything - Exactly as if the session timeout had
434: // expired.
435: bs.timeoutExpired(null);
436: }
437:
438: /**
439: * @return min pool size for Jmx
440: */
441: public int getMinPoolSize() {
442: return minPoolSize;
443: }
444:
445: /**
446: * @return max cache size for Jmx
447: */
448: public int getMaxCacheSize() {
449: return maxCacheSize;
450: }
451:
452: /**
453: * @return current cache size ( = nb of instance created) for Jmx
454: */
455: public int getCacheSize() {
456: return instanceCount;
457: }
458:
459: // ---------------------------------------------------------------
460: // private methods
461: // ---------------------------------------------------------------
462:
463: /**
464: * Create a new instance of the bean and its StatelessContext
465: */
466: private JStatelessContext createNewInstance(JSessionSwitch ss)
467: throws Exception {
468: if (TraceEjb.isDebugIc()) {
469: TraceEjb.interp.log(BasicLevel.DEBUG, "");
470: }
471: // create the bean instance
472: SessionBean bean = null;
473: try {
474: bean = (SessionBean) beanclass.newInstance();
475: } catch (InstantiationException e) {
476: TraceEjb.logger.log(BasicLevel.ERROR, ejbname
477: + " cannot instantiate session bean");
478: throw e;
479: } catch (IllegalAccessException e) {
480: TraceEjb.logger.log(BasicLevel.ERROR, ejbname
481: + " Cannot instantiate SessionBean");
482: throw e;
483: }
484:
485: // create a new StatelessContext and bind it to the instance
486: JStatelessContext bctx = new JStatelessContext(this , bean);
487: bean.setSessionContext(bctx);
488: bctx.setState(1);
489: bctx.initSessionContext(ss);
490: // call ejbCreate on the instance
491: if (TraceEjb.isDebugIc()) {
492: TraceEjb.interp.log(BasicLevel.DEBUG,
493: "call ejbCreate on the instance");
494: }
495: try {
496: beanclass.getMethod("ejbCreate", (Class[]) null).invoke(
497: bean, (Object[]) null);
498: } catch (InvocationTargetException ite) {
499: // get the root cause
500: Throwable t = ite.getTargetException();
501: TraceEjb.logger.log(BasicLevel.ERROR, ejbname
502: + " cannot invoke ejbCreate on Stateless Session "
503: + t.getMessage(), t);
504: } catch (Exception e) {
505: TraceEjb.logger.log(BasicLevel.ERROR, ejbname
506: + " cannot call ejbCreate on Stateless Session "
507: + e.getMessage(), e);
508: }
509: bctx.setState(2);
510:
511: // We always create an instance even if the max is reached
512: // see releaseJContext
513: synchronized (bctxlist) {
514: instanceCount++;
515: }
516: return bctx;
517: }
518:
519: /*
520: * Make sense only for entities
521: */
522: public void storeInstances(Transaction tx) {
523: // unused
524: }
525: }
|