001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.proxy.ejb;
023:
024: import java.lang.reflect.Proxy;
025: import java.lang.reflect.Constructor;
026: import java.lang.reflect.InvocationHandler;
027: import java.util.ArrayList;
028: import java.util.Collection;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.rmi.ServerException;
032:
033: import javax.ejb.EJBHome;
034: import javax.ejb.EJBObject;
035: import javax.ejb.EJBMetaData;
036: import javax.management.ObjectName;
037: import javax.naming.InitialContext;
038: import javax.naming.NamingException;
039:
040: import org.jboss.deployment.DeploymentException;
041: import org.jboss.ejb.Container;
042: import org.jboss.ejb.EJBProxyFactory;
043: import org.jboss.ejb.EJBProxyFactoryContainer;
044: import org.jboss.invocation.Invocation;
045: import org.jboss.invocation.Invoker;
046: import org.jboss.invocation.InvocationContext;
047: import org.jboss.invocation.InvocationKey;
048: import org.jboss.logging.Logger;
049: import org.jboss.metadata.InvokerProxyBindingMetaData;
050: import org.jboss.metadata.MetaData;
051: import org.jboss.metadata.EntityMetaData;
052: import org.jboss.metadata.SessionMetaData;
053: import org.jboss.metadata.BeanMetaData;
054: import org.jboss.util.naming.Util;
055: import org.jboss.proxy.Interceptor;
056: import org.jboss.proxy.ClientContainer;
057: import org.jboss.proxy.ClientContainerEx;
058: import org.jboss.proxy.IClientContainer;
059: import org.jboss.proxy.ejb.handle.HomeHandleImpl;
060: import org.jboss.system.Registry;
061: import org.jboss.util.NestedRuntimeException;
062: import org.w3c.dom.Element;
063: import org.w3c.dom.Node;
064: import org.w3c.dom.NodeList;
065:
066: /**
067: * As we remove the one one association between container STACK and invoker we
068: * keep this around. IN the future the creation of proxies is a task done on a
069: * container basis but the container as a logical representation. In other
070: * words, the container "Entity with RMI/IIOP" is not a container stack but
071: * an association at the invocation level that points to all metadata for
072: * a given container.
073: * <p/>
074: * In other words this is here for legacy reason and to not disrupt the
075: * container at once.
076: * In particular we declare that we "implement" the container invoker
077: * interface when we are just implementing the Proxy generation calls.
078: * Separation of concern.
079: * <p/>
080: * todo eliminate this class, at least in its present form.
081: *
082: * @author <a href="mailto:marc.fleury@jboss.org">Marc Fleury</a>
083: * @author <a href="mailto:scott.stark@jboss.org">Scott Stark/a>
084: * @author <a href="mailto:thomas.diesler@jboss.org">Thomas Diesler/a>
085: * @version $Revision: 61258 $
086: */
087: public class ProxyFactory implements EJBProxyFactory {
088: protected static final String HOME_INTERCEPTOR = "home";
089: protected static final String BEAN_INTERCEPTOR = "bean";
090: protected static final String LIST_ENTITY_INTERCEPTOR = "list-entity";
091:
092: protected static Logger log = Logger.getLogger(ProxyFactory.class);
093:
094: // Metadata for the proxies
095: public EJBMetaData ejbMetaData;
096:
097: // as of EJB2.1, we may have the case of web-service enabled beans without
098: // remote interface, we will simply "mute" this factory in this case
099: protected boolean isServiceEndpointOnly;
100:
101: protected EJBHome home;
102: protected EJBObject statelessObject;
103:
104: // The name of the bean being deployed
105: protected String jndiBinding;
106: protected ObjectName jmxName;
107: protected int jmxNameHash;
108: private Integer jmxNameHashInteger;
109:
110: // The name of the delegate invoker
111: // We have a beanInvoker and homeInvoker
112: // because clustering has a different invoker for each
113: // and we want to reuse code here.
114: protected Invoker beanInvoker;
115: protected Invoker homeInvoker;
116: protected InvokerProxyBindingMetaData invokerMetaData;
117:
118: /**
119: * The proxy-config/client-interceptors/home stack
120: */
121: protected ArrayList homeInterceptorClasses = new ArrayList();
122: /**
123: * The proxy-config/client-interceptors/bean stack
124: */
125: protected ArrayList beanInterceptorClasses = new ArrayList();
126: /**
127: * The proxy-config/client-interceptors/entity-list stack
128: */
129: protected ArrayList listEntityInterceptorClasses = new ArrayList();
130: /** A flag indicating if the IClientContainer interface should be added */
131: protected boolean includeIClientIface;
132: // A pointer to the container this proxy factory is dedicated to
133: protected Container container;
134:
135: protected Constructor proxyClassConstructor;
136:
137: // Container plugin implementation -----------------------------------------
138:
139: public void setContainer(Container con) {
140: this .container = con;
141: }
142:
143: public void setInvokerMetaData(InvokerProxyBindingMetaData metadata) {
144: this .invokerMetaData = metadata;
145: }
146:
147: public void setInvokerBinding(String binding) {
148: this .jndiBinding = binding;
149: }
150:
151: public void create() throws Exception {
152: jmxName = container.getJmxName();
153: jmxNameHash = jmxName.hashCode();
154: jmxNameHashInteger = new Integer(jmxNameHash);
155: // Create metadata
156:
157: BeanMetaData bmd = container.getBeanMetaData();
158: boolean isSession = !(bmd instanceof EntityMetaData);
159: boolean isStatelessSession = false;
160: if (isSession) {
161: SessionMetaData smd = (SessionMetaData) bmd;
162: if (bmd.getRemote() == null) {
163: isServiceEndpointOnly = true;
164: // nothing more to do
165: return;
166: }
167: isStatelessSession = smd.isStateless();
168: }
169: Class pkClass = null;
170: if (!isSession) {
171: EntityMetaData metaData = (EntityMetaData) bmd;
172: String pkClassName = metaData.getPrimaryKeyClass();
173: try {
174: if (pkClassName != null) {
175: pkClass = container.getClassLoader().loadClass(
176: pkClassName);
177: } else {
178: pkClass = container.getClassLoader().loadClass(
179: metaData.getEjbClass()).getField(
180: metaData.getPrimKeyField()).getClass();
181: }
182: } catch (NoSuchFieldException e) {
183: log
184: .error("Unable to identify Bean's Primary Key class!"
185: + " Did you specify a primary key class and/or field? Does that field exist?");
186: throw new RuntimeException("Primary Key Problem");
187: } catch (NullPointerException e) {
188: log
189: .error("Unable to identify Bean's Primary Key class!"
190: + " Did you specify a primary key class and/or field? Does that field exist?");
191: throw new RuntimeException("Primary Key Problem");
192: }
193: }
194:
195: ejbMetaData = new EJBMetaDataImpl(
196: ((EJBProxyFactoryContainer) container).getRemoteClass(),
197: ((EJBProxyFactoryContainer) container).getHomeClass(),
198: pkClass, //null if not entity
199: isSession, //Session
200: isStatelessSession, //Stateless
201: new HomeHandleImpl(jndiBinding));
202:
203: if (log.isDebugEnabled()) {
204: log.debug("Proxy Factory for " + jndiBinding
205: + " initialized");
206: }
207:
208: initInterceptorClasses();
209: }
210:
211: /**
212: * Become fully available. At this point our invokers should be started
213: * and we can bind the homes into JNDI.
214: */
215: public void start() throws Exception {
216: if (!isServiceEndpointOnly) {
217: setupInvokers();
218: bindProxy();
219: }
220: }
221:
222: /**
223: * Lookup the invokers in the object registry. This typically cannot
224: * be done until our start method as the invokers may need to be started
225: * themselves.
226: */
227: protected void setupInvokers() throws Exception {
228: ObjectName oname = new ObjectName(invokerMetaData
229: .getInvokerMBean());
230: Invoker invoker = (Invoker) Registry.lookup(oname);
231: if (invoker == null) {
232: throw new RuntimeException("invoker is null: " + oname);
233: }
234:
235: homeInvoker = beanInvoker = invoker;
236: }
237:
238: /**
239: * Load the client interceptor classes
240: */
241: protected void initInterceptorClasses() throws Exception {
242: HashMap interceptors = new HashMap();
243:
244: Element proxyConfig = invokerMetaData.getProxyFactoryConfig();
245: Element clientInterceptors = MetaData.getOptionalChild(
246: proxyConfig, "client-interceptors", null);
247: if (clientInterceptors != null) {
248: String value = MetaData.getElementAttribute(
249: clientInterceptors, "exposeContainer");
250: this .includeIClientIface = Boolean.valueOf(value)
251: .booleanValue();
252: NodeList children = clientInterceptors.getChildNodes();
253: for (int i = 0; i < children.getLength(); i++) {
254: Node currentChild = children.item(i);
255: if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
256: Element interceptor = (Element) children.item(i);
257: interceptors.put(interceptor.getTagName(),
258: interceptor);
259: }
260: }
261: } else {
262: log.debug("client interceptors element is null");
263: }
264: Element homeInterceptorConf = (Element) interceptors
265: .get(HOME_INTERCEPTOR);
266: loadInterceptorClasses(homeInterceptorClasses,
267: homeInterceptorConf);
268: if (homeInterceptorClasses.size() == 0) {
269: throw new DeploymentException(
270: "There are no home interface interceptors configured");
271: }
272:
273: Element beanInterceptorConf = (Element) interceptors
274: .get(BEAN_INTERCEPTOR);
275: loadInterceptorClasses(beanInterceptorClasses,
276: beanInterceptorConf);
277: if (beanInterceptorClasses.size() == 0) {
278: throw new DeploymentException(
279: "There are no bean interface interceptors configured");
280: }
281:
282: Element listEntityInterceptorConf = (Element) interceptors
283: .get(LIST_ENTITY_INTERCEPTOR);
284: loadInterceptorClasses(listEntityInterceptorClasses,
285: listEntityInterceptorConf);
286: }
287:
288: /**
289: * The <code>loadInterceptorClasses</code> load an interceptor classes from
290: * configuration
291: *
292: * @throws Exception if an error occurs
293: */
294: protected void loadInterceptorClasses(ArrayList classes,
295: Element interceptors) throws Exception {
296: Iterator interceptorElements = MetaData.getChildrenByTagName(
297: interceptors, "interceptor");
298: ClassLoader loader = container.getClassLoader();
299: while (interceptorElements != null
300: && interceptorElements.hasNext()) {
301: Element ielement = (Element) interceptorElements.next();
302: String className = null;
303: className = MetaData.getElementContent(ielement);
304:
305: // load the invoker interceptor that corresponds to the beans call semantic
306: String byValueAttr = MetaData.getElementAttribute(ielement,
307: "call-by-value");
308: if (byValueAttr != null) {
309: if (container.isCallByValue() == new Boolean(
310: byValueAttr).booleanValue()) {
311: Class clazz = loader.loadClass(className);
312: classes.add(clazz);
313: }
314: } else {
315: Class clazz = loader.loadClass(className);
316: classes.add(clazz);
317: }
318: }
319: }
320:
321: /**
322: * The <code>loadInterceptorChain</code> create instances of interceptor
323: * classes previously loaded in loadInterceptorClasses
324: *
325: * @throws Exception if an error occurs
326: */
327: protected void loadInterceptorChain(ArrayList chain,
328: ClientContainer client) throws Exception {
329: Interceptor last = null;
330: for (int i = 0; i < chain.size(); i++) {
331: Class clazz = (Class) chain.get(i);
332: Interceptor interceptor = (Interceptor) clazz.newInstance();
333: if (last == null) {
334: last = interceptor;
335: client.setNext(interceptor);
336: } else {
337: last.setNext(interceptor);
338: last = interceptor;
339: }
340: }
341: }
342:
343: /**
344: * The <code>bindProxy</code> method creates the home proxy and binds
345: * the home into jndi. It also creates the InvocationContext and client
346: * container and interceptor chain.
347: *
348: * @throws Exception if an error occurs
349: */
350: protected void bindProxy() throws Exception {
351: try {
352: // Create a stack from the description (in the future) for now we hardcode it
353: InvocationContext context = new InvocationContext();
354:
355: context.setObjectName(jmxNameHashInteger);
356: context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
357: // The behavior for home proxying should be isolated in an interceptor FIXME
358: context.setInvoker(homeInvoker);
359: context.setValue(InvocationKey.EJB_METADATA, ejbMetaData);
360: context.setInvokerProxyBinding(invokerMetaData.getName());
361:
362: ClientContainer client = null;
363: EJBProxyFactoryContainer pfc = (EJBProxyFactoryContainer) container;
364: Class[] ifaces = { pfc.getHomeClass(),
365: Class.forName("javax.ejb.Handle") };
366: if (includeIClientIface) {
367: ifaces = new Class[] { IClientContainer.class,
368: pfc.getHomeClass(),
369: Class.forName("javax.ejb.Handle") };
370: client = new ClientContainerEx(context);
371: } else {
372: client = new ClientContainer(context);
373: }
374: loadInterceptorChain(homeInterceptorClasses, client);
375:
376: // Create the EJBHome
377: this .home = (EJBHome) Proxy.newProxyInstance(
378: // Class loader pointing to the right classes from deployment
379: pfc.getHomeClass().getClassLoader(),
380: // The classes we want to implement home and handle
381: ifaces,
382: // The home proxy as invocation handler
383: client);
384:
385: // Create stateless session object
386: // Same instance is used for all objects
387: if (ejbMetaData.isStatelessSession() == true) {
388: // Create a stack from the description (in the future) for now we hardcode it
389: context = new InvocationContext();
390:
391: context.setObjectName(jmxNameHashInteger);
392: context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
393: // The behavior for home proxying should be isolated in an interceptor FIXME
394: context.setInvoker(beanInvoker);
395: context.setInvokerProxyBinding(invokerMetaData
396: .getName());
397: context.setValue(InvocationKey.EJB_HOME, home);
398:
399: Class[] ssifaces = { pfc.getRemoteClass() };
400: if (includeIClientIface) {
401: ssifaces = new Class[] { IClientContainer.class,
402: pfc.getRemoteClass() };
403: client = new ClientContainerEx(context);
404: } else {
405: client = new ClientContainer(context);
406: }
407: loadInterceptorChain(beanInterceptorClasses, client);
408:
409: this .statelessObject = (EJBObject) Proxy
410: .newProxyInstance(
411: // Correct CL
412: pfc.getRemoteClass().getClassLoader(),
413: // Interfaces
414: ssifaces,
415: // SLSB proxy as invocation handler
416: client);
417: } else {
418: // this is faster than newProxyInstance
419: Class[] intfs = { pfc.getRemoteClass() };
420: if (this .includeIClientIface) {
421: intfs = new Class[] { IClientContainer.class,
422: pfc.getRemoteClass() };
423: }
424: Class proxyClass = Proxy.getProxyClass(pfc
425: .getRemoteClass().getClassLoader(), intfs);
426: final Class[] constructorParams = { InvocationHandler.class };
427: proxyClassConstructor = proxyClass
428: .getConstructor(constructorParams);
429: }
430:
431: // Bind the home in the JNDI naming space
432: rebindHomeProxy();
433: } catch (Exception e) {
434: throw new ServerException("Could not bind home", e);
435: }
436: }
437:
438: protected void rebindHomeProxy() throws NamingException {
439: // (Re-)Bind the home in the JNDI naming space
440: log.debug("(re-)Binding Home " + jndiBinding);
441: Util.rebind(
442: // The context
443: new InitialContext(),
444: // Jndi name
445: jndiBinding,
446: // The Home
447: getEJBHome());
448:
449: log.info("Bound EJB Home '"
450: + container.getBeanMetaData().getEjbName()
451: + "' to jndi '" + jndiBinding + "'");
452: }
453:
454: public void stop() {
455: }
456:
457: public void destroy() {
458: if (!isServiceEndpointOnly) {
459: log.info("Unbind EJB Home '"
460: + container.getBeanMetaData().getEjbName()
461: + "' from jndi '" + jndiBinding + "'");
462:
463: try {
464: InitialContext ctx = new InitialContext();
465: ctx.unbind(jndiBinding);
466: } catch (Exception e) {
467: // ignore.
468: }
469: homeInterceptorClasses.clear();
470: beanInterceptorClasses.clear();
471: listEntityInterceptorClasses.clear();
472: }
473:
474: container = null;
475: ejbMetaData = null;
476: home = null;
477: statelessObject = null;
478: beanInvoker = null;
479: homeInvoker = null;
480: invokerMetaData = null;
481: proxyClassConstructor = null;
482: }
483:
484: // EJBProxyFactory implementation -------------------------------------
485:
486: public boolean isIdentical(Container container, Invocation mi) {
487: throw new UnsupportedOperationException(
488: "TODO provide a default implementation");
489: }
490:
491: public EJBMetaData getEJBMetaData() {
492: return ejbMetaData;
493: }
494:
495: public Object getEJBHome() {
496: return home;
497: }
498:
499: /**
500: * Return the EJBObject proxy for stateless sessions.
501: */
502: public Object getStatelessSessionEJBObject() {
503:
504: return statelessObject;
505: }
506:
507: /**
508: * Create an EJBObject proxy for a stateful session given its session id.
509: */
510: public Object getStatefulSessionEJBObject(Object id) {
511: // Create a stack from the description (in the future) for now we hardcode it
512: InvocationContext context = new InvocationContext();
513:
514: context.setObjectName(jmxNameHashInteger);
515: context.setCacheId(id);
516: context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
517: context.setInvoker(beanInvoker);
518: log.debug("seting invoker proxy binding for stateful session: "
519: + invokerMetaData.getName());
520: context.setInvokerProxyBinding(invokerMetaData.getName());
521: context.setValue(InvocationKey.EJB_HOME, home);
522: context.setValue("InvokerID", Invoker.ID);
523:
524: ClientContainer client;
525: if (includeIClientIface) {
526: client = new ClientContainerEx(context);
527: } else {
528: client = new ClientContainer(context);
529: }
530:
531: try {
532: loadInterceptorChain(beanInterceptorClasses, client);
533: } catch (Exception e) {
534: throw new NestedRuntimeException(
535: "Failed to load interceptor chain", e);
536: }
537:
538: try {
539: return (EJBObject) proxyClassConstructor
540: .newInstance(new Object[] { client });
541: } catch (Exception ex) {
542: throw new NestedRuntimeException(ex);
543: }
544:
545: }
546:
547: /**
548: * Create an EJBObject proxy for an entity given its primary key.
549: */
550: public Object getEntityEJBObject(Object id) {
551: Object result;
552: if (id == null) {
553: result = null;
554: } else {
555: // Create a stack from the description (in the future) for now we hardcode it
556: InvocationContext context = new InvocationContext();
557:
558: context.setObjectName(jmxNameHashInteger);
559: context.setCacheId(id);
560: context.setValue(InvocationKey.JNDI_NAME, jndiBinding);
561: context.setInvoker(beanInvoker);
562: context.setInvokerProxyBinding(invokerMetaData.getName());
563: context.setValue(InvocationKey.EJB_HOME, home);
564:
565: ClientContainer client;
566: if (includeIClientIface) {
567: client = new ClientContainerEx(context);
568: } else {
569: client = new ClientContainer(context);
570: }
571:
572: try {
573: loadInterceptorChain(beanInterceptorClasses, client);
574: } catch (Exception e) {
575: throw new NestedRuntimeException(
576: "Failed to load interceptor chain", e);
577: }
578:
579: try {
580: result = proxyClassConstructor
581: .newInstance(new Object[] { client });
582: } catch (Exception ex) {
583: throw new NestedRuntimeException(ex);
584: }
585: }
586: return result;
587: }
588:
589: /**
590: * Create a Collection EJBObject proxies for an entity given its primary keys.
591: */
592: public Collection getEntityCollection(Collection ids) {
593: ArrayList list = new ArrayList(ids.size());
594: Iterator idEnum = ids.iterator();
595:
596: while (idEnum.hasNext()) {
597: Object nextId = idEnum.next();
598: list.add(getEntityEJBObject(nextId));
599: }
600: return list;
601: }
602: }
|