View Javadoc

1   /*
2    * $Id: JdbcConnector.java 19191 2010-08-25 21:05:23Z tcarlson $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.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.transport.jdbc;
12  
13  import org.mule.api.MuleContext;
14  import org.mule.api.MuleException;
15  import org.mule.api.MuleMessage;
16  import org.mule.api.construct.FlowConstruct;
17  import org.mule.api.endpoint.ImmutableEndpoint;
18  import org.mule.api.endpoint.InboundEndpoint;
19  import org.mule.api.expression.ExpressionManager;
20  import org.mule.api.lifecycle.InitialisationException;
21  import org.mule.api.retry.RetryContext;
22  import org.mule.api.transaction.Transaction;
23  import org.mule.api.transaction.TransactionException;
24  import org.mule.api.transport.MessageReceiver;
25  import org.mule.config.ExceptionHelper;
26  import org.mule.config.i18n.MessageFactory;
27  import org.mule.transaction.TransactionCoordination;
28  import org.mule.transport.AbstractConnector;
29  import org.mule.transport.ConnectException;
30  import org.mule.transport.jdbc.sqlstrategy.DefaultSqlStatementStrategyFactory;
31  import org.mule.transport.jdbc.sqlstrategy.SqlStatementStrategyFactory;
32  import org.mule.transport.jdbc.xa.DataSourceWrapper;
33  import org.mule.util.StringUtils;
34  import org.mule.util.TemplateParser;
35  
36  import java.sql.Connection;
37  import java.text.MessageFormat;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.regex.Matcher;
41  import java.util.regex.Pattern;
42  
43  import javax.sql.DataSource;
44  import javax.sql.XADataSource;
45  
46  import org.apache.commons.dbutils.QueryRunner;
47  import org.apache.commons.dbutils.ResultSetHandler;
48  
49  public class JdbcConnector extends AbstractConnector
50  {
51      public static final String JDBC = "jdbc";
52  
53      // These are properties that can be overridden on the Receiver by the endpoint
54      // declaration
55      public static final String PROPERTY_POLLING_FREQUENCY = "pollingFrequency";
56      public static final long DEFAULT_POLLING_FREQUENCY = 1000;
57  
58      private static final Pattern STATEMENT_ARGS = TemplateParser.WIGGLY_MULE_TEMPLATE_PATTERN;
59  
60      private SqlStatementStrategyFactory sqlStatementStrategyFactory = new DefaultSqlStatementStrategyFactory();
61  
62  
63      /* Register the SQL Exception reader if this class gets loaded */
64      static
65      {
66          ExceptionHelper.registerExceptionReader(new SQLExceptionReader());
67      }
68  
69      protected long pollingFrequency = 0;
70      protected Map queries;
71  
72      protected DataSource dataSource;
73      protected ResultSetHandler resultSetHandler;
74      protected QueryRunner queryRunner;
75      
76      private int queryTimeout;
77      
78      /** 
79       * Should each DB record be received in a separate transaction or should 
80       * there be a single transaction for the entire ResultSet? 
81       */
82      protected boolean transactionPerMessage = true;
83  
84      public JdbcConnector(MuleContext context)
85      {
86          super(context);
87      }
88      
89      protected void doInitialise() throws InitialisationException
90      {
91          createMultipleTransactedReceivers = false;
92  
93          if (dataSource == null)
94          {
95              throw new InitialisationException(MessageFactory.createStaticMessage("Missing data source"), this);
96          }
97          if (resultSetHandler == null)
98          {
99              resultSetHandler = new org.apache.commons.dbutils.handlers.MapListHandler();
100         }
101         if (queryRunner == null)
102         {
103             if (this.queryTimeout >= 0)
104             {
105                 queryRunner = new ExtendedQueryRunner(dataSource, this.queryTimeout);
106             }
107             else
108             {
109                 queryRunner = new QueryRunner();
110             }
111         }
112     }
113 
114     public MessageReceiver createReceiver(FlowConstruct flowConstruct, InboundEndpoint endpoint) throws Exception
115     {
116         Map props = endpoint.getProperties();
117         if (props != null)
118         {
119             String tempPolling = (String) props.get(PROPERTY_POLLING_FREQUENCY);
120             if (tempPolling != null)
121             {
122                 pollingFrequency = Long.parseLong(tempPolling);
123             }
124         }
125 
126         if (pollingFrequency <= 0)
127         {
128             pollingFrequency = DEFAULT_POLLING_FREQUENCY;
129         }
130 
131         String[] params = getReadAndAckStatements(endpoint);
132         return getServiceDescriptor().createMessageReceiver(this, flowConstruct, endpoint, params);
133     }
134 
135     public String[] getReadAndAckStatements(ImmutableEndpoint endpoint)
136     {
137         String str;
138 
139         // Find read statement
140         String readStmt;
141         if ((str = (String) endpoint.getProperty("sql")) != null)
142         {
143             readStmt = str;
144         }
145         else
146         {
147             readStmt = endpoint.getEndpointURI().getAddress();
148         }
149 
150         // Find ack statement
151         String ackStmt;
152         if ((str = (String) endpoint.getProperty("ack")) != null)
153         {
154             ackStmt = str;
155             if ((str = getQuery(endpoint, ackStmt)) != null)
156             {
157                 ackStmt = str;
158             }
159             ackStmt = ackStmt.trim();
160         }
161         else
162         {
163             ackStmt = readStmt + ".ack";
164             if ((str = getQuery(endpoint, ackStmt)) != null)
165             {
166                 ackStmt = str.trim();
167             }
168             else
169             {
170                 ackStmt = null;
171             }
172         }
173 
174         // Translate both using queries map
175         if ((str = getQuery(endpoint, readStmt)) != null)
176         {
177             readStmt = str;
178         }
179         if (readStmt == null)
180         {
181             throw new IllegalArgumentException("Read statement should not be null");
182         }
183         else
184         {
185             // MULE-3109: trim the readStatement for better user experience
186             readStmt = readStmt.trim();
187         }
188 
189         if (!"select".equalsIgnoreCase(readStmt.substring(0, 6)) && !"call".equalsIgnoreCase(readStmt.substring(0, 4)))
190         {
191             throw new IllegalArgumentException("Read statement should be a select sql statement or a stored procedure");
192         }
193         if (ackStmt != null)
194         {
195             if (!"insert".equalsIgnoreCase(ackStmt.substring(0, 6))
196                     && !"update".equalsIgnoreCase(ackStmt.substring(0, 6))
197                     && !"delete".equalsIgnoreCase(ackStmt.substring(0, 6)))
198             {
199                 throw new IllegalArgumentException(
200                         "Ack statement should be an insert / update / delete sql statement");
201             }
202         }
203         return new String[]{readStmt, ackStmt};
204     }
205 
206     public String getQuery(ImmutableEndpoint endpoint, String stmt)
207     {
208         Object query = null;
209         if (endpoint != null && endpoint.getProperties() != null)
210         {
211             Object queries = endpoint.getProperties().get("queries");
212             if (queries instanceof Map)
213             {
214                 query = ((Map) queries).get(stmt);
215             }
216         }
217         if (query == null)
218         {
219             if (this.queries != null)
220             {
221                 query = this.queries.get(stmt);
222             }
223         }
224         return query == null ? null : query.toString();
225     }
226 
227     public Connection getConnection() throws Exception
228     {
229         Transaction tx = TransactionCoordination.getInstance().getTransaction();
230         if (tx != null)
231         {
232             if (tx.hasResource(dataSource))
233             {
234                 logger.debug("Retrieving connection from current transaction: " + tx);
235                 return (Connection) tx.getResource(dataSource);
236             }
237         }
238         logger.debug("Retrieving new connection from data source");
239         
240         Connection con;
241         try
242         {
243             con = dataSource.getConnection();
244         }
245         catch (Exception e)
246         {
247             throw new ConnectException(e, this);
248         }
249 
250         if (tx != null)
251         {
252             logger.debug("Binding connection " + con + " to current transaction: " + tx);
253             try
254             {
255                 tx.bindResource(dataSource, con);
256             }
257             catch (TransactionException e)
258             {
259                 JdbcUtils.close(con);
260                 throw new RuntimeException("Could not bind connection to current transaction: " + tx, e);
261             }
262         }
263         return con;
264     }
265 
266     public boolean isTransactionPerMessage()
267     {
268         return transactionPerMessage;
269     }
270 
271     public void setTransactionPerMessage(boolean transactionPerMessage)
272     {
273         this.transactionPerMessage = transactionPerMessage;
274         if (!transactionPerMessage)
275         {
276             logger.warn("transactionPerMessage property is set to false so setting createMultipleTransactedReceivers " +
277                     "to false also to prevent creation of multiple JdbcMessageReceivers");
278             setCreateMultipleTransactedReceivers(transactionPerMessage);
279         }
280     }
281 
282     /**
283      * Parse the given statement filling the parameter list and return the ready to
284      * use statement.
285      *
286      * @param stmt
287      * @param params
288      */
289     public String parseStatement(String stmt, List params)
290     {
291         if (stmt == null)
292         {
293             return stmt;
294         }
295         Matcher m = STATEMENT_ARGS.matcher(stmt);
296         StringBuffer sb = new StringBuffer(200);
297         while (m.find())
298         {
299             String key = m.group();
300             m.appendReplacement(sb, "?");
301             //Special legacy handling for #[payload]
302             if (key.equals("#[payload]"))
303             {
304                 //MULE-3597
305                 logger.error("invalid expression template #[payload]. It should be replaced with #[payload:] to conform with the correct expression syntax. Mule has replaced this for you, but may not in future versions.");
306                 key = "#[payload:]";
307             }
308             params.add(key);
309         }
310         m.appendTail(sb);
311         return sb.toString();
312     }
313 
314     public Object[] getParams(ImmutableEndpoint endpoint, List paramNames, MuleMessage message, String query)
315             throws Exception
316     {
317         Object[] params = new Object[paramNames.size()];
318         for (int i = 0; i < paramNames.size(); i++)
319         {
320             String param = (String) paramNames.get(i);
321             Object value = null;
322             // If we find a value and it happens to be null, thats acceptable
323             boolean foundValue = false;
324             boolean validExpression = muleContext.getExpressionManager().isValidExpression(param);
325             //There must be an expression namespace to use the ExpresionEvaluator i.e. header:type
326             if (message != null && validExpression)
327             {
328                 value = muleContext.getExpressionManager().evaluate(param, message);
329                 foundValue = value != null;
330             }
331             if (!foundValue)
332             {
333                 String name = param.substring(2, param.length() - 1);
334                 //MULE-3597
335                 if (!validExpression)
336                 {
337                     logger.warn(MessageFormat.format("Config is using the legacy param format {0} (no evaluator defined)." +
338                                                      " This expression can be replaced with {1}header:{2}{3}",
339                                                      param, ExpressionManager.DEFAULT_EXPRESSION_PREFIX,
340                                                      name, ExpressionManager.DEFAULT_EXPRESSION_POSTFIX));
341                 }
342                 value = endpoint.getProperty(name);
343             }
344 
345             // Allow null values which may be acceptable to the user
346             // Why shouldn't nulls be allowed? Otherwise every null parameter has to
347             // be defined
348             // if (value == null && !foundValue)
349             // {
350             // throw new IllegalArgumentException("Can not retrieve argument " +
351             // name);
352             // }
353             params[i] = value;
354         }
355         return params;
356     }
357 
358     protected String getNameFromParam(String param)
359     {
360         return param.substring(2, param.length() - 1);
361     }
362 
363     protected void doDispose()
364     {
365         // template method
366     }
367 
368     protected void doConnect() throws Exception
369     {
370         // template method
371     }
372 
373     /** 
374      * Verify that we are able to connect to the DataSource (needed for retry policies)
375      * @param retryContext
376      */
377     public RetryContext validateConnection(RetryContext retryContext)
378     {
379         Connection con;
380         try
381         {
382             con = getConnection();
383             if (con != null)
384             {
385                 con.close();
386             }
387             retryContext.setOk();
388         }
389         catch (Exception ex)
390         {
391             retryContext.setFailed(ex);
392         }
393         finally
394         {
395             con = null;
396         }
397 
398         return retryContext;
399     }
400 
401     protected void doDisconnect() throws Exception
402     {
403         // template method
404     }
405 
406     protected void doStart() throws MuleException
407     {
408         // template method
409     }
410 
411     protected void doStop() throws MuleException
412     {
413         // template method
414     }
415 
416     //////////////////////////////////////////////////////////////////////////////////////
417     // Getters and Setters
418     //////////////////////////////////////////////////////////////////////////////////////
419 
420     public String getProtocol()
421     {
422         return JDBC;
423     }
424 
425     public DataSource getDataSource()
426     {
427         return dataSource;
428     }
429 
430     public void setDataSource(DataSource dataSource)
431     {
432         if (dataSource instanceof XADataSource)
433         {
434             this.dataSource = new DataSourceWrapper((XADataSource) dataSource);
435         }
436         else
437         {
438             this.dataSource = dataSource;
439         }
440     }
441 
442     public ResultSetHandler getResultSetHandler()
443     {
444         return resultSetHandler;
445     }
446 
447     public void setResultSetHandler(ResultSetHandler resultSetHandler)
448     {
449         this.resultSetHandler = resultSetHandler;
450     }
451 
452     public QueryRunner getQueryRunnerFor(ImmutableEndpoint endpoint)
453     {
454         String queryTimeoutAsString = (String) endpoint.getProperty("queryTimeout");
455         Integer queryTimeout = -1;
456         
457         try
458         {
459             queryTimeout = Integer.valueOf(queryTimeoutAsString);
460         }
461         catch (NumberFormatException e)
462         {
463 
464         }
465         
466         if (queryTimeout >= 0)
467         {
468 			ExtendedQueryRunner extendedQueryRunner = new ExtendedQueryRunner(
469 					this.queryRunner.getDataSource(), queryTimeout);
470 			return extendedQueryRunner;
471         }
472         else
473         {
474             return queryRunner;
475         }
476     }
477 
478     public QueryRunner getQueryRunner()
479     {
480         return queryRunner;
481     }
482 
483     public void setQueryRunner(QueryRunner queryRunner)
484     {
485         this.queryRunner = queryRunner;
486     }
487 
488     /**
489      * @return Returns the pollingFrequency.
490      */
491     public long getPollingFrequency()
492     {
493         return pollingFrequency;
494     }
495 
496     /**
497      * @param pollingFrequency The pollingFrequency to set.
498      */
499     public void setPollingFrequency(long pollingFrequency)
500     {
501         this.pollingFrequency = pollingFrequency;
502     }
503 
504     /**
505      * @return Returns the queries.
506      */
507     public Map getQueries()
508     {
509         return queries;
510     }
511 
512     /**
513      * @param queries The queries to set.
514      */
515     public void setQueries(Map queries)
516     {
517         this.queries = queries;
518     }
519 
520     public SqlStatementStrategyFactory getSqlStatementStrategyFactory()
521     {
522         return sqlStatementStrategyFactory;
523     }
524 
525     public void setSqlStatementStrategyFactory(SqlStatementStrategyFactory sqlStatementStrategyFactory)
526     {
527         this.sqlStatementStrategyFactory = sqlStatementStrategyFactory;
528     }
529 
530     public String getStatement(ImmutableEndpoint endpoint)
531     {
532         String writeStmt = endpoint.getEndpointURI().getAddress();
533         String str;
534         if ((str = getQuery(endpoint, writeStmt)) != null)
535         {
536             writeStmt = str;
537         }
538         writeStmt = StringUtils.trimToEmpty(writeStmt);
539         if (StringUtils.isBlank(writeStmt))
540         {
541             throw new IllegalArgumentException("Missing statement");
542         }
543 
544         return writeStmt;
545     }
546 
547     public int getQueryTimeout()
548     {
549         return queryTimeout;
550     }
551 
552     public void setQueryTimeout(int queryTimeout)
553     {
554         this.queryTimeout = queryTimeout;
555     }
556 }