001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
004: * All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program 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
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * $Id: EJBUtil.java,v 1.8 2007/03/27 21:59:42 mlipp Exp $
021: *
022: * $Log: EJBUtil.java,v $
023: * Revision 1.8 2007/03/27 21:59:42 mlipp
024: * Fixed lots of checkstyle warnings.
025: *
026: * Revision 1.7 2006/10/11 09:42:36 drmlipp
027: * Comepleted adaption to local KeyGen EJB.
028: *
029: * Revision 1.6 2006/10/11 09:06:08 drmlipp
030: * Fixed EJB naming.
031: *
032: * Revision 1.5 2006/10/11 07:47:02 drmlipp
033: * Adapted properties file to new convention.
034: *
035: * Revision 1.4 2006/10/07 20:41:34 mlipp
036: * Merged J2EE 1.4 adaptions from test branch.
037: *
038: * Revision 1.3 2006/09/29 12:32:08 drmlipp
039: * Consistently using WfMOpen as projct name now.
040: *
041: * Revision 1.2 2005/09/10 21:44:18 mlipp
042: * Fixed JDK 5.0 warnings.
043: *
044: * Revision 1.1.1.3 2004/08/18 15:17:34 drmlipp
045: * Update to 1.2
046: *
047: * Revision 1.19 2004/02/13 10:32:22 lipp
048: * Added support for local home interfaces.
049: *
050: * Revision 1.18 2003/10/27 15:28:51 lipp
051: * Fixed repeat behaviour.
052: *
053: * Revision 1.17 2003/10/27 14:33:37 lipp
054: * Added debug statements.
055: *
056: * Revision 1.16 2003/10/24 11:08:01 lipp
057: * Added retrievJNDIEntry.
058: *
059: * Revision 1.15 2003/10/22 15:58:45 lipp
060: * Fixed synchronization problem.
061: *
062: * Revision 1.14 2003/09/09 13:38:47 lipp
063: * Added support for local home and business interfaces.
064: *
065: * Revision 1.13 2003/06/29 19:50:07 lipp
066: * Moved primary key generator fropm JDBCUtil to EJBUtil and made some
067: * fixes.
068: *
069: * Revision 1.12 2003/06/27 08:51:47 lipp
070: * Fixed copyright/license information.
071: *
072: * Revision 1.11 2003/05/23 15:42:41 lipp
073: * Fixed deployment unit dependencies.
074: *
075: * Revision 1.10 2003/03/31 16:50:27 huaiyang
076: * Logging using common-logging.
077: *
078: * Revision 1.9 2002/09/26 15:05:13 lipp
079: * Added method and extended comments.
080: *
081: * Revision 1.8 2002/08/30 13:37:04 lipp
082: * Using Workflow engine facade now.
083: *
084: * Revision 1.7 2002/07/29 21:03:57 lipp
085: * Some JavaDoc fixes.
086: *
087: * Revision 1.6 2002/07/24 05:48:16 huaiyang
088: * javadocs added.
089: *
090: * Revision 1.5 2002/07/03 11:02:17 lipp
091: * New session ejb handling support.
092: *
093: * Revision 1.4 2002/02/06 16:02:29 lipp
094: * Added caching for home interfaces.
095: *
096: * Revision 1.3 2001/12/12 14:11:45 lipp
097: * Added environment lookup.
098: *
099: * Revision 1.2 2001/10/25 17:02:13 robert
100: * javadoc
101: *
102: * Revision 1.1 2001/10/25 07:41:23 lipp
103: * Moved EJBUtil to de.danet.an.util
104: *
105: * Revision 1.1 2001/10/21 20:03:00 lipp
106: * Initial version
107: *
108: */
109:
110: package de.danet.an.util;
111:
112: import java.io.IOException;
113: import java.io.InputStream;
114:
115: import java.util.HashMap;
116: import java.util.Map;
117: import java.util.Properties;
118:
119: import java.lang.reflect.InvocationTargetException;
120: import java.lang.reflect.Method;
121: import java.rmi.RemoteException;
122:
123: import javax.ejb.CreateException;
124: import javax.ejb.EJBException;
125: import javax.ejb.EJBHome;
126: import javax.ejb.EJBLocalHome;
127: import javax.ejb.EJBLocalObject;
128: import javax.ejb.EJBObject;
129: import javax.ejb.RemoveException;
130: import javax.naming.CommunicationException;
131: import javax.naming.InitialContext;
132: import javax.naming.InterruptedNamingException;
133: import javax.naming.NamingException;
134: import javax.rmi.PortableRemoteObject;
135:
136: /**
137: * Collection of EJB utilities.
138: * This class adds common utilitites for an EJB environment.
139: */
140: public class EJBUtil {
141:
142: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
143: .getLog(EJBUtil.class);
144:
145: /**
146: * The initial context used for lookup.
147: */
148: private static InitialContext initialContext = null;
149:
150: /** Size of low key. */
151: private static int lowKeySize = 64;
152:
153: /** Key generation EJB name. */
154: private static String keyGenEjbName = null;
155:
156: /** Key generation high table. */
157: private static String keyGenTable = null;
158:
159: /**
160: * Lookup a JNDI entry. This method implements caching as not all
161: * application servers implement JNDI efficiently. As a
162: * consequence, this method can only be used for read-only entries
163: * that do not change over time.
164: *
165: * @param entry the JNDI name of the entry
166: * @return the object found object
167: * @throws NamingException if the JNDI lookup fails.
168: */
169: public static Object lookupJNDIEntry(String entry)
170: throws NamingException {
171: if (initialContext == null) {
172: initialContext = new InitialContext();
173: }
174: Object objref = null;
175: try {
176: objref = initialContext.lookup(entry);
177: } catch (NamingException ne) {
178: // maybe naming context is no longer valid (e.g. cluster), retry
179: initialContext = new InitialContext();
180: objref = initialContext.lookup(entry);
181: }
182: return objref;
183: }
184:
185: /**
186: * Lookup a JNDI entry using {@link #lookupJNDIEntry
187: * <code>lookupJNDIEntry</code>}. This should be called from a
188: * context where the JNDI name is expected
189: * to exist. <code>NamingException</code>s should thus only occur
190: * if the service is not available due to dynamic error conditions
191: * (network failure etc.). These exceptions are mapped to a
192: * <code>ResourceNotAvailable</code> exception.<P>
193: *
194: * If, nevertheless, the <code>NamingException</code> indicates
195: * that the JNDI entry does not exists, we assume a configuration
196: * error and throw an <code>IllegalStateException</code>.
197: *
198: * @param entry the JNDI name
199: * @return an EJBHome object
200: * @throws ResourceNotAvailableException if a problem occurs that
201: * should be temporary.
202: * @throws IllegalStateException if the given
203: * <code>jndiName</code> is not registered.
204: */
205: public static Object retrieveJNDIEntry(String entry)
206: throws ResourceNotAvailableException, IllegalStateException {
207: try {
208: return lookupJNDIEntry(entry);
209: } catch (CommunicationException e) {
210: logger.error(e.getMessage(), e);
211: throw new ResourceNotAvailableException(e.getMessage());
212: } catch (InterruptedNamingException e) {
213: logger.error(e.getMessage(), e);
214: throw new ResourceNotAvailableException(e.getMessage());
215: } catch (NamingException e) {
216: logger.error(e.getMessage(), e);
217: throw new IllegalStateException(e.getMessage());
218: }
219: }
220:
221: private static Map homeCache = new HashMap();
222:
223: /**
224: * Lookup a home interface using {@link #lookupJNDIEntry
225: * <code>lookupJNDIEntry</code>}.
226: *
227: * The method provides caching of looked up home interface
228: * if the JNDI name is global (i.e. does not start with
229: * "<code>java:comp/</code>").
230: *
231: * @param ejbClass the class of the home interface
232: * @param jndiName the JNDI name of the home interface
233: * @return an EJBHome object
234: * @throws NamingException if the JNDI lookup fails.
235: */
236: public static EJBHome lookupEJBHome(Class ejbClass, String jndiName)
237: throws NamingException {
238: if (jndiName.startsWith("java:comp/")) {
239: Object objref = lookupJNDIEntry(jndiName);
240: return (EJBHome) PortableRemoteObject.narrow(objref,
241: ejbClass);
242: }
243: synchronized (homeCache) {
244: EJBHome ejbHome = (EJBHome) homeCache.get(jndiName);
245: if (ejbHome == null) {
246: ejbHome = (EJBHome) PortableRemoteObject.narrow(
247: lookupJNDIEntry(jndiName), ejbClass);
248: homeCache.put(jndiName, ejbHome);
249: }
250: return ejbHome;
251: }
252: }
253:
254: /**
255: * Return an EJB's home interface using {@link #lookupEJBHome
256: * <code>lookupEJBHome</code>}. This should be called from a
257: * context where the JNDI name of the home interface is expected
258: * to exist. <code>NamingException</code>s should thus only occur
259: * if the service is not available due to dynamic error conditions
260: * (network failure etc.). These exceptions are mapped to a
261: * <code>ResourceNotAvailable</code> exception.<P>
262: *
263: * If, nevertheless, the <code>NamingException</code> indicates
264: * that the JNDI entry does not exists, we assume a configuration
265: * error and throw an <code>IllegalStateException</code>.
266: *
267: * @param ejbClass the class of the home interface
268: * @param jndiName the JNDI name of the home interface
269: * @return an EJBHome object
270: * @throws ResourceNotAvailableException if a problem occurs that
271: * should be temporary.
272: * @throws IllegalStateException if the given
273: * <code>jndiName</code> is not registered.
274: */
275: public static EJBHome retrieveEJBHome(Class ejbClass,
276: String jndiName) throws ResourceNotAvailableException,
277: IllegalStateException {
278: try {
279: return lookupEJBHome(ejbClass, jndiName);
280: } catch (CommunicationException e) {
281: logger.error(e.getMessage(), e);
282: throw new ResourceNotAvailableException(e.getMessage());
283: } catch (InterruptedNamingException e) {
284: logger.error(e.getMessage(), e);
285: throw new ResourceNotAvailableException(e.getMessage());
286: } catch (NamingException e) {
287: logger.error(e.getMessage(), e);
288: throw new IllegalStateException(e.getMessage());
289: }
290: }
291:
292: /**
293: * Lookup a local home interface using {@link #lookupJNDIEntry
294: * <code>lookupJNDIEntry</code>}.
295: *
296: * @param ejbClass the class of the home interface
297: * @param jndiName the JNDI name of the home interface
298: * @return an EJBHome object
299: * @throws NamingException if the JNDI lookup fails.
300: */
301: public static EJBLocalHome lookupEJBLocalHome(Class ejbClass,
302: String jndiName) throws NamingException {
303: Object objref = lookupJNDIEntry(jndiName);
304: return (EJBLocalHome) PortableRemoteObject.narrow(objref,
305: ejbClass);
306: }
307:
308: /**
309: * Return an EJB's local home interface using {@link
310: * #lookupEJBHome <code>lookupEJBLocalHome</code>}. This should be
311: * called from a context where the JNDI name of the home interface
312: * is expected to exist. <code>NamingException</code>s should thus
313: * only occur if the service is not available due to dynamic error
314: * conditions (network failure etc.). These exceptions are mapped
315: * to a <code>ResourceNotAvailable</code> exception.<P>
316: *
317: * If, nevertheless, the <code>NamingException</code> indicates
318: * that the JNDI entry does not exists, we assume a configuration
319: * error and throw an <code>IllegalStateException</code>.
320: *
321: * @param ejbClass the class of the home interface
322: * @param jndiName the JNDI name of the home interface
323: * @return an EJBHome object
324: * @throws ResourceNotAvailableException if a problem occurs that
325: * should be temporary.
326: * @throws IllegalStateException if the given
327: * <code>jndiName</code> is not registered.
328: */
329: public static EJBLocalHome retrieveEJBLocalHome(Class ejbClass,
330: String jndiName) throws ResourceNotAvailableException,
331: IllegalStateException {
332: try {
333: return lookupEJBLocalHome(ejbClass, jndiName);
334: } catch (CommunicationException e) {
335: logger.error(e.getMessage(), e);
336: throw new ResourceNotAvailableException(e.getMessage());
337: } catch (InterruptedNamingException e) {
338: logger.error(e.getMessage(), e);
339: throw new ResourceNotAvailableException(e.getMessage());
340: } catch (NamingException e) {
341: logger.error(e.getMessage(), e);
342: throw new IllegalStateException(e.getMessage());
343: }
344: }
345:
346: /**
347: * Create a session EJB. This method can be used for the special
348: * — but very common — case when you want to create a
349: * session EJB and the corresponding home interface has a create
350: * method without any paramaters.<P>
351: *
352: * The given home interface is looked up using {@link
353: * #retrieveEJBHome <code>retrieveEJBHome</code>} or {@link
354: * #retrieveEJBLocalHome <code>retrieveEJBLocalHome</code>} (as
355: * appropriate for the given class) and then the create session
356: * method is called.
357: *
358: * @param ejbClass the class of the home interface which must define a
359: * <code>create()</code> method.
360: * @param jndiName the JNDI name of the home interface
361: * @return the new <code>EJBObject</code>.
362: * @throws IllegalArgumentException if no <code>create</code>
363: * method without arguments exists.
364: * @throws ResourceNotAvailableException if a problem occurs that
365: * should be temporary.
366: */
367: public static Object createSession(Class ejbClass, String jndiName)
368: throws IllegalArgumentException,
369: ResourceNotAvailableException {
370: if (EJBHome.class.isAssignableFrom(ejbClass)) {
371: EJBHome home = retrieveEJBHome(ejbClass, jndiName);
372: return createSession(home);
373: } else {
374: EJBLocalHome home = retrieveEJBLocalHome(ejbClass, jndiName);
375: return createSession(home);
376: }
377: }
378:
379: /**
380: * Create a session EJB. This method can be used for the special -
381: * but very common - case when you want to create a session EJB
382: * and the corresponding home interface has a create method
383: * without any paramaters.<P>
384: *
385: * @param home the home interface
386: * @throws IllegalArgumentException if no <code>create</code>
387: * method without arguments exists.
388: * @throws ResourceNotAvailableException if a problem occurs that
389: * should be temporary.
390: * @return the new <code>EJBObject</code>.
391: */
392: public static EJBObject createSession(EJBHome home)
393: throws IllegalArgumentException,
394: ResourceNotAvailableException {
395: Class homeClass = null;
396: try {
397: homeClass = home.getClass();
398: Method createMethod = homeClass.getMethod("create",
399: (Class[]) null);
400: return (EJBObject) createMethod.invoke(home, new Object[0]);
401: } catch (NoSuchMethodException nm) {
402: throw new IllegalArgumentException(homeClass.getName()
403: + " does not define a create() method.");
404: } catch (IllegalAccessException ia) {
405: throw new IllegalArgumentException("Calling "
406: + homeClass.getName() + ".create() not allowed: "
407: + ia.getMessage());
408: } catch (InvocationTargetException ie) {
409: Throwable tex = ie.getTargetException();
410: logger.warn("Exception in " + homeClass.getName()
411: + ".create():", unwrapEJBException(tex));
412: logger.warn("Unwrapped exception is:", tex);
413: if ((tex instanceof RemoteException)
414: || (tex instanceof CreateException)
415: || (tex instanceof EJBException)) {
416: // these should be temporary conditions
417: throw new ResourceNotAvailableException(tex
418: .getMessage());
419: }
420: throw new IllegalArgumentException(tex.getMessage());
421: }
422: }
423:
424: /**
425: * Create a session EJB. This method can be used for the special -
426: * but very common - case when you want to create a session EJB
427: * and the corresponding home interface has a create method
428: * without any paramaters.<P>
429: *
430: * @param home the home interface
431: * @throws IllegalArgumentException if no <code>create</code>
432: * method without arguments exists.
433: * @throws ResourceNotAvailableException if a problem occurs that
434: * should be temporary.
435: * @return the new <code>EJBObject</code>.
436: */
437: public static EJBLocalObject createSession(EJBLocalHome home)
438: throws IllegalArgumentException,
439: ResourceNotAvailableException {
440: Class homeClass = null;
441: try {
442: homeClass = home.getClass();
443: Method createMethod = homeClass.getMethod("create",
444: (Class[]) null);
445: return (EJBLocalObject) createMethod.invoke(home,
446: new Object[0]);
447: } catch (NoSuchMethodException nm) {
448: throw new IllegalArgumentException(homeClass.getName()
449: + " does not define a create() method.");
450: } catch (IllegalAccessException ia) {
451: throw new IllegalArgumentException("Calling "
452: + homeClass.getName() + ".create() not allowed: "
453: + ia.getMessage());
454: } catch (InvocationTargetException ie) {
455: Throwable tex = ie.getTargetException();
456: logger.warn("Exception in " + homeClass.getName()
457: + ".create():", unwrapEJBException(tex));
458: logger.warn("Unwrapped exception is:", tex);
459: if ((tex instanceof RemoteException)
460: || (tex instanceof CreateException)
461: || (tex instanceof EJBException)) {
462: // these should be temporary conditions
463: throw new ResourceNotAvailableException(tex
464: .getMessage());
465: }
466: throw new IllegalArgumentException(tex.getMessage());
467: }
468: }
469:
470: /**
471: * Convenience method to clean up a session connection after
472: * usage. If the given parameter is an instance of
473: * <code>EJBObject</code> it simply calls <code>remove()</code> on
474: * the given object. If the parameter is <code>null</code>
475: * (indicating that the previous session creation propably failed)
476: * nothing happens. If an error occurs when calling
477: * <code>remove()</code>, it will be logged as warning.
478: * @param handle the — maybe — ejb session object.
479: */
480: public static void removeSession(Object handle) {
481: if (handle == null || !(handle instanceof EJBObject)) {
482: return;
483: }
484: try {
485: ((EJBObject) handle).remove();
486: } catch (RemoveException rex) {
487: logger.warn("Error in remove(): " + rex.getMessage(), rex);
488: } catch (RemoteException rex) {
489: logger.warn("Error in remove(): " + rex.getMessage(), rex);
490: }
491: }
492:
493: /**
494: * Unwrap the root cuase of an <code>EJBException</code>. The
495: * <code>EJBException</code> wrappers often hide the root cause of
496: * an exception. This method calls
497: * <code>getCausedByException()</code> until it finds an exception
498: * that is not of type <code>EJBException</code>. This exception
499: * is returned.
500: *
501: * @param ex the exception to be unwrapped.
502: * @return the root cause exception.
503: */
504: public static Throwable unwrapEJBException(Throwable ex) {
505: while (ex instanceof EJBException) {
506: ex = ((EJBException) ex).getCausedByException();
507: }
508: return ex;
509: }
510:
511: /**
512: * Included database id values of each database tables. If a table is
513: * not present it is added.
514: */
515: private static Map counters = new HashMap();
516:
517: private static class HighLow {
518: public long high;
519: public int low;
520: }
521:
522: /**
523: * Gets the next unique primary key for the given database table
524: * observing a minimum value.<P>
525: *
526: * This method uses a high/low algorithm. The high values are
527: * obtained from an EJB that maintains a table in a database.
528: * The JNDI name of the ejb used can be set in a properties file
529: * "<code>/de.danet.an.util.jdbcKeyGen.properties</code> with the
530: * entry "<code>generatorEjbJndiName</code>". <P>
531: *
532: * The JNDI name defaults to
533: * "<code>java:comp/env/ejb/JdbcKeyGenLocal</code>". This reflects the
534: * assumption that this method is usually called from an EJB or
535: * servlet. Of course, the deployment descriptor of the calling
536: * EJB or servlet must include an <code><ejb-ref></code>
537: * entry that links to the local home of EJB "KeyGen".<P>
538: *
539: * The table used by the EJB can be set with the property
540: * "<code>highKeyTable</code>". It defaults to
541: * "<code>KeyGeneratorHighs</code>". This table must be created as:
542: * <pre>create table KeyGeneratorHighs (
543: * TabName VARCHAR(50) NOT NULL,
544: * NextKey INTEGER NOT NULL
545: * )/</pre><P>
546: *
547: * Another property that can be set is "<code>highFactor</code>".
548: * It determines the number of low keys used before a new
549: * high key is requested. This value defaults to 64.
550: *
551: * @param table the name of the table.
552: * @param min the minimum value of the returned key.
553: * @return the new primary key.
554: * @throws ResourceNotAvailableException if an error occurs.
555: */
556: public static long newPrimaryKey(String table, long min)
557: throws ResourceNotAvailableException {
558: try {
559: if (keyGenEjbName == null) {
560: Properties props = new Properties();
561: try {
562: InputStream is = JDBCUtil.class
563: .getResourceAsStream("/de.danet.an.util.jdbcKeyGen.properties");
564: if (is != null) {
565: props.load(is);
566: }
567: } catch (IOException ex) {
568: logger.error("Cannot read jdbcKeyGen.properties: "
569: + ex.getMessage(), ex);
570: }
571: keyGenTable = props.getProperty("highKeyTable",
572: "KeyGeneratorHighs");
573: lowKeySize = Integer.parseInt(props.getProperty(
574: "highFactor", "64"));
575: keyGenEjbName = props.getProperty(
576: "generatorEjbJndiName",
577: "java:comp/env/ejb/JdbcKeyGenLocal");
578: }
579: // now do the work
580: HighLow hl = null;
581: synchronized (counters) {
582: hl = (HighLow) counters.get(table);
583: if (hl == null) {
584: hl = new HighLow();
585: hl.low = lowKeySize - 1;
586: counters.put(table, hl);
587: }
588: }
589: synchronized (hl) {
590: if (logger.isDebugEnabled()) {
591: logger.debug("Finding new key for " + table
592: + " using HighLow " + hl);
593: }
594: if (hl.low == (lowKeySize - 1)) {
595: KeyGenLocal keyGen = (KeyGenLocal) EJBUtil
596: .createSession(KeyGenLocalHome.class,
597: keyGenEjbName);
598: while (true) {
599: try {
600: hl.high = keyGen.newHigh(keyGenTable,
601: table, (min + lowKeySize - 1)
602: / lowKeySize);
603: break;
604: } catch (EJBException e) {
605: logger
606: .info("Problem calling newHigh (will be "
607: + "repeated): "
608: + e.getMessage());
609: }
610: }
611: EJBUtil.removeSession(keyGen);
612: hl.low = 0;
613: }
614: return hl.high * lowKeySize + hl.low++;
615: }
616: } catch (RemoteException e) {
617: logger.error("Cannot generate key: " + e.getMessage(), e);
618: throw new ResourceNotAvailableException(e.getMessage());
619: }
620: }
621:
622: /**
623: * Gets the next unique primary key for the given database table.
624: * @param table the name of the table.
625: * @return the new primary key.
626: * @throws ResourceNotAvailableException if an error occurs.
627: * @see #newPrimaryKey(String,long)
628: */
629: public static long newPrimaryKey(String table)
630: throws ResourceNotAvailableException {
631: return newPrimaryKey(table, 0);
632: }
633:
634: }
|