View Javadoc

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