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