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