1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.ejb;
23
24 import java.lang.reflect.Method;
25 import java.rmi.RemoteException;
26 import java.util.Collection;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.Hashtable;
30 import java.util.Iterator;
31 import java.util.Map;
32
33 import javax.ejb.EJBException;
34 import javax.ejb.EJBHome;
35 import javax.ejb.EJBLocalHome;
36 import javax.ejb.EJBLocalObject;
37 import javax.ejb.EJBMetaData;
38 import javax.ejb.EJBObject;
39 import javax.ejb.Handle;
40 import javax.ejb.HomeHandle;
41 import javax.ejb.RemoveException;
42 import javax.ejb.TimedObject;
43 import javax.ejb.Timer;
44 import javax.management.ObjectName;
45 import javax.transaction.Transaction;
46
47 import org.jboss.invocation.Invocation;
48 import org.jboss.invocation.InvocationType;
49 import org.jboss.invocation.MarshalledInvocation;
50 import org.jboss.metadata.ConfigurationMetaData;
51 import org.jboss.metadata.EntityMetaData;
52 import org.jboss.monitor.StatisticsProvider;
53 import org.jboss.util.collection.SerializableEnumeration;
54
55 /**
56 * This is a Container for EntityBeans (both BMP and CMP).
57 *
58 * @see Container
59 * @see EntityEnterpriseContext
60 *
61 * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</a>
62 * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a>
63 * @author <a href="mailto:sebastien.alborini@m4x.org">Sebastien Alborini</a>
64 * @author <a href="mailto:docodan@mvcsoft.com">Daniel OConnor</a>
65 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
66 * @author <a href="mailto:andreas.schaefer@madplanet.com">Andreas Schaefer</a>
67 * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
68 * @author <a href="mailto:anil.saldhana@redhat.com">Anil Saldhana</a>
69 * @version $Revision: 66439 $
70 *
71 * @jmx.mbean extends="org.jboss.ejb.ContainerMBean"
72 */
73 public class EntityContainer
74 extends Container
75 implements EJBProxyFactoryContainer, InstancePoolContainer,
76 EntityContainerMBean
77 {
78 /**
79 * These are the mappings between the home interface methods and the
80 * container methods.
81 */
82 protected Map homeMapping = new HashMap();
83
84 /**
85 * These are the mappings between the remote/local interface methods and the
86 * bean methods.
87 */
88 protected Map beanMapping = new HashMap();
89
90 /** This is the persistence manager for this container */
91 protected EntityPersistenceManager persistenceManager;
92
93 /** This is the instance cache for this container */
94 protected InstanceCache instanceCache;
95
96 /** This is the instancepool that is to be used */
97 protected InstancePool instancePool;
98
99 /**
100 * This is the first interceptor in the chain. The last interceptor must
101 * be provided by the container itself.
102 */
103 protected Interceptor interceptor;
104
105 /**
106 * <code>readOnly</code> determines if state can be written to resource manager.
107 */
108 protected boolean readOnly = false;
109
110 /**
111 * This provides a way to find the entities that are part of a given
112 * transaction EntitySynchronizationInterceptor and InstanceSynchronization
113 * manage this instance.
114 */
115 protected static GlobalTxEntityMap globalTxEntityMap = new GlobalTxEntityMap();
116
117 public static GlobalTxEntityMap getGlobalTxEntityMap()
118 {
119 return globalTxEntityMap;
120 }
121
122 /**
123 * Stores all of the entities associated with the specified transaction.
124 * As per the spec 9.6.4, entities must be synchronized with the datastore
125 * when an ejbFind<METHOD> is called.
126 * Also, all entities within entire transaction should be synchronized before
127 * a remove, otherwise there may be problems with 'cascade delete'.
128 *
129 * @param tx the transaction that associated entites will be stored
130 */
131 public static void synchronizeEntitiesWithinTransaction(Transaction tx)
132 {
133 // If there is no transaction, there is nothing to synchronize.
134 if(tx != null)
135 {
136 getGlobalTxEntityMap().synchronizeEntities(tx);
137 }
138 }
139
140 // Public --------------------------------------------------------
141
142 public boolean isReadOnly()
143 {
144 return readOnly;
145 }
146
147 public LocalProxyFactory getLocalProxyFactory()
148 {
149 return localProxyFactory;
150 }
151
152 public void setInstancePool(InstancePool ip)
153 {
154 if (ip == null)
155 throw new IllegalArgumentException("Null pool");
156
157 this.instancePool = ip;
158 ip.setContainer(this);
159 }
160
161 public InstancePool getInstancePool()
162 {
163 return instancePool;
164 }
165
166 public void setInstanceCache(InstanceCache ic)
167 {
168 if (ic == null)
169 throw new IllegalArgumentException("Null cache");
170
171 this.instanceCache = ic;
172 ic.setContainer(this);
173 }
174
175 public InstanceCache getInstanceCache()
176 {
177 return instanceCache;
178 }
179
180 public EntityPersistenceManager getPersistenceManager()
181 {
182 return persistenceManager;
183 }
184
185 public void setPersistenceManager(EntityPersistenceManager pm)
186 {
187 if (pm == null)
188 throw new IllegalArgumentException("Null persistence manager");
189
190 persistenceManager = pm;
191 pm.setContainer(this);
192 }
193
194 public void addInterceptor(Interceptor in)
195 {
196 if (interceptor == null)
197 {
198 interceptor = in;
199 }
200 else
201 {
202 Interceptor current = interceptor;
203 while (current.getNext() != null)
204 {
205 current = current.getNext();
206 }
207
208 current.setNext(in);
209 }
210 }
211
212 public Interceptor getInterceptor()
213 {
214 return interceptor;
215 }
216
217 public Class getHomeClass()
218 {
219 return homeInterface;
220 }
221
222 public Class getRemoteClass()
223 {
224 return remoteInterface;
225 }
226
227 /**
228 * Returns a new instance of the bean class or a subclass of the bean class.
229 * If this is 1.x cmp, simply return a new instance of the bean class.
230 * If this is 2.x cmp, return a subclass that provides an implementation
231 * of the abstract accessors.
232 *
233 * @see java.lang.Class#newInstance
234 *
235 * @return The new instance.
236 */
237 public Object createBeanClassInstance() throws Exception {
238 return persistenceManager.createBeanClassInstance();
239 }
240
241 // Container implementation --------------------------------------
242
243 protected void createService() throws Exception
244 {
245 // Associate thread with classloader
246 ClassLoader oldCl = SecurityActions.getContextClassLoader();
247 SecurityActions.setContextClassLoader(getClassLoader());
248 pushENC();
249 try
250 {
251 // Acquire classes from CL
252 if (metaData.getHome() != null)
253 homeInterface = classLoader.loadClass(metaData.getHome());
254 if (metaData.getRemote() != null)
255 remoteInterface = classLoader.loadClass(metaData.getRemote());
256
257 // Call default init
258 super.createService();
259
260 // Make some additional validity checks with regards to the container configuration
261 checkCoherency ();
262
263 // Map the bean methods
264 setupBeanMapping();
265
266 // Map the home methods
267 setupHomeMapping();
268
269 // Map the interfaces to Long
270 setupMarshalledInvocationMapping();
271
272 // Try to register the instance pool as an MBean
273 try
274 {
275 ObjectName containerName = super.getJmxName();
276 Hashtable props = containerName.getKeyPropertyList();
277 props.put("plugin", "pool");
278 ObjectName poolName = new ObjectName(containerName.getDomain(), props);
279 server.registerMBean(instancePool, poolName);
280 }
281 catch(Throwable t)
282 {
283 log.debug("Failed to register cache as mbean", t);
284 }
285 // Initialize pool
286 instancePool.create();
287
288 for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); )
289 {
290 String invokerBinding = (String)it.next();
291 EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding);
292 ci.create();
293 }
294
295 // Try to register the instance cache as an MBean
296 try
297 {
298 ObjectName containerName = super.getJmxName();
299 Hashtable props = containerName.getKeyPropertyList();
300 props.put("plugin", "cache");
301 ObjectName cacheName = new ObjectName(containerName.getDomain(), props);
302 server.registerMBean(instanceCache, cacheName);
303 }
304 catch(Throwable t)
305 {
306 log.debug("Failed to register cache as mbean", t);
307 }
308 // Init instance cache
309 instanceCache.create();
310
311 // Init persistence
312 persistenceManager.create();
313
314 // Initialize the interceptor by calling the chain
315 Interceptor in = interceptor;
316 while (in != null)
317 {
318 in.setContainer(this);
319 in.create();
320 in = in.getNext();
321 }
322 readOnly = ((EntityMetaData)metaData).isReadOnly();
323 }
324 finally
325 {
326 popENC();
327 // Reset classloader
328 SecurityActions.setContextClassLoader(oldCl);
329 }
330 }
331
332 protected void startService() throws Exception
333 {
334 // Associate thread with classloader
335 ClassLoader oldCl = SecurityActions.getContextClassLoader();
336 SecurityActions.setContextClassLoader(getClassLoader());
337 pushENC();
338 try
339 {
340 // Call default start
341 super.startService();
342
343 // Start container invokers
344 for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); )
345 {
346 String invokerBinding = (String)it.next();
347 EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding);
348 ci.start();
349 }
350
351 // Start instance cache
352 instanceCache.start();
353
354 // Start the instance pool
355 instancePool.start();
356
357 Interceptor i = interceptor;
358 while(i != null)
359 {
360 i.start();
361 i = i.getNext();
362 }
363
364 // Restore persisted ejb timers
365 restoreTimers();
366 }
367 finally
368 {
369 popENC();
370 // Reset classloader
371 SecurityActions.setContextClassLoader(oldCl);
372 }
373 }
374
375 protected void stopService() throws Exception
376 {
377 // Associate thread with classloader
378 ClassLoader oldCl = SecurityActions.getContextClassLoader();
379 SecurityActions.setContextClassLoader(getClassLoader());
380 pushENC();
381 try
382 {
383 //Stop items in reverse order from start
384 //This assures that CachedConnectionInterceptor will get removed
385 //from in between this and the pm before the pm is stopped.
386 // Stop all interceptors in the chain
387 Interceptor in = interceptor;
388 while (in != null)
389 {
390 in.stop();
391 in = in.getNext();
392 }
393
394 // Stop the instance pool
395 instancePool.stop();
396
397
398 // Stop persistence
399 persistenceManager.stop();
400
401 // Stop instance cache
402 instanceCache.stop();
403
404 // Stop container invoker
405 for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); )
406 {
407 String invokerBinding = (String)it.next();
408 EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding);
409 ci.stop();
410 }
411
412 // Call default stop
413 super.stopService();
414 }
415 finally
416 {
417 popENC();
418 // Reset classloader
419 SecurityActions.setContextClassLoader(oldCl);
420 }
421 }
422
423 protected void destroyService() throws Exception
424 {
425 // Associate thread with classloader
426 ClassLoader oldCl = SecurityActions.getContextClassLoader();
427 SecurityActions.setContextClassLoader(getClassLoader());
428 pushENC();
429 try
430 {
431 // Destroy container invoker
432 for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); )
433 {
434 String invokerBinding = (String)it.next();
435 EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding);
436 ci.destroy();
437 }
438
439 // Destroy instance cache
440 instanceCache.destroy();
441 instanceCache.setContainer(null);
442 try
443 {
444 ObjectName containerName = super.getJmxName();
445 Hashtable props = containerName.getKeyPropertyList();
446 props.put("plugin", "cache");
447 ObjectName cacheName = new ObjectName(containerName.getDomain(), props);
448 server.unregisterMBean(cacheName);
449 }
450 catch(Throwable ignore)
451 {
452 }
453
454 // Destroy persistence
455 persistenceManager.destroy();
456 persistenceManager.setContainer(null);
457
458 // Destroy the pool
459 instancePool.destroy();
460 instancePool.setContainer(null);
461 try
462 {
463 ObjectName containerName = super.getJmxName();
464 Hashtable props = containerName.getKeyPropertyList();
465 props.put("plugin", "pool");
466 ObjectName poolName = new ObjectName(containerName.getDomain(), props);
467 server.unregisterMBean(poolName);
468 }
469 catch(Throwable ignore)
470 {
471 }
472
473 // Destroy all the interceptors in the chain
474 Interceptor in = interceptor;
475 while (in != null)
476 {
477 in.destroy();
478 in.setContainer(null);
479 in = in.getNext();
480 }
481
482 MarshalledInvocation.removeHashes(homeInterface);
483 MarshalledInvocation.removeHashes(remoteInterface);
484
485 // Call default destroy
486 super.destroyService();
487 }
488 finally
489 {
490 popENC();
491 // Reset classloader
492 SecurityActions.setContextClassLoader(oldCl);
493 }
494 }
495
496 public Object internalInvokeHome(Invocation mi) throws Exception
497 {
498 Method method = mi.getMethod();
499 if (method != null && method.getName().equals("remove"))
500 {
501 // Map to EJBHome.remove(Object) to EJBObject.remove()
502 InvocationType type = mi.getType();
503 if (type == InvocationType.HOME)
504 mi.setType(InvocationType.REMOTE);
505 else if (type == InvocationType.LOCALHOME)
506 mi.setType(InvocationType.LOCAL);
507 mi.setMethod(EJBOBJECT_REMOVE);
508
509 // Handle or primary key?
510 Object arg = mi.getArguments()[0];
511 if (arg instanceof Handle)
512 {
513 if (arg == null)
514 throw new RemoteException("Null handle");
515 Handle handle = (Handle) arg;
516 EJBObject ejbObject = handle.getEJBObject();
517 mi.setId(ejbObject.getPrimaryKey());
518 }
519 else
520 mi.setId(arg);
521
522 mi.setArguments(new Object[0]);
523 return getInterceptor().invoke(mi);
524 }
525 // Invoke through interceptors
526 return getInterceptor().invokeHome(mi);
527 }
528
529 public Object internalInvoke(Invocation mi) throws Exception
530 {
531 // Invoke through interceptors
532 return getInterceptor().invoke(mi);
533 }
534
535 // EJBObject implementation --------------------------------------
536
537 public void remove(Invocation mi)
538 throws RemoteException, RemoveException
539 {
540 // synchronize entities with the datastore before the bean is removed
541 // this will write queued updates so datastore will be consistent before removal
542 Transaction tx = mi.getTransaction();
543 if (!getBeanMetaData().getContainerConfiguration().getSyncOnCommitOnly())
544 synchronizeEntitiesWithinTransaction(tx);
545
546 // Get the persistence manager to do the dirty work
547 EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext();
548 getPersistenceManager().removeEntity(ctx);
549
550 Object pk = ctx.getId();
551 removeTimerService(pk);
552
553 // We signify "removed" with a null id
554 // There is no need to synchronize on the context since all the threads reaching here have
555 // gone through the InstanceInterceptor so the instance is locked and we only have one thread
556 // the case of reentrant threads is unclear (would you want to delete an instance in reentrancy)
557 ctx.setId(null);
558 removeCount++;
559 }
560
561 /**
562 * @throws Error Not yet implemented.
563 */
564 public Handle getHandle(Invocation mi)
565 throws RemoteException
566 {
567 // TODO
568 throw new Error("Not yet implemented");
569 }
570
571 public Object getPrimaryKey(Invocation mi)
572 throws RemoteException
573 {
574 return mi.getId();
575 }
576
577 /**
578 * @throws IllegalStateException If container invoker is null.
579 */
580 public EJBHome getEJBHome(Invocation mi)
581 throws RemoteException
582 {
583 EJBProxyFactory ci = getProxyFactory();
584 if (ci == null)
585 {
586 String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor";
587 throw new IllegalStateException(msg);
588 }
589 return (EJBHome) ci.getEJBHome();
590 }
591
592 public boolean isIdentical(Invocation mi)
593 throws RemoteException
594 {
595 EJBProxyFactory ci = getProxyFactory();
596 if (ci == null)
597 {
598 String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor";
599 throw new IllegalStateException(msg);
600 }
601
602 return ci.isIdentical(this, mi);
603 }
604
605 /**
606 * MF FIXME these are implemented on the client
607 */
608 public EJBLocalHome getEJBLocalHome(Invocation mi)
609 {
610 return localProxyFactory.getEJBLocalHome();
611 }
612
613 /**
614 * @throws Error Not yet implemented.
615 */
616 public void removeLocalHome(Invocation mi)
617 throws RemoteException, RemoveException
618 {
619 throw new Error("Not Yet Implemented");
620 }
621
622 /**
623 * Local home interface implementation
624 */
625 public EJBLocalObject createLocalHome(Invocation mi)
626 throws Exception
627 {
628 // The persistence manager takes care of the wiring and creating the EJBLocalObject
629 final EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext();
630 getPersistenceManager().createEntity(mi.getMethod(), mi.getArguments(), ctx);
631
632 // The context implicitely carries the EJBObject
633 createCount++;
634 return localProxyFactory.getEntityEJBLocalObject(ctx.getId(), true);
635 }
636
637 /**
638 * Delegates to the persistence manager postCreateEntityMethod.
639 */
640 public void postCreateLocalHome(Invocation mi) throws Exception
641 {
642 // The persistence manager takes care of the post create step
643 getPersistenceManager().postCreateEntity(mi.getMethod(),mi.getArguments(),
644 (EntityEnterpriseContext) mi.getEnterpriseContext());
645 }
646
647 public Object findLocal(Invocation mi)
648 throws Exception
649 {
650 Method method = mi.getMethod();
651 Object[] args = mi.getArguments();
652 EntityEnterpriseContext instance = (EntityEnterpriseContext)mi.getEnterpriseContext();
653
654 boolean syncOnCommitOnly = metaData.getContainerConfiguration().getSyncOnCommitOnly();
655 Transaction tx = mi.getTransaction();
656
657 Class returnType = method.getReturnType();
658 if (Collection.class.isAssignableFrom(returnType) || returnType == Enumeration.class)
659 {
660 // as per the spec 9.6.4, entities must be synchronized with the datastore when an ejbFind<METHOD> is called.
661 if (!syncOnCommitOnly)
662 {
663 synchronizeEntitiesWithinTransaction(tx);
664 }
665
666 // Iterator finder
667 Collection c = getPersistenceManager().findEntities(method, args, instance, localProxyFactory);
668
669 // BMP entity finder methods are allowed to return java.util.Enumeration.
670 if (returnType == Enumeration.class)
671 {
672 return java.util.Collections.enumeration(c);
673 }
674 else
675 {
676 return c;
677 }
678 }
679 else
680 {
681 return findSingleObject(tx, method, args, instance, localProxyFactory);
682 }
683 }
684
685 // Home interface implementation ---------------------------------
686
687 /**
688 * This methods finds the target instances by delegating to the persistence
689 * manager It then manufactures EJBObject for all the involved instances
690 * found.
691 */
692 public Object find(Invocation mi) throws Exception
693 {
694 EJBProxyFactory ci = getProxyFactory();
695 if (ci == null)
696 {
697 String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor";
698 throw new IllegalStateException(msg);
699 }
700
701 Method method = mi.getMethod();
702 Object[] args = mi.getArguments();
703 EntityEnterpriseContext instance = (EntityEnterpriseContext)mi.getEnterpriseContext();
704
705 boolean syncOnCommitOnly = metaData.getContainerConfiguration().getSyncOnCommitOnly();
706 Transaction tx = mi.getTransaction();
707
708 Class returnType = method.getReturnType();
709 if (Collection.class.isAssignableFrom(returnType) || returnType == Enumeration.class)
710 {
711 // as per the spec 9.6.4, entities must be synchronized with the datastore when an ejbFind<METHOD> is called.
712 if (!syncOnCommitOnly)
713 {
714 synchronizeEntitiesWithinTransaction(tx);
715 }
716
717 // Iterator finder
718 Collection c = getPersistenceManager().findEntities(method, args, instance, ci);
719
720 // BMP entity finder methods are allowed to return java.util.Enumeration.
721 // We need a serializable Enumeration, so we can't use Collections.enumeration()
722 if (returnType == Enumeration.class)
723 {
724 return new SerializableEnumeration(c);
725 }
726 else
727 {
728 return c;
729 }
730 }
731 else
732 {
733 return findSingleObject(tx, method, args, instance, ci);
734 }
735 }
736
737 /**
738 * Invokes ejbStore method on the instance
739 * @param ctx the instance to invoke ejbStore on
740 * @throws Exception
741 */
742 public void invokeEjbStore(EntityEnterpriseContext ctx) throws Exception
743 {
744 if (ctx.getId() != null)
745 {
746 final EntityPersistenceManager pm = getPersistenceManager();
747 pm.invokeEjbStore(ctx);
748 }
749 }
750
751 /**
752 * For CMP actually stores the instance
753 */
754 public void storeEntity(EntityEnterpriseContext ctx) throws Exception
755 {
756 if (ctx.getId() != null)
757 {
758 final EntityPersistenceManager pm = getPersistenceManager();
759 if(pm.isStoreRequired(ctx))
760 {
761 pm.storeEntity(ctx);
762 }
763 }
764 }
765
766 /**
767 * Delegates to the persistence manager postCreateEntityMethod.
768 */
769 public void postCreateHome(Invocation mi) throws Exception
770 {
771 // The persistence manager takes care of the post create step
772 getPersistenceManager().postCreateEntity(mi.getMethod(),mi.getArguments(),
773 (EntityEnterpriseContext) mi.getEnterpriseContext());
774 }
775
776 /**
777 * This method takes care of the wiring of the "EJBObject" trio
778 * (target, context, proxy). It delegates to the persistence manager.
779 */
780 public EJBObject createHome(Invocation mi)
781 throws Exception
782 {
783 // The persistence manager takes care of the wiring and creating the EJBObject
784 getPersistenceManager().createEntity(mi.getMethod(),mi.getArguments(),
785 (EntityEnterpriseContext) mi.getEnterpriseContext());
786
787 // The context implicitely carries the EJBObject
788 createCount++;
789 return ((EntityEnterpriseContext)mi.getEnterpriseContext()).getEJBObject();
790 }
791
792 /**
793 * A method for the getEJBObject from the handle
794 */
795 public EJBObject getEJBObject(Invocation mi)
796 throws RemoteException
797 {
798 EJBProxyFactory ci = getProxyFactory();
799 if (ci == null)
800 {
801 String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor";
802 throw new IllegalStateException(msg);
803 }
804 // All we need is an EJBObject for this Id;
805 return (EJBObject)ci.getEntityEJBObject(((EntityCache) instanceCache).createCacheKey(mi.getId()));
806 }
807
808 // EJBHome implementation ----------------------------------------
809
810 /**
811 * @throws Error Not yet implemented.
812 */
813 public void removeHome(Invocation mi)
814 throws RemoteException, RemoveException
815 {
816 throw new Error("Not yet implemented");
817 }
818
819 public EJBMetaData getEJBMetaDataHome(Invocation mi)
820 throws RemoteException
821 {
822 EJBProxyFactory ci = getProxyFactory();
823 if (ci == null)
824 {
825 String msg = "No ProxyFactory, check for ProxyFactoryFinderInterceptor";
826 throw new IllegalStateException(msg);
827 }
828 return ci.getEJBMetaData();
829 }
830
831 /**
832 * @throws Error Not yet implemented.
833 */
834 public HomeHandle getHomeHandleHome(Invocation mi)
835 throws RemoteException
836 {
837 // TODO
838 throw new Error("Not yet implemented");
839 }
840
841 /**
842 * @jmx.managed-attribute
843 * @return the current cache size
844 */
845 public long getCacheSize()
846 {
847 return instanceCache.getCacheSize();
848 }
849
850 /** Flush the cache
851 * @jmx.managed-operation
852 */
853 public void flushCache()
854 {
855 instanceCache.flush();
856 }
857
858 // StatisticsProvider implementation ------------------------------------
859
860 public Map retrieveStatistic()
861 {
862 // Loop through all Interceptors and add statistics
863 Map lStatistics = new HashMap();
864 StatisticsProvider lProvider = (StatisticsProvider) getPersistenceManager();
865 lStatistics.putAll( lProvider.retrieveStatistic() );
866 lProvider = (StatisticsProvider) getInstancePool();
867 lStatistics.putAll( lProvider.retrieveStatistic() );
868 return lStatistics;
869 }
870
871 public void resetStatistic()
872 {
873 }
874
875 // Private -------------------------------------------------------
876
877 private void setupHomeMappingImpl(Method[] m,
878 String finderName,
879 String append)
880 throws Exception
881 {
882 // Adrian Brock: This should go away when we don't support EJB1x
883 boolean isEJB1x = metaData.getApplicationMetaData().isEJB1x();
884
885 for (int i = 0; i < m.length; i++)
886 {
887 String methodName = m[i].getName();
888 try
889 {
890 try // Try home method
891 {
892 String ejbHomeMethodName = "ejbHome" + methodName.substring(0,1).toUpperCase() + methodName.substring(1);
893 homeMapping.put(m[i], beanClass.getMethod(ejbHomeMethodName, m[i].getParameterTypes()));
894
895 continue;
896 }
897 catch (NoSuchMethodException ignore) {} // just go on with other types of methods
898
899
900 // Implemented by container (in both cases)
901 if (methodName.startsWith("find"))
902 {
903 homeMapping.put(m[i], this.getClass().getMethod(finderName, new Class[] { Invocation.class }));
904 }
905 else if (methodName.equals("create") ||
906 (isEJB1x == false && methodName.startsWith("create")))
907 {
908 homeMapping.put(m[i], this.getClass().getMethod("create"+append, new Class[] { Invocation.class }));
909 beanMapping.put(m[i], this.getClass().getMethod("postCreate"+append, new Class[] { Invocation.class }));
910 }
911 else
912 {
913 homeMapping.put(m[i], this.getClass().getMethod(methodName+append, new Class[] { Invocation.class }));
914 }
915 }
916 catch (NoSuchMethodException e)
917 {
918 throw new NoSuchMethodException("Could not find matching method for "+m[i]);
919 }
920 }
921 }
922
923 protected void setupHomeMapping() throws Exception
924 {
925 try {
926 if (homeInterface != null)
927 {
928 Method[] m = homeInterface.getMethods();
929 setupHomeMappingImpl( m, "find", "Home" );
930 }
931 if (localHomeInterface != null)
932 {
933 Method[] m = localHomeInterface.getMethods();
934 setupHomeMappingImpl( m, "findLocal", "LocalHome" );
935 }
936
937 // Special methods
938
939 // Get the One on Handle (getEJBObject), get the class
940 Class handleClass = Class.forName("javax.ejb.Handle");
941
942 // Get the methods (there is only one)
943 Method[] handleMethods = handleClass.getMethods();
944
945 //Just to make sure let's iterate
946 for (int j=0; j<handleMethods.length ;j++)
947 {
948 //Get only the one called handle.getEJBObject
949 if (handleMethods[j].getName().equals("getEJBObject"))
950 {
951 //Map it in the home stuff
952 homeMapping.put(handleMethods[j],
953 this.getClass().getMethod("getEJBObject",
954 new Class[] {Invocation.class}));
955 }
956 }
957 }
958 catch (Exception e)
959 {
960 // ditch the half built mappings
961 homeMapping.clear();
962 beanMapping.clear();
963
964 throw e;
965 }
966 }
967
968 private void setupBeanMappingImpl( Method[] m, String intfName )
969 throws Exception
970 {
971 for (int i = 0; i < m.length; i++)
972 {
973 if (!m[i].getDeclaringClass().getName().equals(intfName))
974 {
975 // Implemented by bean
976 beanMapping.put(m[i], beanClass.getMethod(m[i].getName(), m[i].getParameterTypes()));
977 }
978 else
979 {
980 // Implemented by container
981 beanMapping.put(m[i], getClass().getMethod(m[i].getName(),
982 new Class[] { Invocation.class }));
983 }
984 }
985 }
986
987 protected void setupBeanMapping() throws Exception
988 {
989 try {
990 if (remoteInterface != null)
991 {
992 Method[] m = remoteInterface.getMethods();
993 setupBeanMappingImpl( m, "javax.ejb.EJBObject" );
994 }
995 if (localInterface != null)
996 {
997 Method[] m = localInterface.getMethods();
998 setupBeanMappingImpl( m, "javax.ejb.EJBLocalObject" );
999 }
1000 if( TimedObject.class.isAssignableFrom( beanClass ) ) {
1001 // Map ejbTimeout
1002 beanMapping.put(
1003 TimedObject.class.getMethod( "ejbTimeout", new Class[] { Timer.class } ),
1004 beanClass.getMethod( "ejbTimeout", new Class[] { Timer.class } )
1005 );
1006 }
1007 }
1008 catch (Exception e)
1009 {
1010 // ditch the half built mappings
1011 homeMapping.clear();
1012 beanMapping.clear();
1013
1014 throw e;
1015 }
1016 }
1017
1018 protected void setupMarshalledInvocationMapping() throws Exception
1019 {
1020 // Create method mappings for container invoker
1021 if (homeInterface != null)
1022 {
1023 Method [] m = homeInterface.getMethods();
1024 for (int i = 0 ; i<m.length ; i++)
1025 {
1026 marshalledInvocationMapping.put( new Long(MarshalledInvocation.calculateHash(m[i])), m[i]);
1027 }
1028 }
1029
1030 if (remoteInterface != null)
1031 {
1032 Method [] m = remoteInterface.getMethods();
1033 for (int j = 0 ; j<m.length ; j++)
1034 {
1035 marshalledInvocationMapping.put( new Long(MarshalledInvocation.calculateHash(m[j])), m[j]);
1036 }
1037 }
1038
1039 // Get the getEJBObjectMethod
1040 Method getEJBObjectMethod = Class.forName("javax.ejb.Handle").getMethod("getEJBObject", new Class[0]);
1041
1042 // Hash it
1043 marshalledInvocationMapping.put(new Long(MarshalledInvocation.calculateHash(getEJBObjectMethod)),getEJBObjectMethod);
1044 }
1045
1046 Interceptor createContainerInterceptor()
1047 {
1048 return new ContainerInterceptor();
1049 }
1050
1051 protected void checkCoherency () throws Exception
1052 {
1053 // Check clustering cohrency wrt metadata
1054 //
1055 if (metaData.isClustered())
1056 {
1057 boolean clusteredProxyFactoryFound = false;
1058 for (Iterator it = proxyFactories.keySet().iterator(); it.hasNext(); )
1059 {
1060 String invokerBinding = (String)it.next();
1061 EJBProxyFactory ci = (EJBProxyFactory)proxyFactories.get(invokerBinding);
1062 if (ci instanceof org.jboss.proxy.ejb.ClusterProxyFactory)
1063 clusteredProxyFactoryFound = true;
1064 }
1065
1066 if (!clusteredProxyFactoryFound)
1067 {
1068 log.warn("*** EJB '" + this.metaData.getEjbName() + "' deployed as CLUSTERED but not a single clustered-invoker is bound to container ***");
1069 }
1070 }
1071 }
1072
1073 private Object findSingleObject(Transaction tx,
1074 Method method,
1075 Object[] args,
1076 EntityEnterpriseContext instance,
1077 GenericEntityObjectFactory factory)
1078 throws Exception
1079 {
1080 if(method.getName().equals("findByPrimaryKey"))
1081 {
1082 if(args[0] == null)
1083 throw new IllegalArgumentException("findByPrimaryKey called with null argument.");
1084
1085 if(metaData.getContainerConfiguration().getCommitOption() != ConfigurationMetaData.B_COMMIT_OPTION)
1086 {
1087 Object key = instance.getCacheKey();
1088 if(key == null)
1089 {
1090 key = ((EntityCache)instanceCache).createCacheKey(args[0]);
1091 }
1092
1093 if(instanceCache.isActive(key))
1094 {
1095 return factory.getEntityEJBObject(key);
1096 }
1097 }
1098 }
1099 else if(!metaData.getContainerConfiguration().getSyncOnCommitOnly())
1100 {
1101 EntityContainer.synchronizeEntitiesWithinTransaction(tx);
1102 }
1103
1104 return getPersistenceManager().findEntity(method, args, instance, factory);
1105 }
1106
1107 // Inner classes -------------------------------------------------
1108
1109 /**
1110 * This is the last step before invocation - all interceptors are done
1111 */
1112 class ContainerInterceptor
1113 extends AbstractContainerInterceptor
1114 {
1115 public Object invokeHome(Invocation mi) throws Exception
1116 {
1117 // Invoke and handle exceptions
1118 Method miMethod = mi.getMethod();
1119 Method m = (Method) homeMapping.get(miMethod);
1120 if( m == null )
1121 {
1122 String msg = "Invalid invocation, check your deployment packaging"
1123 +", method="+miMethod;
1124 throw new EJBException(msg);
1125 }
1126
1127 if (m.getDeclaringClass().equals(EntityContainer.class))
1128 {
1129 try
1130 {
1131 return mi.performCall(EntityContainer.this, m, new Object[] { mi });
1132 }
1133 catch (Exception e)
1134 {
1135 rethrow(e);
1136 }
1137 }
1138 else // Home method
1139 {
1140 EnterpriseContext ctx = (EnterpriseContext) mi.getEnterpriseContext();
1141 try
1142 {
1143 AllowedOperationsAssociation.pushInMethodFlag(AllowedOperationsAssociation.IN_EJB_HOME);
1144 return mi.performCall(ctx.getInstance(), m, mi.getArguments());
1145 }
1146 catch (Exception e)
1147 {
1148 rethrow(e);
1149 }
1150 finally{
1151 AllowedOperationsAssociation.popInMethodFlag();
1152 }
1153 }
1154
1155 // We will never get this far, but the compiler does not know that
1156 throw new org.jboss.util.UnreachableStatementException();
1157 }
1158
1159 public Object invoke(Invocation mi) throws Exception
1160 {
1161 // Get method
1162 Method miMethod = mi.getMethod();
1163 Method m = (Method) beanMapping.get(miMethod);
1164 if( m == null )
1165 {
1166 String msg = "Invalid invocation, check your deployment packaging"
1167 +", method="+miMethod;
1168 throw new EJBException(msg);
1169 }
1170
1171 // Select instance to invoke (container or bean)
1172 if (m.getDeclaringClass().equals(EntityContainer.class))
1173 {
1174 // Invoke container
1175 try
1176 {
1177 return mi.performCall(EntityContainer.this, m, new Object[]{ mi });
1178 }
1179 catch (Exception e)
1180 {
1181 rethrow(e);
1182 }
1183 }
1184 else
1185 {
1186 // Invoke bean instance
1187 try
1188 {
1189 EnterpriseContext ctx = (EnterpriseContext) mi.getEnterpriseContext();
1190 Object instance = ctx.getInstance();
1191
1192 return mi.performCall(instance, m, mi.getArguments());
1193 }
1194 catch (Exception e)
1195 {
1196 rethrow(e);
1197 }
1198 }
1199
1200 // We will never get this far, but the compiler does not know that
1201 throw new org.jboss.util.UnreachableStatementException();
1202 }
1203 }
1204 }