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.file;
018    
019    import java.io.BufferedOutputStream;
020    import java.io.File;
021    import java.io.FileNotFoundException;
022    import java.io.FileOutputStream;
023    import java.io.IOException;
024    import java.io.OutputStream;
025    import java.io.PrintWriter;
026    import java.text.DateFormat;
027    import java.text.SimpleDateFormat;
028    import java.util.Date;
029    
030    import javax.jbi.messaging.ExchangeStatus;
031    import javax.jbi.messaging.MessageExchange;
032    import javax.jbi.messaging.MessagingException;
033    import javax.jbi.messaging.NormalizedMessage;
034    import javax.xml.transform.TransformerException;
035    import javax.xml.transform.stream.StreamResult;
036    import javax.xml.transform.stream.StreamSource;
037    
038    import org.apache.commons.logging.Log;
039    import org.apache.commons.logging.LogFactory;
040    import org.apache.servicemix.jbi.audit.AbstractAuditor;
041    import org.apache.servicemix.jbi.audit.AuditorException;
042    import org.apache.servicemix.jbi.event.ExchangeEvent;
043    import org.apache.servicemix.jbi.jaxp.SourceTransformer;
044    import org.apache.servicemix.jbi.util.FileUtil;
045    import org.apache.servicemix.jbi.util.MessageUtil;
046    import org.springframework.beans.factory.InitializingBean;
047    
048    /**
049     * Simple implementation of a ServiceMix auditor that stores messages in files in a directory.
050     * Shows usage of {@link TeeInputStream} for auditing {@link StreamSource} message content. 
051     * 
052     * Currently, the file auditor will only store the message body for ACTIVE exchanges.
053     * 
054     * @org.apache.xbean.XBean element="fileAuditor" description="The Auditor of message exchanges to a directory"
055     */
056    public class FileAuditor extends AbstractAuditor implements InitializingBean {
057    
058        private static final Log LOG = LogFactory.getLog(FileAuditor.class);
059        private File directory;
060        private FileAuditorStrategy strategy = new FileAuditorStrategyImpl();
061        private boolean autostart = true;
062    
063        /**
064         * The directory used for storing the audited messages
065         * 
066         * @param directory
067         *            the directory
068         */
069        public void setDirectory(File directory) {
070            if (!directory.exists()) {
071                LOG.info("Creating directory " + directory);
072                directory.mkdirs();
073            }
074            this.directory = directory;
075        }
076    
077        /**
078         * {@inheritDoc}
079         */
080        public void exchangeSent(ExchangeEvent event) {
081            try {
082                MessageExchange exchange = event.getExchange();
083                if (exchange.getStatus() == ExchangeStatus.ACTIVE) {
084                    OutputStream os = getOutputStream(exchange);
085                    writeFileHeader(os, exchange);
086                    NormalizedMessage in = exchange.getMessage("in");                
087                    if (StreamSource.class.isAssignableFrom(in.getContent().getClass())) {
088                        StreamSource original = (StreamSource) exchange.getMessage("in").getContent();
089                        TeeInputStream tis = new TeeInputStream(original.getInputStream(), os);
090                        exchange.getMessage("in").setContent(new StreamSource(tis));
091                    } else {
092                        MessageUtil.enableContentRereadability(in);
093                        SourceTransformer transformer = new SourceTransformer();
094                        transformer.toResult(in.getContent(), new StreamResult(os));
095                    }
096                }
097            } catch (IOException e) {
098                LOG.error(String.format("Error occurred while storing message %s", event.getExchange().getExchangeId()), e);
099            } catch (TransformerException e) {
100                LOG.error(String.format("Error occurred while storing message %s", event.getExchange().getExchangeId()), e);
101            } catch (MessagingException e) {
102                LOG.error(String.format("Error occurred while storing message %s", event.getExchange().getExchangeId()), e);
103            }
104        }
105    
106        private void writeFileHeader(OutputStream os, MessageExchange exchange) {
107            MessageExchangeWriter writer = new MessageExchangeWriter(os);
108            writer.writeMessageExchange(exchange);
109            writer.println(); 
110            writer.println("-- Normalized message (in) --");
111            writer.writeNormalizedMessage(exchange.getMessage("in"));
112            writer.flush();
113        }
114    
115        /*
116         * Get the outputstream for writing the message content
117         */
118        private OutputStream getOutputStream(MessageExchange exchange) throws FileNotFoundException {
119            File file = new File(directory, strategy.getFileName(exchange));
120            if (!file.getParentFile().exists()) {
121                file.getParentFile().mkdirs();
122            }
123            return new BufferedOutputStream(new FileOutputStream(file));
124        }
125    
126        @Override
127        public int deleteExchangesByIds(String[] ids) throws AuditorException {
128            throw new AuditorException("deleteExchangesById(s) currently unsupported by FileAuditor");
129        }
130    
131        @Override
132        public int getExchangeCount() throws AuditorException {
133            return FileUtil.countFilesInDirectory(directory);
134        }
135    
136        @Override
137        public String[] getExchangeIdsByRange(int fromIndex, int toIndex) throws AuditorException {
138            throw new AuditorException("getExchangeIdsByRange currently unsupported by FileAuditor");
139        }
140    
141        @Override
142        public MessageExchange[] getExchangesByIds(String[] ids) throws AuditorException {
143            throw new AuditorException("getExchangeByIds currently unsupported by FileAuditor");
144        }
145    
146        /**
147         * {@inheritDoc}
148         */
149        public String getDescription() {
150            return "File-based auditing service";
151        }
152    
153        public void afterPropertiesSet() throws Exception {
154            init(getContainer());
155            if (autostart) {
156                start();
157            } else {
158                stop();
159            }
160        }
161        
162        /*
163         * Convenience PrintWriter implementation
164         */
165        private final class MessageExchangeWriter extends PrintWriter {
166          
167            private MessageExchangeWriter(OutputStream os) {
168                super(os);
169            }
170            
171            private void writeMessageExchange(MessageExchange exchange) {
172                println("-- Exchange " + exchange.getExchangeId() + " --");
173                writeProperty("endpoint", exchange.getEndpoint());
174                writeProperty("MEP", exchange.getPattern());
175                for (Object key : exchange.getPropertyNames()) {
176                    writeProperty(key, exchange.getProperty(key.toString()));
177                }
178            }
179            
180            private void writeNormalizedMessage(NormalizedMessage message) {
181                for (Object key : message.getPropertyNames()) {
182                    writeProperty(key, message.getProperty(key.toString()));
183                }
184                println(); println("- content -");
185            }
186    
187            
188            private void writeProperty(Object key, Object value) {
189                println(String.format(" %s : %s", key, value));
190            }
191        }
192        
193        /*
194         * Default FileAuditorStrategy implementation, writing audit files in a folder per day
195         */
196        private class FileAuditorStrategyImpl implements FileAuditorStrategy {
197            
198            private final DateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd");
199            
200            public String getFileName(MessageExchange exchange) {
201                return dateformat.format(new Date()) + File.separatorChar + exchange.getExchangeId().replaceAll("[:\\.]", "_");
202            }
203        }
204    }