001: /*
002: * JFox - The most lightweight Java EE Application Server!
003: * more details please visit http://www.huihoo.org/jfox or http://www.jfox.org.cn.
004: *
005: * JFox is licenced and re-distributable under GNU LGPL.
006: */
007: package org.jfox.ejb3;
008:
009: import java.io.Serializable;
010: import java.lang.reflect.Method;
011: import java.util.ArrayList;
012: import java.util.Arrays;
013: import java.util.Collection;
014: import java.util.Collections;
015: import java.util.Date;
016: import java.util.HashMap;
017: import java.util.Iterator;
018: import java.util.List;
019: import java.util.Map;
020: import java.util.concurrent.ConcurrentHashMap;
021: import java.util.concurrent.ScheduledFuture;
022: import java.util.concurrent.ScheduledThreadPoolExecutor;
023: import java.util.concurrent.TimeUnit;
024: import javax.ejb.EJBException;
025: import javax.ejb.MessageDriven;
026: import javax.ejb.Stateful;
027: import javax.ejb.Stateless;
028: import javax.ejb.Timer;
029: import javax.ejb.TimerService;
030: import javax.naming.Binding;
031: import javax.naming.Context;
032: import javax.naming.NameAlreadyBoundException;
033: import javax.naming.NameClassPair;
034: import javax.naming.NameNotFoundException;
035: import javax.naming.NamingEnumeration;
036: import javax.naming.NamingException;
037: import javax.transaction.SystemException;
038: import javax.transaction.TransactionManager;
039:
040: import org.apache.log4j.Logger;
041: import org.jfox.ejb3.event.EJBLoadedComponentEvent;
042: import org.jfox.ejb3.event.EJBUnloadedComponentEvent;
043: import org.jfox.ejb3.invocation.InterceptorsEJBInvocationHandler;
044: import org.jfox.ejb3.invocation.SecurityEJBInvocationHandler;
045: import org.jfox.ejb3.invocation.ThreadContextEJBInvocationHandler;
046: import org.jfox.ejb3.invocation.TransactionEJBInvocationHandler;
047: import org.jfox.ejb3.naming.ContextAdapter;
048: import org.jfox.ejb3.naming.InitialContextFactoryImpl;
049: import org.jfox.ejb3.timer.EJBTimerTask;
050: import org.jfox.ejb3.transaction.JTATransactionManager;
051: import org.jfox.framework.annotation.Constant;
052: import org.jfox.framework.annotation.Service;
053: import org.jfox.framework.component.ComponentContext;
054: import org.jfox.framework.component.Module;
055: import org.jfox.framework.event.ModuleEvent;
056: import org.jfox.framework.event.ModuleListener;
057: import org.jfox.framework.event.ModuleLoadingEvent;
058: import org.jfox.framework.event.ModuleUnloadedEvent;
059: import org.jfox.jms.JMSConnectionFactory;
060: import org.jfox.jms.MessageService;
061: import org.jfox.mvc.SessionContext;
062:
063: /**
064: * �支� Local/Stateless Session Bean, Local MDB
065: * �时,该 Container 也承担了 NamingContainer 的能力
066: * <p/>
067: * å¿…é¡»ä¿?è¯? SimpleEJB3Container ç¬¬ä¸€ä¸ªåŠ è½½ï¼Œå?¦åˆ™ï¼Œæ— 法监å?¬åˆ° ModuleEventï¼Œè€Œæ— æ³• load ejb
068: *
069: * @author <a href="mailto:jfox.young@gmail.com">Young Yang</a>
070: */
071: @Service(id="EJB3Container",singleton=true,active=true)
072: public class SimpleEJB3Container implements EJBContainer,
073: ModuleListener {
074:
075: protected Logger logger = Logger
076: .getLogger(SimpleEJB3Container.class);
077:
078: // Transaction Manager
079: private JTATransactionManager tm = null;
080:
081: // default Transaction timeout
082: @Constant(type=Integer.class,value="$jta_transaction_timeout")
083: private int transactionTimeout = 60; // default transaction timeout 60 seconds
084:
085: // TimerServer
086: private ContainerTimerService timerService = null;
087:
088: // JMS ConnectionFactory
089: private MessageService messageService = null;
090:
091: // container naming context, also is initialcontext for IntialContextFactoryImpl
092: private Context namingContext = null;
093:
094: /**
095: * 执行 ejb invocation 的 chain
096: */
097: private final List<EJBInvocationHandler> invocationChain = new ArrayList<EJBInvocationHandler>();
098:
099: /**
100: * ejb name => EJBBucket
101: */
102: private final Map<String, EJBBucket> bucketMap = new ConcurrentHashMap<String, EJBBucket>();
103:
104: /**
105: * jndi Resource
106: */
107: private final Map<String, Object> jndiMap = new ConcurrentHashMap<String, Object>();
108:
109: private ComponentContext componentContext;
110:
111: public SimpleEJB3Container() {
112: invocationChain.add(new ThreadContextEJBInvocationHandler());
113: invocationChain.add(new SecurityEJBInvocationHandler());
114: invocationChain.add(new TransactionEJBInvocationHandler());
115: invocationChain.add(new InterceptorsEJBInvocationHandler());
116: }
117:
118: public void postContruct(ComponentContext componentContext) {
119: this .componentContext = componentContext;
120: }
121:
122: public void postInject() {
123: // new NamingContext, then set to InitialContextFactoryImpl
124: namingContext = new ContainerNamingContext();
125: InitialContextFactoryImpl.setInitialContext(namingContext);
126:
127: tm = JTATransactionManager.getIntsance();
128: tm.setDefaultTransactionTimeout(getTransactionTimeout());
129: timerService = new ContainerTimerService();
130: messageService = new JMSConnectionFactory();
131:
132: // 将 TransactionManager 注册 java:/TransactionManager
133: try {
134: tm.setTransactionTimeout(getTransactionTimeout());
135: getNamingContext().bind("java:/TransactionManager", tm);
136: getNamingContext().bind("java:/UserTransaction", tm);
137: getNamingContext().bind("defaultcf", messageService);
138: } catch (NamingException e) {
139: logger.fatal("Bind TransactionManager error.", e);
140: System.exit(1);
141: } catch (SystemException e) {
142: logger.fatal("Failed to setTransactionTimeout!", e);
143: System.exit(1);
144: }
145: }
146:
147: public boolean preUnregister(ComponentContext context) {
148: tm.stop();
149: timerService.stop();
150: try {
151: namingContext.close();
152: } catch (NamingException e) {
153: logger.warn("EJBContainer NamingContext close exception.",
154: e);
155: }
156: jndiMap.clear();
157: return true;
158: }
159:
160: public void postUnregister() {
161:
162: }
163:
164: public int getTransactionTimeout() {
165: return transactionTimeout;
166: }
167:
168: public void setTransactionTimeout(int transactionTimeout) {
169: this .transactionTimeout = transactionTimeout;
170: if (tm != null) {
171: tm.setDefaultTransactionTimeout(transactionTimeout);
172: }
173: }
174:
175: /**
176: * 监å?¬ Module äº‹ä»¶ï¼Œæ ¹æ?® Module çš„ load/unload 事件æ?¥åŠ 载其ä¸çš„ EJB
177: *
178: * @param moduleEvent module event
179: */
180: public void moduleChanged(ModuleEvent moduleEvent) {
181: Module module = moduleEvent.getModule();
182: if (moduleEvent instanceof ModuleLoadingEvent) {
183: // 监� ModuleLoadingEvent,以��能够在Action之�完� EJB 装载,从而在 Action 实例化的时候,注入
184: // 对于 SYSTEM_MODULE,� SystemModule.start,�了特殊处�
185: EJBBucket[] buckets = loadEJB(module);
186: for (EJBBucket bucket : buckets) {
187: bucketMap.put(bucket.getEJBName(), bucket);
188: }
189: } else if (moduleEvent instanceof ModuleUnloadedEvent) {
190: unloadEJB(module);
191: }
192: }
193:
194: protected EJBBucket[] loadEJB(Module module) {
195: List<EJBBucket> buckets = new ArrayList<EJBBucket>();
196:
197: // stateless
198: Class[] statelessBeans = module.getModuleClassLoader()
199: .findClassAnnotatedWith(Stateless.class);
200: for (Class beanClass : statelessBeans) {
201: EJBBucket bucket = new StatelessBucket(
202: (EJBContainer) componentContext
203: .getMyselfComponent(), beanClass, module);
204: buckets.add(bucket);
205: //fireEvent, 以便XFire�以 register Endpoint
206: componentContext
207: .fireComponentEvent(new EJBLoadedComponentEvent(
208: componentContext.getComponentId(), bucket));
209: // bind to jndi
210: try {
211: for (String mappedName : bucket.getMappedNames()) {
212: this .getNamingContext().bind(mappedName,
213: bucket.createProxyStub());
214: }
215: } catch (NamingException e) {
216: throw new EJBException("bind "
217: + Arrays.toString(bucket.getMappedNames())
218: + " failed!", e);
219: }
220: bucket.start();
221: logger.info("Stateless EJB loaded, bean class: "
222: + beanClass.getName());
223: }
224: // stateful
225: Class[] statefulBeans = module.getModuleClassLoader()
226: .findClassAnnotatedWith(Stateful.class);
227: for (Class beanClass : statefulBeans) {
228: final EJBBucket bucket = new StatefulBucket(
229: (EJBContainer) componentContext
230: .getMyselfComponent(), beanClass, module);
231: buckets.add(bucket);
232: // bind to jndi
233: try {
234: for (String mappedName : bucket.getMappedNames()) {
235: this .getNamingContext().bind(mappedName,
236: bucket.createProxyStub());
237: }
238: } catch (NamingException e) {
239: throw new EJBException("Failed to bind EJB with name: "
240: + Arrays.toString(bucket.getMappedNames())
241: + " !", e);
242: }
243: bucket.start();
244: logger.info("Stateful EJB loaded, bean class: "
245: + beanClass.getName());
246: }
247:
248: // message driven
249: Class[] mdbBeans = module.getModuleClassLoader()
250: .findClassAnnotatedWith(MessageDriven.class);
251: for (Class beanClass : mdbBeans) {
252: EJBBucket bucket = new MDBBucket(
253: (EJBContainer) componentContext
254: .getMyselfComponent(), beanClass, module);
255: buckets.add(bucket);
256: // bind to jndi
257: try {
258: for (String mappedName : bucket.getMappedNames()) {
259: this .getNamingContext().bind(mappedName,
260: bucket.createProxyStub());
261: }
262: } catch (NamingException e) {
263: throw new EJBException("Failed to bind EJB with name: "
264: + Arrays.toString(bucket.getMappedNames())
265: + " !", e);
266: }
267: // will register MDBBucket as MessageListener
268: bucket.start();
269: logger.info("Message Driven EJB loaded, bean class: "
270: + beanClass.getName());
271: }
272:
273: return buckets.toArray(new EJBBucket[buckets.size()]);
274: }
275:
276: protected void unloadEJB(Module module) {
277: Iterator<Map.Entry<String, EJBBucket>> it = bucketMap
278: .entrySet().iterator();
279: while (it.hasNext()) {
280: EJBBucket bucket = it.next().getValue();
281: if (bucket.getModule() == module) {
282: it.remove();
283: //fireEvent, 以便XFire�以 unregister Endpoint
284: componentContext
285: .fireComponentEvent(new EJBUnloadedComponentEvent(
286: componentContext.getComponentId(),
287: bucket));
288: // destroy ejb bucket
289: logger.info("Unload EJB: " + bucket.getEJBName()
290: + ", Module: " + bucket.getModule().getName());
291: bucket.stop();
292: try {
293: for (String mappedName : bucket.getMappedNames()) {
294: this .getNamingContext().unbind(mappedName);
295: }
296:
297: } catch (NamingException e) {
298: throw new EJBException("unbind ejb: "
299: + bucket.getMappedNames() + " failed!", e);
300: }
301: }
302: }
303: }
304:
305: public Collection<EJBBucket> listBuckets() {
306: return Collections.unmodifiableCollection(bucketMap.values());
307: }
308:
309: public EJBBucket getEJBBucket(String name) {
310: return bucketMap.get(name);
311: }
312:
313: /**
314: * 通过接�类� EJBBucket
315: *
316: * @param interfaceClass bean interface
317: */
318: public Collection<EJBBucket> getEJBBucketByBeanInterface(
319: Class interfaceClass) {
320: List<EJBBucket> buckets = new ArrayList<EJBBucket>();
321: for (EJBBucket bucket : bucketMap.values()) {
322: if (bucket.isBusinessInterface(interfaceClass)) {
323: buckets.add(bucket);
324: }
325: }
326: return Collections.unmodifiableCollection(buckets);
327: }
328:
329: /**
330: * æž„é€ ejb invocation,并且获得 chain,然å?Žå?‘起调用
331: *
332: * @param ejbObjectId ejb object id
333: * @param interfaceMethod ejb interfaceMethod, 已�解��实体方法
334: * @param params parameters
335: * @param securityContext security context
336: * @throws Exception exception
337: */
338: public Object invokeEJB(EJBObjectId ejbObjectId,
339: Method interfaceMethod, Object[] params,
340: SessionContext securityContext) throws Exception {
341: logger.debug("invokeEJB: EJBObjectId=" + ejbObjectId
342: + ", Method: " + interfaceMethod.getName());
343: EJBBucket bucket = getEJBBucket(ejbObjectId.getEJBName());
344: // get instance from bucket's pool
345: ExtendEJBContext ejbContext = null;
346: try {
347: ejbContext = bucket.getEJBContext(ejbObjectId);
348: Method concreteMethod = bucket
349: .getConcreteMethod(interfaceMethod);
350: if (concreteMethod == null) {
351: throw new NoSuchMethodException(
352: "Could not found Concrete Business Method for interface method: "
353: + interfaceMethod.getName());
354: }
355: EJBInvocation invocation = new EJBInvocation(ejbObjectId,
356: bucket, ejbContext.getEJBInstance(),
357: interfaceMethod, concreteMethod, params,
358: securityContext);
359: return invokeEJBInvocation(invocation);
360: } finally {
361: // reuse ejb instance
362: if (ejbContext != null) {
363: bucket.reuseEJBContext(ejbContext);
364: }
365: }
366:
367: }
368:
369: /**
370: * invoke timeout method
371: *
372: * @param ejbObjectId ejb object id
373: * @param interfaceMethod timeout interfaceMethod,�能是实体方法,也�能是 TimedObject 接�方法
374: * @param params parameters
375: * @throws Exception exception
376: */
377: protected Object invokeTimeout(EJBObjectId ejbObjectId,
378: Method interfaceMethod, Object[] params,
379: SessionContext sessionContext) throws Exception {
380: EJBBucket bucket = getEJBBucket(ejbObjectId.getEJBName());
381: // get instance from bucket's pool
382: ExtendEJBContext ejbContext = null;
383: try {
384: ejbContext = bucket.getEJBContext(ejbObjectId);
385: EJBInvocation invocation = new EJBInvocation(ejbObjectId,
386: bucket, ejbContext.getEJBInstance(),
387: interfaceMethod, interfaceMethod, params,
388: sessionContext);
389: return invokeEJBInvocation(invocation);
390: } finally {
391: // reuse ejb instance
392: if (ejbContext != null) {
393: bucket.reuseEJBContext(ejbContext);
394: }
395: }
396: }
397:
398: protected Object invokeEJBInvocation(EJBInvocation invocation)
399: throws Exception {
400: invocation.setTransactionManager(getTransactionManager());
401: Iterator<EJBInvocationHandler> chain = invocationChain
402: .iterator();
403: return chain.next().invoke(invocation, chain);
404: }
405:
406: public TransactionManager getTransactionManager() {
407: return tm;
408: }
409:
410: public TimerService getTimerService() {
411: return timerService;
412: }
413:
414: public Context getNamingContext() {
415: return namingContext;
416: }
417:
418: public MessageService getMessageService() {
419: return messageService;
420: }
421:
422: public boolean preInvoke(Method method, Object[] params) {
423: // just return true
424: return true;
425: }
426:
427: public Object postInvoke(Method method, Object[] params,
428: Object result, Throwable exception) {
429: return result;
430: }
431:
432: // ------------ JNDI Context ------
433:
434: public class ContainerNamingContext extends ContextAdapter {
435:
436: public void bind(String name, Object obj)
437: throws NamingException {
438: if (jndiMap.containsKey(name)) {
439: throw new NameAlreadyBoundException(name);
440: }
441: jndiMap.put(name, obj);
442: }
443:
444: public void rebind(String name, Object obj)
445: throws NamingException {
446: jndiMap.put(name, obj);
447: }
448:
449: public void unbind(String name) throws NamingException {
450: if (!jndiMap.containsKey(name)) {
451: throw new NameNotFoundException(name);
452: }
453: }
454:
455: /**
456: * 从 jndiMap lookup resource, ejb proxy stub 已�绑定了
457: *
458: * @param name resource or ejb name
459: * @throws NamingException if name not found
460: */
461: public Object lookup(String name) throws NamingException {
462:
463: //解� java:comp/env
464: if (name.startsWith(JAVA_COMP_ENV)) {
465: EJBInvocation currentEJBInvocation = EJBInvocation
466: .current();
467: if (currentEJBInvocation == null) {
468: // ä¸?在 EJB 调用上下文ä¸
469: throw new NameNotFoundException(JAVA_COMP_ENV);
470: }
471:
472: if (name.equals(JAVA_COMP_ENV)) { // lookup java:comp/env
473: // EJBBucket extends Context
474: return getEJBBucket(
475: currentEJBInvocation.getEJBObjectId()
476: .getEJBName()).getENContext(
477: currentEJBInvocation.getEJBObjectId());
478: } else { // lookup java:comp/env/abc
479: EJBBucket bucket = getEJBBucket(currentEJBInvocation
480: .getEJBObjectId().getEJBName());
481: return bucket.getENContext(
482: currentEJBInvocation.getEJBObjectId())
483: .lookup(
484: name.substring(JAVA_COMP_ENV
485: .length() + 1));
486: }
487: }
488:
489: if (!jndiMap.containsKey(name)) {
490: throw new NameNotFoundException(name);
491: }
492: return jndiMap.get(name);
493: }
494:
495: public NamingEnumeration<NameClassPair> list(String name)
496: throws NamingException {
497: final NamingEnumeration<Binding> bindings = listBindings(name);
498: return new NamingEnumeration<NameClassPair>() {
499: public NameClassPair next() throws NamingException {
500: return bindings.next();
501: }
502:
503: public boolean hasMore() throws NamingException {
504: return bindings.hasMore();
505: }
506:
507: public void close() throws NamingException {
508: bindings.close();
509: }
510:
511: public boolean hasMoreElements() {
512: return bindings.hasMoreElements();
513: }
514:
515: public NameClassPair nextElement() {
516: return bindings.nextElement();
517: }
518: };
519: }
520:
521: public NamingEnumeration<Binding> listBindings(String name)
522: throws NamingException {
523: final Map<String, Object> namingMap = new HashMap<String, Object>();
524: if (name == null || name.trim().length() == 0
525: || name.trim().equals("/")) { // all Bindings
526: namingMap.putAll(jndiMap);
527: } else {
528: namingMap.put(name, jndiMap.get(name));
529: }
530: final Iterator<Map.Entry<String, Object>> iterator = namingMap
531: .entrySet().iterator();
532: return new NamingEnumeration<Binding>() {
533: public boolean hasMore() throws NamingException {
534: return iterator.hasNext();
535: }
536:
537: public Binding next() throws NamingException {
538: Map.Entry<String, Object> entry = iterator.next();
539: return new Binding(entry.getKey(), entry.getValue());
540: }
541:
542: public void close() throws NamingException {
543: // do nothing
544: }
545:
546: public boolean hasMoreElements() {
547: return iterator.hasNext();
548: }
549:
550: public Binding nextElement() {
551: try {
552: return next();
553: } catch (NamingException nException) {
554: throw new EJBException(
555: "NamingEnumeration.nextElement exception.",
556: nException);
557: }
558: }
559: };
560: }
561: }
562:
563: // Container Timer Service
564: public class ContainerTimerService implements TimerService {
565:
566: /**
567: * EJBTimerTasks, use WeakHashMap, when EJBTimerTask un contained by java.util.Timer,
568: * it will be automatic removed by GC
569: * EJBTimerTask => timer hashCode
570: */
571: // private Map<EJBTimerTask, String> timerTasks = new WeakHashMap<EJBTimerTask, String>();
572: private ScheduledThreadPoolExecutor scheduleService = new ScheduledThreadPoolExecutor(
573: 2);
574:
575: public ContainerTimerService() {
576:
577: }
578:
579: public Timer createTimer(final long duration,
580: final Serializable info)
581: throws IllegalArgumentException, IllegalStateException,
582: EJBException {
583: EJBTimerTask timer = new EJBTimerTask(this , info);
584: ScheduledFuture future = scheduleService.schedule(timer,
585: duration, TimeUnit.MILLISECONDS);
586: timer.setFuture(future);
587: // timerTasks.put(timer, System.currentTimeMillis() + "");
588: return timer;
589: }
590:
591: public Timer createTimer(Date expiration, Serializable info)
592: throws IllegalArgumentException, IllegalStateException,
593: EJBException {
594: EJBTimerTask timer = new EJBTimerTask(this , info);
595: ScheduledFuture future = scheduleService.schedule(timer,
596: expiration.getTime() - System.currentTimeMillis(),
597: TimeUnit.MILLISECONDS);
598: timer.setFuture(future);
599: // timerTasks.put(timer, System.currentTimeMillis() + "");
600: return timer;
601: }
602:
603: public Timer createTimer(final long initialDuration,
604: final long intervalDuration, final Serializable info)
605: throws IllegalArgumentException, IllegalStateException,
606: EJBException {
607: EJBTimerTask timer = new EJBTimerTask(this , info);
608: ScheduledFuture future = scheduleService
609: .scheduleWithFixedDelay(timer, initialDuration,
610: intervalDuration, TimeUnit.MILLISECONDS);
611: timer.setFuture(future);
612: // timerTasks.put(timer, System.currentTimeMillis() + "");
613: return timer;
614: }
615:
616: public Timer createTimer(Date initialExpiration,
617: long intervalDuration, Serializable info)
618: throws IllegalArgumentException, IllegalStateException,
619: EJBException {
620: EJBTimerTask timer = new EJBTimerTask(this , info);
621: ScheduledFuture future = scheduleService
622: .scheduleWithFixedDelay(timer, initialExpiration
623: .getTime()
624: - System.currentTimeMillis(),
625: intervalDuration, TimeUnit.MILLISECONDS);
626: timer.setFuture(future);
627: // timerTasks.put(timer, System.currentTimeMillis() + "");
628: return timer;
629: }
630:
631: public Collection getTimers() throws IllegalStateException,
632: EJBException {
633: // return Collections.unmodifiableCollection(timerTasks.keySet());
634: return Arrays.asList(scheduleService.getQueue().toArray(
635: new Runnable[scheduleService.getQueue().size()]));
636: }
637:
638: /**
639: * 执行 Timeout 方法,有 ScheduleService 调用 EJBTimerTask.run,EJBTimerTask回调该方法,
640: * 通过容器æ?¥è°ƒç”¨ï¼Œä»¥æ??供事务和执行 lifecycle 回调
641: *
642: * @param ejbTimerTask ejb TimerTask
643: * @throws EJBException ejb exception when error
644: */
645: public void timeout(final EJBTimerTask ejbTimerTask)
646: throws EJBException {
647: Method timeMethod = null;
648: try {
649: for (Method _timeoutMethod : ejbTimerTask
650: .getTimeoutMethods()) {
651: timeMethod = _timeoutMethod;
652: logger.info("Call Timeout method: "
653: + _timeoutMethod + " of EJB: "
654: + ejbTimerTask.getEJBObjectId());
655: // è¿™æ ·ä¼šå?¯åŠ¨äº‹åŠ¡
656: invokeTimeout(ejbTimerTask.getEJBObjectId(),
657: _timeoutMethod,
658: new Object[] { ejbTimerTask }, ejbTimerTask
659: .getSessionContext());
660: }
661: } catch (Exception e) {
662: logger.error("Call Timeout method " + timeMethod
663: + " throw exception.", e);
664: throw new EJBException(
665: "TimedObject callback exception.", e);
666: }
667: }
668:
669: public void stop() {
670: scheduleService.shutdown();
671: }
672: }
673: }
|