View Javadoc

1   /*
2    * $Id: JdbcMessageReceiver.java 23234 2011-10-21 15:16:17Z 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<Object>();
98          this.readStmt = this.connector.parseStatement(readStmt, this.readParams);
99          this.ackParams = new ArrayList<Object>();
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             MuleMessage muleMessage = createMuleMessage(message, endpoint.getEncoding());
129             routeMessage(muleMessage);
130             if (hasAckStatement())
131             {
132                 con = this.connector.getConnection();
133 
134                 if (aggregateResult)
135                 {
136                     List<MuleMessage> messages = createMuleMessages((List) message);
137                     int[] nbRows = executeBatchAckStatement(con, messages);
138 
139                     if (nbRows[0] == 0)
140                     {
141                         logger.warn(".ack statement did not update any rows");
142                     }
143                     // Reset this flag
144                     aggregateResult = false;
145                 }
146                 else
147                 {
148                     int nbRows = executeAckStatement(con, muleMessage);
149                     if (nbRows == 0)
150                     {
151                         logger.warn(".ack statement did not update any rows");
152                     }
153                 }
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)
169             {
170                 //Only close connection when there's no transaction.
171                 //If there's a transaction available, then the transaction
172                 //will be the one doing close after commit or rollback
173                 JdbcUtils.close(con);
174             }
175         }
176     }
177 
178     protected boolean hasAckStatement()
179     {
180         return this.ackStmt != null;
181     }
182 
183     /**
184      * Creates a mule message per each data record.
185      *
186      * @param records data records used to created the payload of the new messages.
187      * @return the created messages
188      */
189     protected List<MuleMessage> createMuleMessages(List<Object> records)
190     {
191         List<MuleMessage> messages = new LinkedList<MuleMessage>();
192         for (Object record : records)
193         {
194             messages.add(new DefaultMuleMessage(record, connector.getMuleContext()));
195         }
196 
197         return messages;
198     }
199 
200     /**
201      * Executes the acknowledge SQL statement for a given message.
202      *
203      * @param con         database connection to execute the statement
204      * @param muleMessage message to been acknowledge
205      * @return the number of updated rows by the SQL statement
206      * @throws Exception
207      */
208     protected int executeAckStatement(Connection con, MuleMessage muleMessage)
209             throws Exception
210     {
211         Object[] paramValues = connector.getParams(endpoint, this.ackParams, muleMessage, this.endpoint.getEndpointURI().getAddress());
212         if (logger.isDebugEnabled())
213         {
214             logger.debug("SQL UPDATE: " + ackStmt + ", params = " + ArrayUtils.toString(paramValues));
215         }
216         int nbRows = connector.getQueryRunnerFor(endpoint).update(con, this.ackStmt, paramValues);
217         return nbRows;
218     }
219 
220     /**
221      * Executes the acknowledge SQL statement for a list of messages.
222      *
223      * @param con      database connection to execute the statement
224      * @param messages messages to be acknowledge
225      * @return the number of updated rows by each batched execution
226      * @throws Exception
227      */
228     protected int[] executeBatchAckStatement(Connection con, List<MuleMessage> messages)
229             throws Exception
230     {
231         Object[][] paramValuesArray = new Object[messages.size()][];
232 
233         for (int i = 0; i < messages.size(); i++)
234         {
235             MuleMessage message = messages.get(i);
236             paramValuesArray[i] = connector.getParams(endpoint, this.ackParams, message, this.endpoint.getEndpointURI().getAddress());
237         }
238 
239         if (logger.isDebugEnabled())
240         {
241             logger.debug("SQL UPDATE: " + ackStmt + ", params = " + ArrayUtils.toString(ackParams));
242         }
243 
244         int[] nbRows = connector.getQueryRunnerFor(endpoint).batch(con, this.ackStmt, paramValuesArray);
245 
246         return nbRows;
247     }
248 
249     @Override
250     public List getMessages() throws Exception
251     {
252         if (!flowConstruct.getMuleContext().isPrimaryPollingInstance())
253         {
254             return null;
255         }
256         Connection con = null;
257         try
258         {
259             con = this.connector.getConnection();
260 
261             List resultList = executeReadStatement(con);
262             if (resultList != null && resultList.size() > 1 && isReceiveMessagesInTransaction() && !receiveMessagesInXaTransaction)
263             {
264                 aggregateResult = true;
265                 logger.warn(JdbcMessages.moreThanOneMessageInTransaction(RECEIVE_MESSAGE_IN_TRANSCTION, RECEIVE_MESSAGES_IN_XA_TRANSCTION));
266                 List singleResultList = new ArrayList(1);
267                 singleResultList.add(resultList);
268                 return singleResultList;
269             }
270 
271             return resultList;
272         }
273         finally
274         {
275             if (TransactionCoordination.getInstance().getTransaction() == null)
276             {
277                 JdbcUtils.close(con);
278             }
279         }
280     }
281 
282     /**
283      * Executes the read SQL statement to get data from the database.
284      *
285      * @param con database connection to execute the statement
286      * @return the list of read records
287      * @throws Exception
288      */
289     protected List executeReadStatement(Connection con) throws Exception
290     {
291         Object[] readParams = connector.getParams(endpoint, this.readParams, null, this.endpoint.getEndpointURI().getAddress());
292         if (logger.isDebugEnabled())
293         {
294             logger.debug("SQL QUERY: " + readStmt + ", params = " + ArrayUtils.toString(readParams));
295         }
296         Object results = connector.getQueryRunnerFor(endpoint).query(con, this.readStmt, readParams,
297                 connector.getResultSetHandler());
298 
299         return (List) results;
300     }
301 }