View Javadoc

1   /*
2    * $Id: JdbcConnector.java 11728 2008-05-13 07:31:11Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.providers.jdbc;
12  
13  import org.mule.MuleManager;
14  import org.mule.config.ExceptionHelper;
15  import org.mule.config.i18n.CoreMessages;
16  import org.mule.providers.AbstractConnector;
17  import org.mule.providers.jdbc.i18n.JdbcMessages;
18  import org.mule.providers.jdbc.xa.DataSourceWrapper;
19  import org.mule.transaction.TransactionCoordination;
20  import org.mule.umo.TransactionException;
21  import org.mule.umo.UMOComponent;
22  import org.mule.umo.UMOException;
23  import org.mule.umo.UMOTransaction;
24  import org.mule.umo.endpoint.UMOEndpoint;
25  import org.mule.umo.endpoint.UMOImmutableEndpoint;
26  import org.mule.umo.lifecycle.InitialisationException;
27  import org.mule.umo.provider.UMOMessageReceiver;
28  import org.mule.util.ClassUtils;
29  import org.mule.util.ExceptionUtils;
30  import org.mule.util.StringUtils;
31  import org.mule.util.properties.BeanPropertyExtractor;
32  import org.mule.util.properties.MapPropertyExtractor;
33  import org.mule.util.properties.MessagePropertyExtractor;
34  import org.mule.util.properties.PayloadPropertyExtractor;
35  import org.mule.util.properties.PropertyExtractor;
36  
37  import java.sql.Connection;
38  import java.util.HashSet;
39  import java.util.Hashtable;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Set;
44  import java.util.StringTokenizer;
45  import java.util.regex.Matcher;
46  import java.util.regex.Pattern;
47  
48  import javax.naming.Context;
49  import javax.naming.InitialContext;
50  import javax.naming.NamingException;
51  import javax.sql.DataSource;
52  import javax.sql.XADataSource;
53  
54  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
55  import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentMap;
56  import org.apache.commons.dbutils.QueryRunner;
57  import org.apache.commons.dbutils.ResultSetHandler;
58  
59  public class JdbcConnector extends AbstractConnector
60  {
61      // These are properties that can be overridden on the Receiver by the endpoint
62      // declaration
63      public static final String PROPERTY_POLLING_FREQUENCY = "pollingFrequency";
64      public static final long DEFAULT_POLLING_FREQUENCY = 1000;
65  
66      private static final String DEFAULT_QUERY_RUNNER = "org.apache.commons.dbutils.QueryRunner";
67      private static final String DEFAULT_RESULTSET_HANDLER = "org.apache.commons.dbutils.handlers.MapListHandler";
68  
69      private static final Pattern STATEMENT_ARGS = Pattern.compile("\\$\\{[^\\}]*\\}");
70      
71      /* Register the SQL Exception reader if this class gets loaded */
72      static
73      {
74          ExceptionHelper.registerExceptionReader(new SQLExceptionReader());
75      }
76  
77      protected long pollingFrequency = 0;
78      protected DataSource dataSource;
79      protected String dataSourceJndiName;
80      protected Context jndiContext;
81      protected String jndiInitialFactory;
82      protected String jndiProviderUrl;
83      protected Map providerProperties;
84      protected Map queries;
85      protected String resultSetHandler = DEFAULT_RESULTSET_HANDLER;
86      protected String queryRunner = DEFAULT_QUERY_RUNNER;
87      protected Set queryValueExtractors;
88      protected Set propertyExtractors;
89  
90      protected ConcurrentMap propertyExtractorCache = new ConcurrentHashMap();
91  
92      protected void doInitialise() throws InitialisationException
93      {
94          try
95          {
96              // If we have a dataSource, there is no need to initialise
97              // the JndiContext
98              if (dataSource == null)
99              {
100                 initJndiContext();
101                 createDataSource();
102             }
103 
104             if (dataSource != null && dataSource instanceof XADataSource)
105             {
106                 if (MuleManager.getInstance().getTransactionManager() != null)
107                 {
108                     dataSource = new DataSourceWrapper((XADataSource)dataSource, MuleManager.getInstance()
109                         .getTransactionManager());
110                 }
111             }
112 
113             // setup property Extractors for queries
114             if (queryValueExtractors == null)
115             {
116                 // Add defaults
117                 queryValueExtractors = new HashSet();
118                 queryValueExtractors.add(MessagePropertyExtractor.class.getName());
119                 queryValueExtractors.add(NowPropertyExtractor.class.getName());
120                 queryValueExtractors.add(PayloadPropertyExtractor.class.getName());
121                 queryValueExtractors.add(MapPropertyExtractor.class.getName());
122                 queryValueExtractors.add(BeanPropertyExtractor.class.getName());
123 
124                 if (ClassUtils.isClassOnPath("org.mule.util.properties.Dom4jPropertyExtractor", getClass()))
125                 {
126                     queryValueExtractors.add("org.mule.util.properties.Dom4jPropertyExtractor");
127                 }
128 
129                 if (ClassUtils.isClassOnPath("org.mule.util.properties.JDomPropertyExtractor", getClass()))
130                 {
131                     queryValueExtractors.add("org.mule.util.properties.JDomPropertyExtractor");
132                 }
133             }
134 
135             propertyExtractors = new HashSet();
136             for (Iterator iterator = queryValueExtractors.iterator(); iterator.hasNext();)
137             {
138                 String s = (String)iterator.next();
139                 propertyExtractors.add(ClassUtils.instanciateClass(s, ClassUtils.NO_ARGS));
140             }
141         }
142         catch (Exception e)
143         {
144             throw new InitialisationException(CoreMessages.failedToCreate("Jdbc Connector"), e, this);
145         }
146     }
147 
148     protected void doDispose()
149     {
150         // template method
151     }
152 
153     protected void doConnect() throws Exception
154     {
155         // template method
156     }
157 
158     protected void doDisconnect() throws Exception
159     {
160         // template method
161     }
162 
163     protected void doStart() throws UMOException
164     {
165         // template method
166     }
167 
168     protected void doStop() throws UMOException
169     {
170         // template method
171     }
172 
173     /*
174      * (non-Javadoc)
175      * 
176      * @see org.mule.umo.provider.UMOConnector#getProtocol()
177      */
178     public String getProtocol()
179     {
180         return "jdbc";
181     }
182 
183     public UMOMessageReceiver createReceiver(UMOComponent component, UMOEndpoint endpoint) throws Exception
184     {
185         Map props = endpoint.getProperties();
186         if (props != null)
187         {
188             String tempPolling = (String) props.get(PROPERTY_POLLING_FREQUENCY);
189             if (tempPolling != null)
190             {
191                 pollingFrequency = Long.parseLong(tempPolling);
192             }
193         }
194 
195         if (pollingFrequency <= 0)
196         {
197             pollingFrequency = DEFAULT_POLLING_FREQUENCY;
198         }
199 
200         String[] params = getReadAndAckStatements(endpoint);
201         return getServiceDescriptor().createMessageReceiver(this, component, endpoint, params);
202     }
203 
204     protected void initJndiContext() throws NamingException
205     {
206         if (this.jndiContext == null)
207         {
208             Hashtable props = new Hashtable();
209             if (this.jndiInitialFactory != null)
210             {
211                 props.put(Context.INITIAL_CONTEXT_FACTORY, this.jndiInitialFactory);
212             }
213             if (this.jndiProviderUrl != null)
214             {
215                 props.put(Context.PROVIDER_URL, jndiProviderUrl);
216             }
217             if (this.providerProperties != null)
218             {
219                 props.putAll(this.providerProperties);
220             }
221             this.jndiContext = new InitialContext(props);
222         }
223 
224     }
225 
226     protected void createDataSource() throws InitialisationException, NamingException
227     {
228         Object temp = this.jndiContext.lookup(this.dataSourceJndiName);
229         if (temp instanceof DataSource)
230         {
231             dataSource = (DataSource)temp;
232         }
233         else
234         {
235             throw new InitialisationException(
236                 JdbcMessages.jndiResourceNotFound(this.dataSourceJndiName), this);
237         }
238     }
239 
240     public String[] getReadAndAckStatements(UMOImmutableEndpoint endpoint)
241     {
242         String str;
243         // Find read statement
244         String readStmt;
245         if ((str = (String)endpoint.getProperty("sql")) != null)
246         {
247             readStmt = str;
248         }
249         else
250         {
251             readStmt = endpoint.getEndpointURI().getAddress();
252         }
253         // Find ack statement
254         String ackStmt;
255         if ((str = (String)endpoint.getProperty("ack")) != null)
256         {
257             ackStmt = str;
258             if ((str = getQuery(endpoint, ackStmt)) != null)
259             {
260                 ackStmt = str;
261             }
262         }
263         else
264         {
265             ackStmt = readStmt + ".ack";
266             if ((str = getQuery(endpoint, ackStmt)) != null)
267             {
268                 ackStmt = str;
269             }
270             else
271             {
272                 ackStmt = null;
273             }
274         }
275         // Translate both using queries map
276         if ((str = getQuery(endpoint, readStmt)) != null)
277         {
278             readStmt = str;
279         }
280         if (readStmt == null)
281         {
282             throw new IllegalArgumentException("Read statement should not be null");
283         }
284         if (!"select".equalsIgnoreCase(readStmt.substring(0, 6)) && !"call".equalsIgnoreCase(readStmt.substring(0, 4)))
285         {
286             throw new IllegalArgumentException("Read statement should be a select sql statement or a stored procedure");
287         }
288         if (ackStmt != null)
289         {
290             if (!"insert".equalsIgnoreCase(ackStmt.substring(0, 6))
291                 && !"update".equalsIgnoreCase(ackStmt.substring(0, 6))
292                 && !"delete".equalsIgnoreCase(ackStmt.substring(0, 6)))
293             {
294                 throw new IllegalArgumentException(
295                     "Ack statement should be an insert / update / delete sql statement");
296             }
297         }
298         return new String[]{readStmt, ackStmt};
299     }
300 
301     public String getQuery(UMOImmutableEndpoint endpoint, String stmt)
302     {
303         Object query = null;
304         if (endpoint != null && endpoint.getProperties() != null)
305         {
306             Object queries = endpoint.getProperties().get("queries");
307             if (queries instanceof Map)
308             {
309                 query = ((Map)queries).get(stmt);
310             }
311         }
312         if (query == null)
313         {
314             if (this.queries != null)
315             {
316                 query = this.queries.get(stmt);
317             }
318         }
319         return query == null ? null : query.toString();
320     }
321 
322     /**
323      * @return Returns the dataSource.
324      */
325     public DataSource getDataSource()
326     {
327         return dataSource;
328     }
329 
330     /**
331      * @param dataSource The dataSource to set.
332      */
333     public void setDataSource(DataSource dataSource)
334     {
335         this.dataSource = dataSource;
336     }
337 
338     /**
339      * @return Returns the pollingFrequency.
340      */
341     public long getPollingFrequency()
342     {
343         return pollingFrequency;
344     }
345 
346     /**
347      * @param pollingFrequency The pollingFrequency to set.
348      */
349     public void setPollingFrequency(long pollingFrequency)
350     {
351         this.pollingFrequency = pollingFrequency;
352     }
353 
354     /**
355      * @return Returns the queries.
356      */
357     public Map getQueries()
358     {
359         return queries;
360     }
361 
362     /**
363      * @param queries The queries to set.
364      */
365     public void setQueries(Map queries)
366     {
367         this.queries = queries;
368     }
369 
370     /**
371      * @return Returns the dataSourceJndiName.
372      */
373     public String getDataSourceJndiName()
374     {
375         return dataSourceJndiName;
376     }
377 
378     /**
379      * @param dataSourceJndiName The dataSourceJndiName to set.
380      */
381     public void setDataSourceJndiName(String dataSourceJndiName)
382     {
383         this.dataSourceJndiName = dataSourceJndiName;
384     }
385 
386     /**
387      * @return Returns the jndiContext.
388      */
389     public Context getJndiContext()
390     {
391         return jndiContext;
392     }
393 
394     /**
395      * @param jndiContext The jndiContext to set.
396      */
397     public void setJndiContext(Context jndiContext)
398     {
399         this.jndiContext = jndiContext;
400     }
401 
402     /**
403      * @return Returns the jndiInitialFactory.
404      */
405     public String getJndiInitialFactory()
406     {
407         return jndiInitialFactory;
408     }
409 
410     /**
411      * @param jndiInitialFactory The jndiInitialFactory to set.
412      */
413     public void setJndiInitialFactory(String jndiInitialFactory)
414     {
415         this.jndiInitialFactory = jndiInitialFactory;
416     }
417 
418     /**
419      * @return Returns the jndiProviderUrl.
420      */
421     public String getJndiProviderUrl()
422     {
423         return jndiProviderUrl;
424     }
425 
426     /**
427      * @param jndiProviderUrl The jndiProviderUrl to set.
428      */
429     public void setJndiProviderUrl(String jndiProviderUrl)
430     {
431         this.jndiProviderUrl = jndiProviderUrl;
432     }
433 
434     /**
435      * @return Returns the providerProperties.
436      */
437     public Map getProviderProperties()
438     {
439         return providerProperties;
440     }
441 
442     /**
443      * @param providerProperties The providerProperties to set.
444      */
445     public void setProviderProperties(Map providerProperties)
446     {
447         this.providerProperties = providerProperties;
448     }
449 
450     public Connection getConnection() throws Exception
451     {
452         UMOTransaction tx = TransactionCoordination.getInstance().getTransaction();
453         if (tx != null)
454         {
455             if (tx.hasResource(dataSource))
456             {
457                 logger.debug("Retrieving connection from current transaction");
458                 return (Connection)tx.getResource(dataSource);
459             }
460         }
461         logger.debug("Retrieving new connection from data source");
462         Connection con = dataSource.getConnection();
463 
464         if (tx != null)
465         {
466             logger.debug("Binding connection to current transaction");
467             try
468             {
469                 tx.bindResource(dataSource, con);
470             }
471             catch (TransactionException e)
472             {
473                 JdbcUtils.close(con);
474                 throw new RuntimeException("Could not bind connection to current transaction", e);
475             }
476         }
477         return con;
478     }
479 
480     /**
481      * @return Returns the resultSetHandler.
482      */
483     public String getResultSetHandler()
484     {
485         return this.resultSetHandler;
486     }
487 
488     /**
489      * @param resultSetHandler The resultSetHandler class name to set.
490      */
491     public void setResultSetHandler(String resultSetHandler)
492     {
493         this.resultSetHandler = resultSetHandler;
494     }
495 
496     /**
497      * @return a new instance of the ResultSetHandler class as defined in the
498      *         JdbcConnector
499      */
500     protected ResultSetHandler createResultSetHandler()
501     {
502         try
503         {
504             return (ResultSetHandler) ClassUtils.instanciateClass(getResultSetHandler(),
505                                                                   ClassUtils.NO_ARGS);
506         }
507         catch (Exception e)
508         {
509             throw new IllegalArgumentException("Error creating instance of the resultSetHandler class :"
510                                                + getResultSetHandler() + System.getProperty("line.separator")
511                                                + ExceptionUtils.getFullStackTrace(e));
512         }
513     }
514 
515     public Set getQueryValueExtractors()
516     {
517         return queryValueExtractors;
518     }
519 
520     public void setQueryValueExtractors(Set queryValueExtractors)
521     {
522         this.queryValueExtractors = queryValueExtractors;
523     }
524 
525     /**
526      * @return Returns the queryRunner.
527      */
528     public String getQueryRunner()
529     {
530         return this.queryRunner;
531     }
532 
533     /**
534      * @param queryRunner The QueryRunner class name to set.
535      */
536     public void setQueryRunner(String queryRunner)
537     {
538         this.queryRunner = queryRunner;
539     }
540 
541     /**
542      * @return a new instance of the QueryRunner class as defined in the
543      *         JdbcConnector
544      */
545     protected QueryRunner createQueryRunner()
546     {
547         try
548         {
549             return (QueryRunner) ClassUtils.instanciateClass(getQueryRunner(),
550                                                              ClassUtils.NO_ARGS);
551         }
552         catch (Exception e)
553         {
554             throw new IllegalArgumentException("Error creating instance of the queryRunner class :"
555                                                + getQueryRunner() + System.getProperty("line.separator")
556                                                + ExceptionUtils.getFullStackTrace(e));
557         }
558     }
559 
560     /**
561      * Parse the given statement filling the parameter list and return the ready to
562      * use statement.
563      * 
564      * @param stmt
565      * @param params
566      * @return
567      */
568     public String parseStatement(String stmt, List params)
569     {
570         if (stmt == null)
571         {
572             return stmt;
573         }
574         Matcher m = STATEMENT_ARGS.matcher(stmt);
575         StringBuffer sb = new StringBuffer(200);
576         while (m.find())
577         {
578             String key = m.group();
579             m.appendReplacement(sb, "?");
580             params.add(key);
581         }
582         m.appendTail(sb);
583         return sb.toString();
584     }
585 
586     public Object[][] getParamsTypes(List params)
587     {
588         Object[][] result = new Object[params.size()][3];
589         int k = 0;
590         for (Iterator iter = params.iterator(); iter.hasNext(); k++)
591         {
592             String param = (String) iter.next();
593             param = param.substring(2, param.length() - 1);
594             StringTokenizer tokenizer = new StringTokenizer(param, ";");
595             int i = 0;
596             while (tokenizer.hasMoreTokens())
597             {
598                 result[k][i] = tokenizer.nextToken();
599                 i++;
600                 if (i > 2)
601                 {
602                     break;
603                 }
604             }
605         }
606         return result;
607     }
608 
609     public Object[] getParams(UMOImmutableEndpoint endpoint, List paramNames, Object message, String query)
610         throws Exception
611     {
612         
613         Object[] params = new Object[paramNames.size()];
614         for (int i = 0; i < paramNames.size(); i++)
615         {
616             String param = (String)paramNames.get(i);
617             String name;
618             int idx;
619             if ((idx = param.indexOf(";")) != -1)
620             {
621                 name = param.substring(2, idx);
622             }
623             else
624             {
625                 name = param.substring(2, param.length() - 1);
626             }
627             Object value = null;
628             // If we find a value and it happens to be null, thats acceptable
629             boolean foundValue = false;
630             if (message != null)
631             {
632                 PropertyExtractor pe = (PropertyExtractor) propertyExtractorCache.get(query + param);
633                 if (pe == null) 
634                 {
635                     for (Iterator iterator = propertyExtractors.iterator(); iterator.hasNext();)
636                     {
637                         pe = (PropertyExtractor) iterator.next();
638                         value = pe.getProperty(name, message);
639                         if (value != null)
640                         {                       
641                             foundValue = true;
642                             propertyExtractorCache.putIfAbsent(query + param, pe);
643                             break;
644                         }    
645                     }
646                 }
647                 else 
648                 {
649                     value = pe.getProperty(name, message);
650                     foundValue = true;
651                 }
652                 if (StringUtils.EMPTY.equals(value) && pe instanceof BeanPropertyExtractor)
653                 {
654                     value = null;
655                 }    
656             }                
657             if (!foundValue)
658             {
659                 value = endpoint.getProperty(name);
660             }
661 
662             // Allow null values which may be acceptable to the user
663             // Why shouldn't nulls be allowed? Otherwise every null parameter has to
664             // be defined
665             // if (value == null && !foundValue)
666             // {
667             // throw new IllegalArgumentException("Can not retrieve argument " +
668             // name);
669             // }
670             params[i] = value;
671         }
672         return params;
673     }
674 }