001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.jbi.audit.jdbc;
018    
019    import java.io.IOException;
020    import java.net.URI;
021    import java.sql.Connection;
022    import java.sql.SQLException;
023    
024    import javax.jbi.messaging.MessageExchange;
025    import javax.sql.DataSource;
026    
027    import org.apache.servicemix.jbi.audit.AbstractAuditor;
028    import org.apache.servicemix.jbi.audit.AuditorException;
029    import org.apache.servicemix.jbi.event.ExchangeEvent;
030    import org.apache.servicemix.jbi.messaging.ExchangePacket;
031    import org.apache.servicemix.jbi.messaging.InOnlyImpl;
032    import org.apache.servicemix.jbi.messaging.InOptionalOutImpl;
033    import org.apache.servicemix.jbi.messaging.InOutImpl;
034    import org.apache.servicemix.jbi.messaging.MessageExchangeImpl;
035    import org.apache.servicemix.jbi.messaging.MessageExchangeSupport;
036    import org.apache.servicemix.jbi.messaging.RobustInOnlyImpl;
037    import org.apache.servicemix.jdbc.JDBCAdapter;
038    import org.apache.servicemix.jdbc.JDBCAdapterFactory;
039    import org.apache.servicemix.jdbc.Statements;
040    import org.springframework.beans.factory.InitializingBean;
041    
042    /**
043     * Basic implementation of ServiceMix auditor on a jdbc store.
044     * This implementation, for performance purposes, only relies
045     * on one table SM_AUDIT with two columns:
046     * <ul>
047     *   <li><b>ID</b> the exchange id (varchar)</li>
048     *   <li><b>EXCHANGE</b> the serialized exchange (blob)</li>
049     * </ul>
050     * To minimize overhead, the exchange serialized is the undelying
051     * {@link org.apache.servicemix.jbi.messaging.ExchangePacket}.
052     * 
053     * @org.apache.xbean.XBean element="jdbcAuditor" description="The Auditor of message exchanges to a JDBC database"
054     * 
055     * @author Guillaume Nodet (gnt)
056     * @version $Revision: 11366 $
057     * @since 2.1
058     */
059    public class JdbcAuditor extends AbstractAuditor implements InitializingBean {
060    
061        private DataSource dataSource;
062        private boolean autoStart = true;
063        private Statements statements;
064        private String tableName = "SM_AUDIT";
065        private JDBCAdapter adapter;
066        private boolean createDataBase = true;
067        
068        public String getDescription() {
069            return "JDBC Auditing Service";
070        }
071        
072        public void afterPropertiesSet() throws Exception {
073            if (this.container == null) {
074                throw new IllegalArgumentException("container should not be null");
075            }
076            if (this.dataSource == null) {
077                throw new IllegalArgumentException("dataSource should not be null");
078            }
079            if (statements == null) {
080                statements = new Statements();
081                statements.setStoreTableName(tableName);
082            }
083            Connection connection = null;
084            boolean restoreAutoCommit = false;
085            try {
086                connection = getDataSource().getConnection();
087                if (connection.getAutoCommit()) {
088                    connection.setAutoCommit(false);
089                    restoreAutoCommit = true;
090                }
091                adapter = JDBCAdapterFactory.getAdapter(connection);
092                if (statements == null) {
093                    statements = new Statements();
094                    statements.setStoreTableName(tableName);
095                }
096                adapter.setStatements(statements);
097                if (createDataBase) {
098                    adapter.doCreateTables(connection);
099                }
100                connection.commit();
101            } catch (SQLException e) {
102                throw (IOException) new IOException("Exception while creating database").initCause(e); 
103            } finally {
104                close(connection, restoreAutoCommit);
105            }
106            init(getContainer());
107            if (autoStart) {
108                start();
109            } else {
110                stop();
111            }
112        }
113        
114        public void exchangeSent(ExchangeEvent event) {
115            MessageExchange exchange = event.getExchange();
116            if (!(exchange instanceof MessageExchangeImpl)) {
117                throw new IllegalArgumentException("exchange should be a MessageExchangeImpl");
118            }
119            try {
120                ExchangePacket packet = ((MessageExchangeImpl) exchange).getPacket();
121                String id = packet.getExchangeId();
122                byte[] data = packet.getData();
123                Connection connection = null;
124                boolean restoreAutoCommit = false;
125                try {
126                    connection = dataSource.getConnection();
127                    if (connection.getAutoCommit()) {
128                        connection.setAutoCommit(false);
129                        restoreAutoCommit = true;
130                    }
131                    store(connection, id, data);
132                    connection.commit();
133                } finally {
134                    close(connection, restoreAutoCommit);
135                }
136            } catch (Exception e) {
137                log.error("Could not persist exchange", e);
138            }
139        }
140        
141        protected void store(Connection connection, String id, byte[] data) throws Exception {
142            if (adapter.doLoadData(connection, id) != null) {
143                adapter.doUpdateData(connection, id, data);
144            } else {
145                adapter.doStoreData(connection, id, data);
146            }
147        }
148        
149        public DataSource getDataSource() {
150            return dataSource;
151        }
152    
153        public void setDataSource(DataSource dataSource) {
154            this.dataSource = dataSource;
155        }
156    
157        /* (non-Javadoc)
158         * @see org.apache.servicemix.jbi.audit.AuditorMBean#getExchangeCount()
159         */
160        public int getExchangeCount() throws AuditorException {
161            Connection connection = null;
162            try {
163                connection = dataSource.getConnection();
164                return adapter.doGetCount(connection);
165            } catch (Exception e) {
166                throw new AuditorException("Could not retrieve exchange count", e);
167            } finally {
168                close(connection, false);
169            }
170        }
171    
172        /* (non-Javadoc)
173         * @see org.apache.servicemix.jbi.audit.AuditorMBean#getExchangeIds(int, int)
174         */
175        public String[] getExchangeIdsByRange(int fromIndex, int toIndex) throws AuditorException {
176            if (fromIndex < 0) {
177                throw new IllegalArgumentException("fromIndex should be greater or equal to zero");
178            }
179            if (toIndex < fromIndex) {
180                throw new IllegalArgumentException("toIndex should be greater or equal to fromIndex");
181            }
182            // Do not hit the database if no ids are requested
183            if (fromIndex == toIndex) {
184                return new String[0];
185            }
186            Connection connection = null;
187            try {
188                connection = dataSource.getConnection();
189                return adapter.doGetIds(connection, fromIndex, toIndex);
190            } catch (Exception e) {
191                throw new AuditorException("Could not retrieve exchange ids", e);
192            } finally {
193                close(connection, false);
194            }
195        }
196    
197        /* (non-Javadoc)
198         * @see org.apache.servicemix.jbi.audit.AuditorMBean#getExchanges(java.lang.String[])
199         */
200        public MessageExchange[] getExchangesByIds(String[] ids) throws AuditorException {
201            MessageExchange[] exchanges = new MessageExchange[ids.length];
202            Connection connection = null;
203            try {
204                connection = dataSource.getConnection();
205                for (int row = 0; row < ids.length; row++) {
206                    exchanges[row] = getExchange(adapter.doLoadData(connection, ids[row]));
207                }
208                return exchanges;
209            } catch (Exception e) {
210                throw new AuditorException("Could not retrieve exchanges", e);
211            } finally {
212                close(connection, false);
213            }
214        }
215    
216        /* (non-Javadoc)
217         * @see org.apache.servicemix.jbi.audit.AuditorMBean#deleteExchanges(java.lang.String[])
218         */
219        public int deleteExchangesByIds(String[] ids) throws AuditorException {
220            Connection connection = null;
221            boolean restoreAutoCommit = false;
222            try {
223                connection = dataSource.getConnection();
224                if (connection.getAutoCommit()) {
225                    connection.setAutoCommit(false);
226                    restoreAutoCommit = true;
227                }
228                for (int row = 0; row < ids.length; row++) {
229                    adapter.doRemoveData(connection, ids[row]);
230                }
231                connection.commit();
232                return -1;
233            } catch (Exception e) {
234                throw new AuditorException("Could not delete exchanges", e);
235            } finally {
236                close(connection, restoreAutoCommit);
237            }
238        }
239        
240        // TODO: this should be somewhere in org.apache.servicemix.jbi.messaging
241        protected MessageExchange getExchange(byte[] data) throws AuditorException {
242            ExchangePacket packet = null;
243            try {
244                packet = ExchangePacket.readPacket(data);
245            } catch (Exception e) {
246                throw new AuditorException("Unable to reconstruct exchange", e);
247            }
248            URI mep = packet.getPattern();
249            if (MessageExchangeSupport.IN_ONLY.equals(mep)) {
250                return new InOnlyImpl(packet);
251            } else if (MessageExchangeSupport.IN_OPTIONAL_OUT.equals(mep)) {
252                return new InOptionalOutImpl(packet);
253            } else if (MessageExchangeSupport.IN_OUT.equals(mep)) {
254                return new InOutImpl(packet);
255            } else if (MessageExchangeSupport.ROBUST_IN_ONLY.equals(mep)) {
256                return new RobustInOnlyImpl(packet);
257            } else {
258                throw new AuditorException("Unhandled mep: " + mep);
259            }
260        }
261        
262        public boolean isAutoStart() {
263            return autoStart;
264        }
265    
266        public void setAutoStart(boolean autoStart) {
267            this.autoStart = autoStart;
268        }
269    
270        private static void close(Connection connection, boolean restoreAutoCommit) {
271            if (connection != null) {
272                try {
273                    if (restoreAutoCommit) {
274                        connection.setAutoCommit(true);
275                    }
276                    connection.close();
277                } catch (SQLException e) {
278                    // Do nothing
279                }
280            }
281        }
282    
283    }