View Javadoc

1   /*
2    * $Id: JdbcMessageReceiver.java 20346 2010-11-25 10:14:22Z 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.DefaultMuleMessage;
14  import org.mule.api.MuleMessage;
15  import org.mule.api.construct.FlowConstruct;
16  import org.mule.api.endpoint.InboundEndpoint;
17  import org.mule.api.lifecycle.CreateException;
18  import org.mule.api.transaction.Transaction;
19  import org.mule.api.transport.Connector;
20  import org.mule.transaction.TransactionCoordination;
21  import org.mule.transaction.XaTransactionFactory;
22  import org.mule.transport.TransactedPollingMessageReceiver;
23  import org.mule.transport.jdbc.i18n.JdbcMessages;
24  import org.mule.util.ArrayUtils;
25  import org.mule.util.MapUtils;
26  
27  import java.sql.Connection;
28  import java.util.ArrayList;
29  import java.util.LinkedList;
30  import java.util.List;
31  
32  /**
33   * Implements {@link TransactedPollingMessageReceiver} reading data from a database.
34   * Provides a way to acknowledge each read data using a SQL statement.
35   */
36  public class JdbcMessageReceiver extends TransactedPollingMessageReceiver
37  {
38  
39      public static final String RECEIVE_MESSAGE_IN_TRANSCTION = "receiveMessageInTransaction";
40      public static final String RECEIVE_MESSAGES_IN_XA_TRANSCTION = "receiveMessagesInXaTransaction";
41      
42      protected JdbcConnector connector;
43      protected String readStmt;
44      protected String ackStmt;
45      protected List readParams;
46      protected List ackParams;
47      public boolean receiveMessagesInXaTransaction = false;
48      private volatile boolean aggregateResult;
49      
50      public JdbcMessageReceiver(Connector connector,
51                                 FlowConstruct flowConstruct,
52                                 InboundEndpoint endpoint,
53                                 String readStmt,
54                                 String ackStmt) throws CreateException
55      {
56          super(connector, flowConstruct, endpoint);
57          this.setFrequency(((JdbcConnector) connector).getPollingFrequency());
58  
59          boolean transactedEndpoint = endpoint.getTransactionConfig().isTransacted();
60          boolean xaTransactedEndpoint = (transactedEndpoint &&
61              endpoint.getTransactionConfig().getFactory() instanceof XaTransactionFactory);
62          
63          boolean receiveMessageInTransaction = MapUtils.getBooleanValue(endpoint.getProperties(),
64              RECEIVE_MESSAGE_IN_TRANSCTION, false);
65          this.setReceiveMessagesInTransaction(receiveMessageInTransaction && transactedEndpoint);
66          if (receiveMessageInTransaction && !transactedEndpoint)
67          {
68              logger.warn(JdbcMessages.forcePropertyNoTransaction(RECEIVE_MESSAGE_IN_TRANSCTION, "transaction"));
69              receiveMessageInTransaction = false;
70          }
71          
72          receiveMessagesInXaTransaction = MapUtils.getBooleanValue(endpoint.getProperties(),
73              RECEIVE_MESSAGES_IN_XA_TRANSCTION, false);
74          if (receiveMessagesInXaTransaction && !receiveMessageInTransaction)
75          {
76              logger.warn(JdbcMessages.forceProperty(RECEIVE_MESSAGES_IN_XA_TRANSCTION, RECEIVE_MESSAGE_IN_TRANSCTION));
77              receiveMessagesInXaTransaction = false;
78          }
79          else if (receiveMessagesInXaTransaction && isReceiveMessagesInTransaction() && !xaTransactedEndpoint)
80          {
81              logger.warn(JdbcMessages.forcePropertyNoTransaction(RECEIVE_MESSAGES_IN_XA_TRANSCTION, "XA transaction"));
82              receiveMessagesInXaTransaction = false;
83          }
84          
85          this.connector = (JdbcConnector) connector;
86          this.setReceiveMessagesInTransaction(endpoint.getTransactionConfig().isTransacted()
87              && !this.connector.isTransactionPerMessage());
88  
89          parseStatements(readStmt, ackStmt);
90      }
91  
92      /**
93       * Parses the read and acknowledge SQL statements
94       */
95      protected void parseStatements(String readStmt, String ackStmt)
96      {
97          this.readParams = new ArrayList();
98          this.readStmt = this.connector.parseStatement(readStmt, this.readParams);
99          this.ackParams = new ArrayList();
100         this.ackStmt = this.connector.parseStatement(ackStmt, this.ackParams);
101     }
102 
103     @Override
104     protected void doDispose()
105     {
106         // template method
107     }
108 
109     @Override
110     protected void doConnect() throws Exception
111     {
112         // template method
113     }
114 
115     @Override
116     protected void doDisconnect() throws Exception
117     {
118         // noop
119     }
120 
121     @Override
122     public void processMessage(Object message) throws Exception
123     {
124         Connection con = null;
125         Transaction tx = TransactionCoordination.getInstance().getTransaction();
126         try
127         {
128             con = this.connector.getConnection();
129             MuleMessage muleMessage = createMuleMessage(message, endpoint.getEncoding());
130             if (hasAckStatement())
131             {
132                 if (aggregateResult)
133                 {
134                     List<MuleMessage> messages = createMuleMessages((List) message);
135                     int[] nbRows = executeBatchAckStatement(con, messages);
136 
137                     if (nbRows[0] == 0)
138                     {
139                         logger.warn(".ack statement did not update any rows");
140                     }
141                     // Reset this flag
142                     aggregateResult = false;
143                 }
144                 else
145                 {
146                     int nbRows = executeAckStatement(con, muleMessage);
147                     if (nbRows == 0)
148                     {
149                         logger.warn(".ack statement did not update any rows");
150                     }
151                 }
152             }
153             routeMessage(muleMessage);
154 
155         }
156         catch (Exception ex)
157         {
158             if (tx != null)
159             {
160                 tx.setRollbackOnly();
161             }
162 
163             // rethrow
164             throw ex;
165         }
166         finally
167         {
168             if (tx == null || tx.isXA())
169             {
170                 // We are running in an XA transaction.
171                 // This call is required here for compatibility with strict XA
172                 // DataSources
173                 // implementations, as is the case for WebSphere AS and Weblogic.
174                 // Failure to do it here may result in a connection leak.
175                 // The close() call will NOT close the connection, neither will it
176                 // return it to the pool.
177                 // It will notify the XA driver's ConnectionEventListener that the XA
178                 // connection
179                 // is no longer used by the application and is ready for the 2PC
180                 // commit.
181                 JdbcUtils.close(con);
182             }
183         }
184     }
185 
186     protected boolean hasAckStatement()
187     {
188         return this.ackStmt != null;
189     }
190 
191     /**
192      * Creates a mule message per each data record.
193      *
194      * @param records data records used to created the payload of the new messages.
195      * @return the created messages
196      */
197     protected List<MuleMessage> createMuleMessages(List<Object> records)
198     {
199         List<MuleMessage> messages = new LinkedList<MuleMessage>();
200         for (Object record : records)
201         {
202             messages.add(new DefaultMuleMessage(record, connector.getMuleContext()));
203         }
204 
205         return messages;
206     }
207 
208     /**
209      * Executes the acknowledge SQL statement for a given message.
210      *
211      * @param con         database connection to execute the statement
212      * @param muleMessage message to been acknowledge
213      * @return the number of updated rows by the SQL statement
214      * @throws Exception
215      */
216     protected int executeAckStatement(Connection con, MuleMessage muleMessage)
217             throws Exception
218     {
219         Object[] paramValues = connector.getParams(endpoint, this.ackParams, muleMessage, this.endpoint.getEndpointURI().getAddress());
220         if (logger.isDebugEnabled())
221         {
222             logger.debug("SQL UPDATE: " + ackStmt + ", params = " + ArrayUtils.toString(paramValues));
223         }
224         int nbRows = connector.getQueryRunnerFor(endpoint).update(con, this.ackStmt, paramValues);
225         return nbRows;
226     }
227 
228     /**
229      * Executes the acknowledge SQL statement for a list of messages.
230      *
231      * @param con      database connection to execute the statement
232      * @param messages messages to be acknowledge
233      * @return the number of updated rows by each batched execution
234      * @throws Exception
235      */
236     protected int[] executeBatchAckStatement(Connection con, List<MuleMessage> messages)
237             throws Exception
238     {
239         Object[][] paramValuesArray = new Object[messages.size()][];
240 
241         for (int i = 0; i < messages.size(); i++)
242         {
243             MuleMessage message = messages.get(i);
244             paramValuesArray[i] = connector.getParams(endpoint, this.ackParams, message, this.endpoint.getEndpointURI().getAddress());
245         }
246 
247         if (logger.isDebugEnabled())
248         {
249             logger.debug("SQL UPDATE: " + ackStmt + ", params = " + ArrayUtils.toString(ackParams));
250         }
251 
252         int[] nbRows = connector.getQueryRunnerFor(endpoint).batch(con, this.ackStmt, paramValuesArray);
253 
254         return nbRows;
255     }
256 
257     public List getMessages() throws Exception
258     {
259         Connection con = null;
260         try
261         {
262             con = this.connector.getConnection();
263 
264             List resultList = executeReadStatement(con);
265             if (resultList != null && resultList.size() > 1 && isReceiveMessagesInTransaction() && !receiveMessagesInXaTransaction)
266             {
267                 aggregateResult = true;
268                 logger.warn(JdbcMessages.moreThanOneMessageInTransaction(RECEIVE_MESSAGE_IN_TRANSCTION, RECEIVE_MESSAGES_IN_XA_TRANSCTION));
269                 List singleResultList = new ArrayList(1);
270                 singleResultList.add(resultList);
271                 return singleResultList;
272             }
273             
274             return resultList;
275         }
276         finally
277         {
278             if (TransactionCoordination.getInstance().getTransaction() == null)
279             {
280                 JdbcUtils.close(con);
281             }
282         }
283     }
284 
285     /**
286      * Executes the read SQL statement to get data from the database.
287      *
288      * @param con database connection to execute the statement
289      * @return the list of read records
290      * @throws Exception
291      */
292     protected List executeReadStatement(Connection con) throws Exception
293     {
294         Object[] readParams = connector.getParams(endpoint, this.readParams, null, this.endpoint.getEndpointURI().getAddress());
295         if (logger.isDebugEnabled())
296         {
297             logger.debug("SQL QUERY: " + readStmt + ", params = " + ArrayUtils.toString(readParams));
298         }
299         Object results = connector.getQueryRunnerFor(endpoint).query(con, this.readStmt, readParams,
300                 connector.getResultSetHandler());
301 
302         return (List) results;
303     }
304 }