1
2
3
4
5
6
7
8
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
54
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
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
80
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
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
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
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
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
284
285
286
287
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
302 if (key.equals("#[payload]"))
303 {
304
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
323 boolean foundValue = false;
324 boolean validExpression = muleContext.getExpressionManager().isValidExpression(param);
325
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
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
346
347
348
349
350
351
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
366 }
367
368 protected void doConnect() throws Exception
369 {
370
371 }
372
373
374
375
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
404 }
405
406 protected void doStart() throws MuleException
407 {
408
409 }
410
411 protected void doStop() throws MuleException
412 {
413
414 }
415
416
417
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
490
491 public long getPollingFrequency()
492 {
493 return pollingFrequency;
494 }
495
496
497
498
499 public void setPollingFrequency(long pollingFrequency)
500 {
501 this.pollingFrequency = pollingFrequency;
502 }
503
504
505
506
507 public Map getQueries()
508 {
509 return queries;
510 }
511
512
513
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 }