Coverage Report - org.mule.transport.jdbc.JdbcConnector
 
Classes in this File Line Coverage Branch Coverage Complexity
JdbcConnector
0%
0/168
0%
0/86
2.471
 
 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  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 = null;
 322  
             // If we find a value and it happens to be null, thats acceptable
 323  0
             boolean foundValue = false;
 324  0
             boolean validExpression = muleContext.getExpressionManager().isValidExpression(param);
 325  
             //There must be an expression namespace to use the ExpresionEvaluator i.e. header:type
 326  0
             if (message != null && validExpression)
 327  
             {
 328  0
                 value = muleContext.getExpressionManager().evaluate(param, message);
 329  0
                 foundValue = value != null;
 330  
             }
 331  0
             if (!foundValue)
 332  
             {
 333  0
                 String name = param.substring(2, param.length() - 1);
 334  
                 //MULE-3597
 335  0
                 if (!validExpression)
 336  
                 {
 337  0
                     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  0
                 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  0
             params[i] = value;
 354  
         }
 355  0
         return params;
 356  
     }
 357  
 
 358  
     protected String getNameFromParam(String param)
 359  
     {
 360  0
         return param.substring(2, param.length() - 1);
 361  
     }
 362  
 
 363  
     protected void doDispose()
 364  
     {
 365  
         // template method
 366  0
     }
 367  
 
 368  
     protected void doConnect() throws Exception
 369  
     {
 370  
         // template method
 371  0
     }
 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  0
             con = getConnection();
 383  0
             if (con != null)
 384  
             {
 385  0
                 con.close();
 386  
             }
 387  0
             retryContext.setOk();
 388  
         }
 389  0
         catch (Exception ex)
 390  
         {
 391  0
             retryContext.setFailed(ex);
 392  
         }
 393  
         finally
 394  
         {
 395  0
             con = null;
 396  0
         }
 397  
 
 398  0
         return retryContext;
 399  
     }
 400  
 
 401  
     protected void doDisconnect() throws Exception
 402  
     {
 403  
         // template method
 404  0
     }
 405  
 
 406  
     protected void doStart() throws MuleException
 407  
     {
 408  
         // template method
 409  0
     }
 410  
 
 411  
     protected void doStop() throws MuleException
 412  
     {
 413  
         // template method
 414  0
     }
 415  
 
 416  
     //////////////////////////////////////////////////////////////////////////////////////
 417  
     // Getters and Setters
 418  
     //////////////////////////////////////////////////////////////////////////////////////
 419  
 
 420  
     public String getProtocol()
 421  
     {
 422  0
         return JDBC;
 423  
     }
 424  
 
 425  
     public DataSource getDataSource()
 426  
     {
 427  0
         return dataSource;
 428  
     }
 429  
 
 430  
     public void setDataSource(DataSource dataSource)
 431  
     {
 432  0
         if (dataSource instanceof XADataSource)
 433  
         {
 434  0
             this.dataSource = new DataSourceWrapper((XADataSource) dataSource);
 435  
         }
 436  
         else
 437  
         {
 438  0
             this.dataSource = dataSource;
 439  
         }
 440  0
     }
 441  
 
 442  
     public ResultSetHandler getResultSetHandler()
 443  
     {
 444  0
         return resultSetHandler;
 445  
     }
 446  
 
 447  
     public void setResultSetHandler(ResultSetHandler resultSetHandler)
 448  
     {
 449  0
         this.resultSetHandler = resultSetHandler;
 450  0
     }
 451  
 
 452  
     public QueryRunner getQueryRunnerFor(ImmutableEndpoint endpoint)
 453  
     {
 454  0
         String queryTimeoutAsString = (String) endpoint.getProperty("queryTimeout");
 455  0
         Integer queryTimeout = -1;
 456  
         
 457  
         try
 458  
         {
 459  0
             queryTimeout = Integer.valueOf(queryTimeoutAsString);
 460  
         }
 461  0
         catch (NumberFormatException e)
 462  
         {
 463  
 
 464  0
         }
 465  
         
 466  0
         if (queryTimeout >= 0)
 467  
         {
 468  0
                         ExtendedQueryRunner extendedQueryRunner = new ExtendedQueryRunner(
 469  
                                         this.queryRunner.getDataSource(), queryTimeout);
 470  0
                         return extendedQueryRunner;
 471  
         }
 472  
         else
 473  
         {
 474  0
             return queryRunner;
 475  
         }
 476  
     }
 477  
 
 478  
     public QueryRunner getQueryRunner()
 479  
     {
 480  0
         return queryRunner;
 481  
     }
 482  
 
 483  
     public void setQueryRunner(QueryRunner queryRunner)
 484  
     {
 485  0
         this.queryRunner = queryRunner;
 486  0
     }
 487  
 
 488  
     /**
 489  
      * @return Returns the pollingFrequency.
 490  
      */
 491  
     public long getPollingFrequency()
 492  
     {
 493  0
         return pollingFrequency;
 494  
     }
 495  
 
 496  
     /**
 497  
      * @param pollingFrequency The pollingFrequency to set.
 498  
      */
 499  
     public void setPollingFrequency(long pollingFrequency)
 500  
     {
 501  0
         this.pollingFrequency = pollingFrequency;
 502  0
     }
 503  
 
 504  
     /**
 505  
      * @return Returns the queries.
 506  
      */
 507  
     public Map getQueries()
 508  
     {
 509  0
         return queries;
 510  
     }
 511  
 
 512  
     /**
 513  
      * @param queries The queries to set.
 514  
      */
 515  
     public void setQueries(Map queries)
 516  
     {
 517  0
         this.queries = queries;
 518  0
     }
 519  
 
 520  
     public SqlStatementStrategyFactory getSqlStatementStrategyFactory()
 521  
     {
 522  0
         return sqlStatementStrategyFactory;
 523  
     }
 524  
 
 525  
     public void setSqlStatementStrategyFactory(SqlStatementStrategyFactory sqlStatementStrategyFactory)
 526  
     {
 527  0
         this.sqlStatementStrategyFactory = sqlStatementStrategyFactory;
 528  0
     }
 529  
 
 530  
     public String getStatement(ImmutableEndpoint endpoint)
 531  
     {
 532  0
         String writeStmt = endpoint.getEndpointURI().getAddress();
 533  
         String str;
 534  0
         if ((str = getQuery(endpoint, writeStmt)) != null)
 535  
         {
 536  0
             writeStmt = str;
 537  
         }
 538  0
         writeStmt = StringUtils.trimToEmpty(writeStmt);
 539  0
         if (StringUtils.isBlank(writeStmt))
 540  
         {
 541  0
             throw new IllegalArgumentException("Missing statement");
 542  
         }
 543  
 
 544  0
         return writeStmt;
 545  
     }
 546  
 
 547  
     public int getQueryTimeout()
 548  
     {
 549  0
         return queryTimeout;
 550  
     }
 551  
 
 552  
     public void setQueryTimeout(int queryTimeout)
 553  
     {
 554  0
         this.queryTimeout = queryTimeout;
 555  0
     }
 556  
 }