001: /**
002: * perseus/connector: this is an implementation of some JCA-related technologies
003: * (resource adapters and managers) for the ObjectWeb consortium.
004: * Copyright (C) 2001-2004 France Telecom R&D
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * Contact: speedo@objectweb.org
021: *
022: */package org.objectweb.speedo.jca;
023:
024: import org.objectweb.util.monolog.api.BasicLevel;
025: import org.objectweb.util.monolog.api.Logger;
026: import org.objectweb.util.monolog.api.LoggerFactory;
027: import org.objectweb.util.monolog.api.Loggable;
028: import org.objectweb.util.monolog.Monolog;
029: import org.objectweb.util.monolog.wrapper.printwriter.LoggerImpl;
030: import org.objectweb.speedo.api.Debug;
031: import org.objectweb.speedo.api.SpeedoProperties;
032: import org.objectweb.speedo.api.ExceptionHelper;
033: import org.objectweb.speedo.pm.api.POManagerItf;
034: import org.objectweb.speedo.pm.api.POManagerFactoryItf;
035:
036: import java.io.PrintWriter;
037: import java.io.FileInputStream;
038: import java.io.IOException;
039: import java.io.File;
040: import java.io.InputStream;
041: import java.io.FileNotFoundException;
042: import java.util.Set;
043: import java.util.Properties;
044: import java.util.HashMap;
045: import java.util.Iterator;
046: import javax.resource.ResourceException;
047: import javax.resource.spi.ConnectionManager;
048: import javax.resource.spi.ConnectionRequestInfo;
049: import javax.resource.spi.ManagedConnection;
050: import javax.resource.spi.ManagedConnectionFactory;
051: import javax.security.auth.Subject;
052: import javax.transaction.TransactionManager;
053: import javax.transaction.xa.Xid;
054: import javax.transaction.xa.XAException;
055: import javax.jdo.JDOHelper;
056: import javax.naming.InitialContext;
057: import javax.naming.NamingException;
058:
059: /**
060: * @author P. Dechamboux
061: */
062: public abstract class SpeedoManagedConnectionFactory implements
063: ManagedConnectionFactory, SpeedoAttributeController {
064:
065: private final static String[] DEFAULT_JNDI_NAMES = {
066: "javax.transaction.UserTransaction", //JOnAS
067: "javax.transaction.TransactionManager", //WebLogic
068: "java:/TransactionManager", //JBoss
069: "jta/usertransaction", //WebSphere
070: "java:comp/UserTransaction" //Orion
071: };
072:
073: /**
074: * The logger into which traces about SpeedoManagedConnectionFactory are
075: * produced.
076: */
077: private Logger logger;
078: /**
079: * It is assumed that only one ConnectionFactory is actually created by
080: * a ManagedConnectionFactory.
081: */
082: protected SpeedoConnectionFactory connectionFactory;
083: /**
084: * For creating all loggers related to JDO and its adapter.
085: */
086: private LoggerFactory loggerFactory = null;
087: /**
088: * The factory for managing JDO transaction contexts.
089: */
090: public POManagerFactoryItf pmf = null;
091:
092: /**
093: * The name of the property file of the PersistenceManagerFactory associated
094: * with this JDO connector.
095: */
096: private String propertiesFileName = null;
097: /**
098: * The factory for managing JDO transaction contexts.
099: */
100: private ConnectionManager connectionManager = null;
101:
102: private PrintWriter printWriter = null;
103:
104: public boolean started = false;
105:
106: /**
107: * the JNDI Name of the transaction manager
108: */
109: private String tmName = null;
110:
111: protected TransactionManager tm = null;
112:
113: /**
114: * The hashed structure that stores JdoTransaction that have been associated
115: * with a particular XID.
116: */
117: private HashMap xid2xac = new HashMap();
118:
119: /**
120: * This abstract method is used to create a ConnectionFactory. It should be
121: * implemented by the ManagedConnectionFactory associated with the actual
122: * implementation of the JCA driver (either for JDO or for EJB).
123: */
124: protected abstract SpeedoConnectionFactory createConnectionFactory(
125: Logger l, SpeedoManagedConnectionFactory fmcf,
126: ConnectionManager cm, byte transactionMode)
127: throws ResourceException;
128:
129: /**
130: * Lookup in JNDI the transaction manager under the name specified in
131: * parameter. The 'tm' variable is assigned if the transaction manager is
132: * found.
133: * @param name is the jndi name
134: * @return true if an instance implementing
135: * javax.transaction.TransactionManager is availlable in JNDI, otherwise
136: * false.
137: */
138: private boolean lookupTM(String name) {
139: InitialContext ictx = null;
140: try {
141: ictx = new InitialContext();
142: Object o = ictx.lookup(name);
143: if (o == null) {
144: logger.log(BasicLevel.WARN,
145: "JNDI retrieves a null value for the name '"
146: + name + "'.");
147: }
148: if (!(o instanceof TransactionManager)) {
149: logger
150: .log(
151: BasicLevel.WARN,
152: "JNDI retrieves an object which is not a javax.transaction.TransactionManager (JNDI name: "
153: + name + "): " + o);
154: return false;
155: }
156: tm = (TransactionManager) o;
157: logger.log(BasicLevel.INFO,
158: "The TransactionManager was found in JNDI with the name '"
159: + name + "'.");
160: return true;
161: } catch (Exception e) {
162: logger.log(BasicLevel.WARN,
163: "Error when lookup the transaction manager in JNDI with the name '"
164: + name + "'", e);
165: return false;
166: } finally {
167: if (ictx != null) {
168: try {
169: ictx.close();
170: } catch (NamingException e) {
171: }
172: }
173: }
174: }
175:
176: /**
177: * Starts this SpeedoManagedConnectionFactory.
178: */
179: public synchronized void start() throws ResourceException {
180: if (started) {
181: return;
182: }
183: //Logging initialisation
184: if (loggerFactory == null) {
185: if (Monolog.monologFactory == null) {
186: loggerFactory = Monolog.initialize();
187: } else {
188: loggerFactory = Monolog.monologFactory;
189: }
190: }
191: if (logger == null) {
192: logger = loggerFactory
193: .getLogger("org.objectweb.speedo.jca");
194: }
195:
196: //Load properties file of the JDO driver
197: if (pmf == null) {
198: Properties p = loadProperties();
199: if (p.get(SpeedoProperties.MANAGED) == null) {
200: //By default the transaction demarcation is done via the JTA API
201: p.put(SpeedoProperties.MANAGED, "true");
202: }
203: logger.log(BasicLevel.DEBUG, "Properties loaded:" + p);
204: findTransactionManager(p);
205:
206: // In managed environnement the mapping structure must be created
207: // before the server lauching. Indeed some data supports do not
208: // accept to create data strucutre into a XA transaction.
209: String str = p
210: .getProperty(SpeedoProperties.MAPPING_STRUCTURE);
211: if (str == null
212: || !str
213: .equals(SpeedoProperties.MAPPING_STRUCTURE_DN)) {
214: p.put(SpeedoProperties.MAPPING_STRUCTURE,
215: SpeedoProperties.MAPPING_STRUCTURE_DN);
216: logger
217: .log(
218: BasicLevel.WARN,
219: "The mapping structure cannot be"
220: + " managed by Speedo into a managed environnement "
221: + "(XA transaction): "
222: + SpeedoProperties.MAPPING_STRUCTURE
223: + " is forced to "
224: + SpeedoProperties.MAPPING_STRUCTURE_DN);
225: }
226: //the speedo property to define the trasaction mode within a j2ee context
227: byte txMode = getByteTxMode(p
228: .getProperty(SpeedoProperties.TRANSACTION_MODE));
229: connectionFactory = createConnectionFactory(logger, this ,
230: connectionManager, txMode);
231: logger.log(BasicLevel.INFO, "ConnectionManager allocated");
232: //fetch the real PersistenceManagerFactory
233: try {
234: pmf = (POManagerFactoryItf) JDOHelper
235: .getPersistenceManagerFactory(p);
236: } catch (Exception e) {
237: Exception ie = ExceptionHelper.getNested(e);
238: ResourceException re = new ResourceException(
239: "Impossible to instanciate Speedo: ");
240: logger.log(BasicLevel.ERROR, re.getMessage(), ie);
241: re.setLinkedException(ie);
242: throw re;
243: }
244: logger.log(BasicLevel.INFO,
245: "SpeedoManagedConnectionFactory started");
246: }
247: started = true;
248: }
249:
250: /**
251: * Loads the properties file from the classloader or from the file system.
252: *
253: * @param p is the Properties to fill.
254: * @throws ResourceException
255: */
256: private Properties loadProperties() throws ResourceException {
257: if (propertiesFileName == null) {
258: throw new ResourceException(
259: "No name provided for the properties file of the associated PersistenceManagerFactory");
260: }
261: InputStream is = getClass().getClassLoader()
262: .getResourceAsStream(propertiesFileName);
263: if (is == null) {
264: File f = new File(propertiesFileName);
265: try {
266: if (f.exists()) {
267: is = new FileInputStream(propertiesFileName);
268: logger.log(BasicLevel.DEBUG, "Properties file '"
269: + propertiesFileName
270: + "' found in the file system");
271: }
272: } catch (FileNotFoundException e) {
273: } finally {
274: if (is == null) {
275: throw new ResourceException(
276: "Unable to load properties file: "
277: + propertiesFileName);
278: }
279: }
280: } else {
281: logger.log(BasicLevel.DEBUG, "Properties file '"
282: + propertiesFileName + "' found in the classpath");
283: }
284: Properties p = new Properties();
285: try {
286: p.load(is);
287: } catch (IOException e) {
288: throw new ResourceException(
289: "Unable to load properties file: "
290: + propertiesFileName);
291: }
292: return p;
293: }
294:
295: /**
296: * Try to find the TransactionManager with:
297: * - the name specified in the RA configuration xml file
298: * - the name specified the properties of the JDO driver
299: * - names used in some application server
300: *
301: * @param p is the Speedo properties
302: * @return the reference to the TransactionManager
303: * @throw a ResourceException is the TransactionManager cannot be found
304: */
305: private TransactionManager findTransactionManager(Properties p)
306: throws ResourceException {
307: if (tm != null) {
308: return tm;
309: }
310: if (tmName != null && lookupTM(tmName)) {
311: p.setProperty(SpeedoProperties.TM_NAME, tmName);
312: } else {
313: //lookup in the properties of JDO driver if the TM name is
314: // specified
315: String tmName2 = p.getProperty(SpeedoProperties.TM_NAME);
316: if (tmName2 == null || !lookupTM(tmName2)) {
317: logger
318: .log(BasicLevel.DEBUG,
319: "Try to find the transaction manager with default JNDI names.");
320: int i = 0;
321: while (i < DEFAULT_JNDI_NAMES.length
322: && !lookupTM(DEFAULT_JNDI_NAMES[i])) {
323: i++;
324: }
325: if (i < DEFAULT_JNDI_NAMES.length) {
326: p.setProperty(SpeedoProperties.TM_NAME,
327: DEFAULT_JNDI_NAMES[i]);
328: }
329: }
330: }
331:
332: if (tm == null) {
333: throw new ResourceException(
334: "A javax.transaction.TransactionManager instance is required,"
335: + " in order to register the JDO driver as a Synchronization on transaction"
336: + (tmName == null ? "(No JNDI name specified)"
337: : "(Bad JNDI Name)"));
338: }
339: logger.log(BasicLevel.DEBUG,
340: "JDOTransactionItf manager found: " + tm);
341: return tm;
342: }
343:
344: protected void finalize() throws Throwable {
345: stop();
346: super .finalize();
347: }
348:
349: /**
350: * Stops this SpeedoManagedConnectionFactory.
351: */
352: public void stop() throws ResourceException {
353: pmf = null;
354: }
355:
356: /**
357: * Delegates the creation of a Connection to the ConnectionFactory.
358: */
359: public Object createConnection() throws ResourceException {
360: return connectionFactory.createConnection();
361: }
362:
363: // --------------------- SpeedoXAContext Management ----------------------- //
364:
365: /**
366: * Looks for a JdoTxContext associated with the particular transaction.
367: * @param xid The DTP transaction identifier.
368: */
369: SpeedoXAContext getXAContext(Xid xid) {
370: SpeedoXAContext xac = (SpeedoXAContext) xid2xac.get(xid);
371: POManagerItf txc = null;
372: if (xac != null) {
373: txc = xac.pm;
374: }
375: if (Debug.ON && logger.isLoggable(BasicLevel.DEBUG))
376: logger.log(BasicLevel.DEBUG,
377: "Looking for the TxContext associated with XID ("
378: + xid + "): " + txc);
379: return xac;
380: }
381:
382: /**
383: * Creates a JdoTxContext and associates it with the given DTP transaction.
384: * @param xid The DTP transaction identifier.
385: */
386: SpeedoXAContext createXAContext(Xid xid) {
387: if (Debug.ON && logger.isLoggable(BasicLevel.DEBUG)) {
388: Object pm = pmf.lookup();
389: if (pm != null) {
390: logger.log(BasicLevel.DEBUG,
391: "Unbind the PM from the current thread: "
392: + "\t-current pm=" + pm + "\t-xid="
393: + xid);
394: } else {
395: logger.log(BasicLevel.DEBUG,
396: "No PM to unbind from the current thread, xid="
397: + xid);
398: }
399: }
400: pmf.unbindPM();
401: SpeedoXAContext xac = new SpeedoXAContext(xid);
402: if (Debug.ON && logger.isLoggable(BasicLevel.DEBUG)) {
403: logger.log(BasicLevel.DEBUG,
404: "Creates an SpeedoXAContext associated with the XID: "
405: + xid);
406: }
407: xid2xac.put(xid, xac);
408: return xac;
409: }
410:
411: /**
412: * Releases a DTP transaction context and its related resources.
413: * @param xid The DTP transaction identifier.
414: */
415: SpeedoXAContext releaseXAContext(Xid xid, boolean mustExist)
416: throws XAException {
417: SpeedoXAContext xac = (SpeedoXAContext) xid2xac.remove(xid);
418: if (xac == null) {
419: if (mustExist) {
420: String msg = "Impossible to release the SpeedoXAContext, xid="
421: + xid;
422: logger.log(BasicLevel.ERROR, msg);
423: throw new XAException(msg);
424: }
425: } else if (!xac.synchroRegistred && xac.pm != null) {
426: logger
427: .log(
428: BasicLevel.WARN,
429: "Closing a persistenceManager because it has not been registered as a Synchronization on the transaction (pm="
430: + xac.pm + "), xid: " + xid);
431: if (xac.pm.getSpeedoTransaction().isActive()) {
432: xac.pm.getSpeedoTransaction().rollback();
433: }
434: xac.pm.closePOManager();
435: } else if (Debug.ON && logger.isLoggable(BasicLevel.DEBUG)) {
436: logger.log(BasicLevel.DEBUG,
437: "Dissociates the JdoTxContext (" + xac.pm
438: + ") associated with XID: " + xid);
439: }
440: return xac;
441: }
442:
443: // IMPLEMENTATION OF METHODS FROM THE SpeedoAttributeController INTERFACE
444:
445: /**
446: * Gives access to the name of the property file for initializing the
447: * underlying JDO implementation.
448: * @return The name of the property file.
449: */
450: public String getPropertyFile() {
451: return propertiesFileName;
452: }
453:
454: /**
455: * Assigns to this JDO connector the name of the property file for
456: * initializing the underlying JDO implementation.
457: * @param pf The name of the property file.
458: */
459: public void setPropertyFile(String pf) {
460: propertiesFileName = pf;
461: }
462:
463: public String getTransactionManagerJNDIName() {
464: return tmName;
465: }
466:
467: public void setTransactionManagerJNDIName(String jndiname)
468: throws ResourceException {
469: tmName = jndiname;
470: }
471:
472: public void setTransactionManager(TransactionManager tm) {
473: this .tm = tm;
474: }
475:
476: // IMPLEMENTATION OF METHODS FROM THE (cci)ManagedConnectionFactory INTERFACE
477:
478: /**
479: * Creates a JDOConnectionFactory; yields the existing one if any.
480: * @param cm The ConnectionManager to be used by the created
481: * ConnectionFactory (may be null).
482: */
483: public Object createConnectionFactory(ConnectionManager cm)
484: throws ResourceException {
485: if (!started) {
486: start();
487: }
488: connectionFactory.setConnectionManager(cm);
489: return connectionFactory;
490: }
491:
492: /**
493: * Creates a JDOConnectionFactory; yields the existing one if any.
494: */
495: public Object createConnectionFactory() throws ResourceException {
496: return createConnectionFactory(null);
497: }
498:
499: /**
500: * Creates a new SpeedoManagedConnection.
501: */
502: public ManagedConnection createManagedConnection(Subject subject,
503: ConnectionRequestInfo info) throws ResourceException {
504: if (logger == null || pmf == null) {
505: start();
506: }
507: SpeedoManagedConnection jmc = new SpeedoManagedConnection(
508: logger, this );
509: if (info != null) {
510: if (info instanceof SpeedoConnectionSpec) {
511: jmc.cri = (SpeedoConnectionSpec) info;
512: } else {
513: throw new ResourceException(
514: "Impossible to create a "
515: + "ManagedConnection with this kind of ConnectionRequestInfo: "
516: + info);
517: }
518: }
519: return jmc;
520: }
521:
522: /**
523: * No matching rules supported. Always yields the first element of the set
524: * if any.
525: */
526: public ManagedConnection matchManagedConnections(Set set,
527: Subject subject, ConnectionRequestInfo info)
528: throws ResourceException {
529: if (set.size() == 0)
530: return null;
531: Iterator it = set.iterator();
532: if (!it.hasNext()) {
533: return null;
534: }
535: SpeedoManagedConnection jmc = (SpeedoManagedConnection) it
536: .next();
537: if (info != null) {
538: if (info instanceof SpeedoConnectionSpec) {
539: jmc.cri = (SpeedoConnectionSpec) info;
540: } else {
541: throw new ResourceException(
542: "Impossible to create a "
543: + "ManagedConnection with this kind of ConnectionRequestInfo: "
544: + info);
545: }
546: }
547: return jmc;
548: }
549:
550: /**
551: * If he given PrintWrtier is a Loggable implementation then the inner
552: * logger and the inner loggerFactory are used. Otherwise the a basic Logger
553: * implementation is used over the specified PrintWriter.
554: */
555: public void setLogWriter(PrintWriter writer)
556: throws ResourceException {
557: if (logger == null) {
558: if (writer instanceof Loggable) {
559: logger = ((Loggable) writer).getLogger();
560: loggerFactory = ((Loggable) writer).getLoggerFactory();
561: } else {
562: LoggerImpl li = new LoggerImpl(writer);
563: logger = li;
564: loggerFactory = li;
565: }
566: }
567: printWriter = writer;
568: }
569:
570: /**
571: * Retrieves the printwriter used for the logging.
572: */
573: public PrintWriter getLogWriter() throws ResourceException {
574: return printWriter;
575: }
576:
577: //PRIVATE METHODS
578: private byte getByteTxMode(String mode) {
579: if (mode == null
580: || mode.length() == 0
581: || mode
582: .equals(SpeedoProperties.TRANSACTION_MODE_NORMAL)) {
583: return SpeedoProperties.TRANSACTION_BMODE_NORMAL;
584: }
585: if (mode.equals(SpeedoProperties.TRANSACTION_MODE_REQUIRED)) {
586: return SpeedoProperties.TRANSACTION_BMODE_REQUIRED;
587: }
588: return SpeedoProperties.TRANSACTION_BMODE_UT;
589: }
590: }
|