Coverage Report - org.mule.transport.jdbc.JdbcConnector
 
Classes in this File Line Coverage Branch Coverage Complexity
JdbcConnector
83%
100/121
70%
49/70
2.44
 
 1  
 /*
 2  
  * $Id: JdbcConnector.java 12181 2008-06-26 20:05:55Z dirk.olmes $
 3  
  * --------------------------------------------------------------------------------------
 4  
  * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.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.MuleException;
 14  
 import org.mule.api.endpoint.ImmutableEndpoint;
 15  
 import org.mule.api.endpoint.InboundEndpoint;
 16  
 import org.mule.api.lifecycle.InitialisationException;
 17  
 import org.mule.api.service.Service;
 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.util.expression.ExpressionEvaluatorManager;
 26  
 
 27  
 import java.sql.Connection;
 28  
 import java.util.List;
 29  
 import java.util.Map;
 30  
 import java.util.regex.Matcher;
 31  
 import java.util.regex.Pattern;
 32  
 
 33  
 import javax.sql.DataSource;
 34  
 
 35  
 import org.apache.commons.dbutils.QueryRunner;
 36  
 import org.apache.commons.dbutils.ResultSetHandler;
 37  
 import org.apache.commons.dbutils.handlers.MapListHandler;
 38  
 
 39  62
 public class JdbcConnector extends AbstractConnector
 40  
 {
 41  
     public static final String JDBC = "jdbc";
 42  
 
 43  
     // These are properties that can be overridden on the Receiver by the endpoint
 44  
     // declaration
 45  
     public static final String PROPERTY_POLLING_FREQUENCY = "pollingFrequency";
 46  
     public static final long DEFAULT_POLLING_FREQUENCY = 1000;
 47  
 
 48  2
     private static final Pattern STATEMENT_ARGS = Pattern.compile("\\$\\{[^\\}]*\\}");
 49  
 
 50  
     /* Register the SQL Exception reader if this class gets loaded */
 51  
     static
 52  
     {
 53  2
         ExceptionHelper.registerExceptionReader(new SQLExceptionReader());
 54  2
     }
 55  
 
 56  62
     protected long pollingFrequency = 0;
 57  
     protected Map queries;
 58  
     
 59  
     private DataSource dataSource;
 60  
     private ResultSetHandler resultSetHandler;
 61  
     private QueryRunner queryRunner;
 62  62
     protected boolean transactionPerMessage = true;
 63  
     
 64  
     protected void doInitialise() throws InitialisationException
 65  
     {
 66  62
         createMultipleTransactedReceivers = false;
 67  
         
 68  62
         if (dataSource == null)
 69  
         {
 70  0
             throw new InitialisationException(MessageFactory.createStaticMessage("Missing data source"), this);
 71  
         }
 72  62
         if (resultSetHandler == null)
 73  
         {
 74  60
             resultSetHandler = new MapListHandler();
 75  
         }
 76  62
         if (queryRunner == null)
 77  
         {
 78  60
             queryRunner = new QueryRunner();
 79  
         }
 80  62
     }
 81  
 
 82  
     public MessageReceiver createReceiver(Service service, InboundEndpoint endpoint) throws Exception
 83  
     {
 84  12
         Map props = endpoint.getProperties();
 85  12
         if (props != null)
 86  
         {
 87  12
             String tempPolling = (String) props.get(PROPERTY_POLLING_FREQUENCY);
 88  12
             if (tempPolling != null)
 89  
             {
 90  0
                 pollingFrequency = Long.parseLong(tempPolling);
 91  
             }
 92  
         }
 93  
 
 94  12
         if (pollingFrequency <= 0)
 95  
         {
 96  0
             pollingFrequency = DEFAULT_POLLING_FREQUENCY;
 97  
         }
 98  
 
 99  12
         String[] params = getReadAndAckStatements(endpoint);
 100  12
         return getServiceDescriptor().createMessageReceiver(this, service, endpoint, params);
 101  
     }
 102  
 
 103  
     public String[] getReadAndAckStatements(ImmutableEndpoint endpoint)
 104  
     {       
 105  
         String str;
 106  
 
 107  
         // Find read statement
 108  
         String readStmt;
 109  36
         if ((str = (String)endpoint.getProperty("sql")) != null)
 110  
         {
 111  6
             readStmt = str;
 112  
         }
 113  
         else
 114  
         {
 115  30
             readStmt = endpoint.getEndpointURI().getAddress();
 116  
         }
 117  
         
 118  
         // Find ack statement
 119  
         String ackStmt;
 120  36
         if ((str = (String)endpoint.getProperty("ack")) != null)
 121  
         {
 122  0
             ackStmt = str;
 123  0
             if ((str = getQuery(endpoint, ackStmt)) != null)
 124  
             {
 125  0
                 ackStmt = str;
 126  
             }
 127  0
             ackStmt = ackStmt.trim();
 128  
         }
 129  
         else
 130  
         {
 131  36
             ackStmt = readStmt + ".ack";
 132  36
             if ((str = getQuery(endpoint, ackStmt)) != null)
 133  
             {
 134  24
                 ackStmt = str.trim();
 135  
             }
 136  
             else
 137  
             {
 138  12
                 ackStmt = null;
 139  
             }
 140  
         }
 141  
         
 142  
         // Translate both using queries map
 143  36
         if ((str = getQuery(endpoint, readStmt)) != null)
 144  
         {
 145  28
             readStmt = str;
 146  
         }
 147  36
         if (readStmt == null)
 148  
         {
 149  0
             throw new IllegalArgumentException("Read statement should not be null");
 150  
         }
 151  
         else
 152  
         {
 153  
             // MULE-3109: trim the readStatement for better user experience
 154  36
             readStmt = readStmt.trim();
 155  
         }
 156  
         
 157  36
         if (!"select".equalsIgnoreCase(readStmt.substring(0, 6)) && !"call".equalsIgnoreCase(readStmt.substring(0, 4)))
 158  
         {
 159  0
             throw new IllegalArgumentException("Read statement should be a select sql statement or a stored procedure");
 160  
         }
 161  36
         if (ackStmt != null)
 162  
         {
 163  24
             if (!"insert".equalsIgnoreCase(ackStmt.substring(0, 6))
 164  
                 && !"update".equalsIgnoreCase(ackStmt.substring(0, 6))
 165  
                 && !"delete".equalsIgnoreCase(ackStmt.substring(0, 6)))
 166  
             {
 167  0
                 throw new IllegalArgumentException(
 168  
                     "Ack statement should be an insert / update / delete sql statement");
 169  
             }
 170  
         }
 171  36
         return new String[]{readStmt, ackStmt};
 172  
     }
 173  
 
 174  
     public String getQuery(ImmutableEndpoint endpoint, String stmt)
 175  
     {
 176  100
         Object query = null;
 177  100
         if (endpoint != null && endpoint.getProperties() != null)
 178  
         {
 179  100
             Object queries = endpoint.getProperties().get("queries");
 180  100
             if (queries instanceof Map)
 181  
             {
 182  10
                 query = ((Map)queries).get(stmt);
 183  
             }
 184  
         }
 185  100
         if (query == null)
 186  
         {
 187  94
             if (this.queries != null)
 188  
             {
 189  86
                 query = this.queries.get(stmt);
 190  
             }
 191  
         }
 192  100
         return query == null ? null : query.toString();
 193  
     }
 194  
 
 195  
     public Connection getConnection() throws Exception
 196  
     {
 197  140
         Transaction tx = TransactionCoordination.getInstance().getTransaction();
 198  140
         if (tx != null)
 199  
         {
 200  8
             if (tx.hasResource(dataSource))
 201  
             {
 202  0
                 logger.debug("Retrieving connection from current transaction");
 203  0
                 return (Connection)tx.getResource(dataSource);
 204  
             }
 205  
         }
 206  140
         logger.debug("Retrieving new connection from data source");
 207  140
         Connection con = dataSource.getConnection();
 208  
 
 209  140
         if (tx != null)
 210  
         {
 211  8
             logger.debug("Binding connection to current transaction");
 212  
             try
 213  
             {
 214  8
                 tx.bindResource(dataSource, con);
 215  
             }
 216  0
             catch (TransactionException e)
 217  
             {
 218  0
                 JdbcUtils.close(con);
 219  0
                 throw new RuntimeException("Could not bind connection to current transaction", e);
 220  8
             }
 221  
         }
 222  140
         return con;
 223  
     }
 224  
 
 225  
     public boolean isTransactionPerMessage() 
 226  
     {
 227  0
         return transactionPerMessage;
 228  
     }
 229  
     
 230  
     public void setTransactionPerMessage(boolean transactionPerMessage) 
 231  
     {
 232  0
         this.transactionPerMessage = transactionPerMessage;
 233  0
         if (!transactionPerMessage)
 234  
         {
 235  0
             logger.warn("transactionPerMessage property is set to false so setting createMultipleTransactedReceivers " +
 236  
                 "to false also to prevent creation of multiple JdbcMessageReceivers");
 237  0
             setCreateMultipleTransactedReceivers(transactionPerMessage);
 238  
         }
 239  0
     }
 240  
     
 241  
     /**
 242  
      * Parse the given statement filling the parameter list and return the ready to
 243  
      * use statement.
 244  
      *
 245  
      * @param stmt
 246  
      * @param params
 247  
      * @return
 248  
      */
 249  
     public String parseStatement(String stmt, List params)
 250  
     {
 251  80
         if (stmt == null)
 252  
         {
 253  12
             return stmt;
 254  
         }
 255  68
         Matcher m = STATEMENT_ARGS.matcher(stmt);
 256  68
         StringBuffer sb = new StringBuffer(200);
 257  188
         while (m.find())
 258  
         {
 259  120
             String key = m.group();
 260  120
             m.appendReplacement(sb, "?");
 261  120
             params.add(key);
 262  120
         }
 263  68
         m.appendTail(sb);
 264  68
         return sb.toString();
 265  
     }
 266  
 
 267  
     public Object[] getParams(ImmutableEndpoint endpoint, List paramNames, Object message, String query)
 268  
         throws Exception
 269  
     {
 270  
 
 271  50
         Object[] params = new Object[paramNames.size()];
 272  130
         for (int i = 0; i < paramNames.size(); i++)
 273  
         {
 274  80
             String param = (String)paramNames.get(i);
 275  80
             String name = param.substring(2, param.length() - 1);
 276  80
             Object value = null;
 277  
             // If we find a value and it happens to be null, thats acceptable
 278  80
             boolean foundValue = false;
 279  
             //There must be an expression namespace to use the ExpresionEvaluator i.e. header:type
 280  80
             if (message != null && ExpressionEvaluatorManager.isValidExpression(name))
 281  
             {
 282  18
                 value = ExpressionEvaluatorManager.evaluate(name, message);
 283  18
                 foundValue = value!=null;
 284  
             }
 285  80
             if (!foundValue)
 286  
             {
 287  62
                 value = endpoint.getProperty(name);
 288  
             }
 289  
 
 290  
             // Allow null values which may be acceptable to the user
 291  
             // Why shouldn't nulls be allowed? Otherwise every null parameter has to
 292  
             // be defined
 293  
             // if (value == null && !foundValue)
 294  
             // {
 295  
             // throw new IllegalArgumentException("Can not retrieve argument " +
 296  
             // name);
 297  
             // }
 298  80
             params[i] = value;
 299  
         }
 300  50
         return params;
 301  
     }
 302  
 
 303  
     protected void doDispose()
 304  
     {
 305  
         // template method
 306  78
     }
 307  
 
 308  
     protected void doConnect() throws Exception
 309  
     {
 310  
         // template method
 311  62
     }
 312  
 
 313  
     protected void doDisconnect() throws Exception
 314  
     {
 315  
         // template method
 316  62
     }
 317  
 
 318  
     protected void doStart() throws MuleException
 319  
     {
 320  
         // template method
 321  60
     }
 322  
 
 323  
     protected void doStop() throws MuleException
 324  
     {
 325  
         // template method
 326  60
     }
 327  
 
 328  
     //////////////////////////////////////////////////////////////////////////////////////
 329  
     // Getters and Setters
 330  
     //////////////////////////////////////////////////////////////////////////////////////
 331  
     
 332  
     public String getProtocol()
 333  
     {
 334  160
         return JDBC;
 335  
     }
 336  
 
 337  
     public DataSource getDataSource()
 338  
     {
 339  6
         return dataSource;
 340  
     }
 341  
 
 342  
     public void setDataSource(DataSource dataSource)
 343  
     {
 344  62
         this.dataSource = dataSource;
 345  62
     }
 346  
 
 347  
     public ResultSetHandler getResultSetHandler()
 348  
     {
 349  26
         return resultSetHandler;
 350  
     }
 351  
 
 352  
     public void setResultSetHandler(ResultSetHandler resultSetHandler)
 353  
     {
 354  2
         this.resultSetHandler = resultSetHandler;
 355  2
     }
 356  
 
 357  
     public QueryRunner getQueryRunner()
 358  
     {
 359  108
         return queryRunner;
 360  
     }
 361  
 
 362  
     public void setQueryRunner(QueryRunner queryRunner)
 363  
     {
 364  2
         this.queryRunner = queryRunner;
 365  2
     }
 366  
 
 367  
     /**
 368  
      * @return Returns the pollingFrequency.
 369  
      */
 370  
     public long getPollingFrequency()
 371  
     {
 372  12
         return pollingFrequency;
 373  
     }
 374  
 
 375  
     /**
 376  
      * @param pollingFrequency The pollingFrequency to set.
 377  
      */
 378  
     public void setPollingFrequency(long pollingFrequency)
 379  
     {
 380  36
         this.pollingFrequency = pollingFrequency;
 381  36
     }
 382  
 
 383  
     /**
 384  
      * @return Returns the queries.
 385  
      */
 386  
     public Map getQueries()
 387  
     {
 388  8
         return queries;
 389  
     }
 390  
 
 391  
     /**
 392  
      * @param queries The queries to set.
 393  
      */
 394  
     public void setQueries(Map queries)
 395  
     {
 396  26
         this.queries = queries;
 397  26
     }
 398  
 }