View Javadoc

1   /*
2    * $Id: JdbcConnector.java 12181 2008-06-26 20:05:55Z 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.transport.jdbc;
12  
13  import org.mule.api.MuleException;
14  import org.mule.api.endpoint.ImmutableEndpoint;
15  import org.mule.api.endpoint.InboundEndpoint;
16  import org.mule.api.lifecycle.InitialisationException;
17  import org.mule.api.service.Service;
18  import org.mule.api.transaction.Transaction;
19  import org.mule.api.transaction.TransactionException;
20  import org.mule.api.transport.MessageReceiver;
21  import org.mule.config.ExceptionHelper;
22  import org.mule.config.i18n.MessageFactory;
23  import org.mule.transaction.TransactionCoordination;
24  import org.mule.transport.AbstractConnector;
25  import org.mule.util.expression.ExpressionEvaluatorManager;
26  
27  import java.sql.Connection;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.regex.Matcher;
31  import java.util.regex.Pattern;
32  
33  import javax.sql.DataSource;
34  
35  import org.apache.commons.dbutils.QueryRunner;
36  import org.apache.commons.dbutils.ResultSetHandler;
37  import org.apache.commons.dbutils.handlers.MapListHandler;
38  
39  public class JdbcConnector extends AbstractConnector
40  {
41      public static final String JDBC = "jdbc";
42  
43      // These are properties that can be overridden on the Receiver by the endpoint
44      // declaration
45      public static final String PROPERTY_POLLING_FREQUENCY = "pollingFrequency";
46      public static final long DEFAULT_POLLING_FREQUENCY = 1000;
47  
48      private static final Pattern STATEMENT_ARGS = Pattern.compile("\\$\\{[^\\}]*\\}");
49  
50      /* Register the SQL Exception reader if this class gets loaded */
51      static
52      {
53          ExceptionHelper.registerExceptionReader(new SQLExceptionReader());
54      }
55  
56      protected long pollingFrequency = 0;
57      protected Map queries;
58      
59      private DataSource dataSource;
60      private ResultSetHandler resultSetHandler;
61      private QueryRunner queryRunner;
62      protected boolean transactionPerMessage = true;
63      
64      protected void doInitialise() throws InitialisationException
65      {
66          createMultipleTransactedReceivers = false;
67          
68          if (dataSource == null)
69          {
70              throw new InitialisationException(MessageFactory.createStaticMessage("Missing data source"), this);
71          }
72          if (resultSetHandler == null)
73          {
74              resultSetHandler = new MapListHandler();
75          }
76          if (queryRunner == null)
77          {
78              queryRunner = new QueryRunner();
79          }
80      }
81  
82      public MessageReceiver createReceiver(Service service, InboundEndpoint endpoint) throws Exception
83      {
84          Map props = endpoint.getProperties();
85          if (props != null)
86          {
87              String tempPolling = (String) props.get(PROPERTY_POLLING_FREQUENCY);
88              if (tempPolling != null)
89              {
90                  pollingFrequency = Long.parseLong(tempPolling);
91              }
92          }
93  
94          if (pollingFrequency <= 0)
95          {
96              pollingFrequency = DEFAULT_POLLING_FREQUENCY;
97          }
98  
99          String[] params = getReadAndAckStatements(endpoint);
100         return getServiceDescriptor().createMessageReceiver(this, service, endpoint, params);
101     }
102 
103     public String[] getReadAndAckStatements(ImmutableEndpoint endpoint)
104     {       
105         String str;
106 
107         // Find read statement
108         String readStmt;
109         if ((str = (String)endpoint.getProperty("sql")) != null)
110         {
111             readStmt = str;
112         }
113         else
114         {
115             readStmt = endpoint.getEndpointURI().getAddress();
116         }
117         
118         // Find ack statement
119         String ackStmt;
120         if ((str = (String)endpoint.getProperty("ack")) != null)
121         {
122             ackStmt = str;
123             if ((str = getQuery(endpoint, ackStmt)) != null)
124             {
125                 ackStmt = str;
126             }
127             ackStmt = ackStmt.trim();
128         }
129         else
130         {
131             ackStmt = readStmt + ".ack";
132             if ((str = getQuery(endpoint, ackStmt)) != null)
133             {
134                 ackStmt = str.trim();
135             }
136             else
137             {
138                 ackStmt = null;
139             }
140         }
141         
142         // Translate both using queries map
143         if ((str = getQuery(endpoint, readStmt)) != null)
144         {
145             readStmt = str;
146         }
147         if (readStmt == null)
148         {
149             throw new IllegalArgumentException("Read statement should not be null");
150         }
151         else
152         {
153             // MULE-3109: trim the readStatement for better user experience
154             readStmt = readStmt.trim();
155         }
156         
157         if (!"select".equalsIgnoreCase(readStmt.substring(0, 6)) && !"call".equalsIgnoreCase(readStmt.substring(0, 4)))
158         {
159             throw new IllegalArgumentException("Read statement should be a select sql statement or a stored procedure");
160         }
161         if (ackStmt != null)
162         {
163             if (!"insert".equalsIgnoreCase(ackStmt.substring(0, 6))
164                 && !"update".equalsIgnoreCase(ackStmt.substring(0, 6))
165                 && !"delete".equalsIgnoreCase(ackStmt.substring(0, 6)))
166             {
167                 throw new IllegalArgumentException(
168                     "Ack statement should be an insert / update / delete sql statement");
169             }
170         }
171         return new String[]{readStmt, ackStmt};
172     }
173 
174     public String getQuery(ImmutableEndpoint endpoint, String stmt)
175     {
176         Object query = null;
177         if (endpoint != null && endpoint.getProperties() != null)
178         {
179             Object queries = endpoint.getProperties().get("queries");
180             if (queries instanceof Map)
181             {
182                 query = ((Map)queries).get(stmt);
183             }
184         }
185         if (query == null)
186         {
187             if (this.queries != null)
188             {
189                 query = this.queries.get(stmt);
190             }
191         }
192         return query == null ? null : query.toString();
193     }
194 
195     public Connection getConnection() throws Exception
196     {
197         Transaction tx = TransactionCoordination.getInstance().getTransaction();
198         if (tx != null)
199         {
200             if (tx.hasResource(dataSource))
201             {
202                 logger.debug("Retrieving connection from current transaction");
203                 return (Connection)tx.getResource(dataSource);
204             }
205         }
206         logger.debug("Retrieving new connection from data source");
207         Connection con = dataSource.getConnection();
208 
209         if (tx != null)
210         {
211             logger.debug("Binding connection to current transaction");
212             try
213             {
214                 tx.bindResource(dataSource, con);
215             }
216             catch (TransactionException e)
217             {
218                 JdbcUtils.close(con);
219                 throw new RuntimeException("Could not bind connection to current transaction", e);
220             }
221         }
222         return con;
223     }
224 
225     public boolean isTransactionPerMessage() 
226     {
227         return transactionPerMessage;
228     }
229     
230     public void setTransactionPerMessage(boolean transactionPerMessage) 
231     {
232         this.transactionPerMessage = transactionPerMessage;
233         if (!transactionPerMessage)
234         {
235             logger.warn("transactionPerMessage property is set to false so setting createMultipleTransactedReceivers " +
236                 "to false also to prevent creation of multiple JdbcMessageReceivers");
237             setCreateMultipleTransactedReceivers(transactionPerMessage);
238         }
239     }
240     
241     /**
242      * Parse the given statement filling the parameter list and return the ready to
243      * use statement.
244      *
245      * @param stmt
246      * @param params
247      * @return
248      */
249     public String parseStatement(String stmt, List params)
250     {
251         if (stmt == null)
252         {
253             return stmt;
254         }
255         Matcher m = STATEMENT_ARGS.matcher(stmt);
256         StringBuffer sb = new StringBuffer(200);
257         while (m.find())
258         {
259             String key = m.group();
260             m.appendReplacement(sb, "?");
261             params.add(key);
262         }
263         m.appendTail(sb);
264         return sb.toString();
265     }
266 
267     public Object[] getParams(ImmutableEndpoint endpoint, List paramNames, Object message, String query)
268         throws Exception
269     {
270 
271         Object[] params = new Object[paramNames.size()];
272         for (int i = 0; i < paramNames.size(); i++)
273         {
274             String param = (String)paramNames.get(i);
275             String name = param.substring(2, param.length() - 1);
276             Object value = null;
277             // If we find a value and it happens to be null, thats acceptable
278             boolean foundValue = false;
279             //There must be an expression namespace to use the ExpresionEvaluator i.e. header:type
280             if (message != null && ExpressionEvaluatorManager.isValidExpression(name))
281             {
282                 value = ExpressionEvaluatorManager.evaluate(name, message);
283                 foundValue = value!=null;
284             }
285             if (!foundValue)
286             {
287                 value = endpoint.getProperty(name);
288             }
289 
290             // Allow null values which may be acceptable to the user
291             // Why shouldn't nulls be allowed? Otherwise every null parameter has to
292             // be defined
293             // if (value == null && !foundValue)
294             // {
295             // throw new IllegalArgumentException("Can not retrieve argument " +
296             // name);
297             // }
298             params[i] = value;
299         }
300         return params;
301     }
302 
303     protected void doDispose()
304     {
305         // template method
306     }
307 
308     protected void doConnect() throws Exception
309     {
310         // template method
311     }
312 
313     protected void doDisconnect() throws Exception
314     {
315         // template method
316     }
317 
318     protected void doStart() throws MuleException
319     {
320         // template method
321     }
322 
323     protected void doStop() throws MuleException
324     {
325         // template method
326     }
327 
328     //////////////////////////////////////////////////////////////////////////////////////
329     // Getters and Setters
330     //////////////////////////////////////////////////////////////////////////////////////
331     
332     public String getProtocol()
333     {
334         return JDBC;
335     }
336 
337     public DataSource getDataSource()
338     {
339         return dataSource;
340     }
341 
342     public void setDataSource(DataSource dataSource)
343     {
344         this.dataSource = dataSource;
345     }
346 
347     public ResultSetHandler getResultSetHandler()
348     {
349         return resultSetHandler;
350     }
351 
352     public void setResultSetHandler(ResultSetHandler resultSetHandler)
353     {
354         this.resultSetHandler = resultSetHandler;
355     }
356 
357     public QueryRunner getQueryRunner()
358     {
359         return queryRunner;
360     }
361 
362     public void setQueryRunner(QueryRunner queryRunner)
363     {
364         this.queryRunner = queryRunner;
365     }
366 
367     /**
368      * @return Returns the pollingFrequency.
369      */
370     public long getPollingFrequency()
371     {
372         return pollingFrequency;
373     }
374 
375     /**
376      * @param pollingFrequency The pollingFrequency to set.
377      */
378     public void setPollingFrequency(long pollingFrequency)
379     {
380         this.pollingFrequency = pollingFrequency;
381     }
382 
383     /**
384      * @return Returns the queries.
385      */
386     public Map getQueries()
387     {
388         return queries;
389     }
390 
391     /**
392      * @param queries The queries to set.
393      */
394     public void setQueries(Map queries)
395     {
396         this.queries = queries;
397     }
398 }