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