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.lucene;
018    
019    import java.io.IOException;
020    import java.util.Iterator;
021    import java.util.Set;
022    
023    import javax.jbi.JBIException;
024    import javax.jbi.messaging.ExchangeStatus;
025    import javax.jbi.messaging.MessageExchange;
026    import javax.jbi.messaging.MessagingException;
027    import javax.jbi.messaging.NormalizedMessage;
028    
029    import org.apache.lucene.document.Document;
030    import org.apache.lucene.document.Field;
031    import org.apache.servicemix.jbi.audit.AbstractAuditor;
032    import org.apache.servicemix.jbi.audit.AuditorException;
033    import org.apache.servicemix.jbi.audit.AuditorMBean;
034    import org.apache.servicemix.jbi.audit.AuditorQueryMBean;
035    import org.apache.servicemix.jbi.event.ExchangeEvent;
036    import org.apache.servicemix.jbi.event.ExchangeListener;
037    import org.apache.servicemix.jbi.jaxp.SourceTransformer;
038    
039    /**
040     * Lucene AuditorQuery implementation. It uses Lucene as the indexing mechanism
041     * for searching Exchanges and needs a delegated AuditorMBean to persist
042     * Exchanges.
043     * 
044     * The Content of messages are stored as: 
045     *  - org.apache.servicemix.in.contents
046     *  - org.apache.servicemix.out.contents, if exists
047     *  - org.apache.servicemix.fault.contents, if exists
048     * 
049     * Properties for IN Messages are stored as: 
050     *  - org.apache.servicemix.in.propertyname
051     *  - org.apache.servicemix.out.propertyname, if exists
052     *  - org.apache.servicemix.fault.propertyname, if exists
053     * 
054     * @author George Gastaldi
055     * @since 2.1
056     * @version $Revision: 34165 $
057     */
058    public class LuceneAuditor extends AbstractAuditor implements AuditorQueryMBean {
059    
060        private AuditorMBean delegatedAuditor;
061    
062        private LuceneIndexer luceneIndexer = new LuceneIndexer();
063    
064        protected void doStart() throws JBIException {
065            super.doStart();
066            if (delegatedAuditor == null) {
067                throw new JBIException("A delegated auditor must be provided");
068            }
069            this.delegatedAuditor.start();
070        }
071    
072        protected void doStop() throws JBIException {
073            super.doStop();
074            this.delegatedAuditor.stop();
075        }
076    
077        /**
078         * @return Returns the luceneIndexer.
079         */
080        public LuceneIndexer getLuceneIndexer() {
081            return luceneIndexer;
082        }
083    
084        /**
085         * @param luceneIndexer
086         *            The luceneIndexer to set.
087         */
088        public void setLuceneIndexer(LuceneIndexer luceneIndexer) {
089            this.luceneIndexer = luceneIndexer;
090        }
091    
092        /**
093         * @return Returns the delegatedAuditor.
094         */
095        public AuditorMBean getDelegatedAuditor() {
096            return delegatedAuditor;
097        }
098    
099        /**
100         * @param delegatedAuditor
101         *            The delegatedAuditor to set.
102         */
103        public void setDelegatedAuditor(AuditorMBean delegatedAuditor) {
104            this.delegatedAuditor = delegatedAuditor;
105            if (delegatedAuditor instanceof AbstractAuditor) {
106                ((AbstractAuditor) delegatedAuditor).setAsContainerListener(false);
107            }
108        }
109    
110        public int getExchangeCount() throws AuditorException {
111            return this.delegatedAuditor.getExchangeCount();
112        }
113    
114        public String[] getExchangeIdsByRange(int fromIndex, int toIndex) throws AuditorException {
115            return this.delegatedAuditor.getExchangeIdsByRange(fromIndex, toIndex);
116        }
117    
118        public MessageExchange[] getExchangesByIds(String[] ids) throws AuditorException {
119            return this.delegatedAuditor.getExchangesByIds(ids);
120        }
121    
122        public int deleteExchangesByRange(int fromIndex, int toIndex) throws AuditorException {
123            // TODO: Remove ids from Lucene Index
124            return this.delegatedAuditor.deleteExchangesByRange(fromIndex, toIndex);
125        }
126    
127        public int deleteExchangesByIds(String[] ids) throws AuditorException {
128            try {
129                this.luceneIndexer.remove(ids);
130            } catch (IOException io) {
131                throw new AuditorException(io);
132            }
133            return this.delegatedAuditor.deleteExchangesByIds(ids);
134        }
135    
136        public void exchangeSent(ExchangeEvent event) {
137            MessageExchange exchange = event.getExchange();
138            try {
139                Document doc = createDocument(exchange);
140                this.luceneIndexer.add(doc, exchange.getExchangeId());
141                if (delegatedAuditor instanceof ExchangeListener) {
142                    ((ExchangeListener) delegatedAuditor).exchangeSent(event);
143                }
144            } catch (Exception e) {
145                log.error("Error while adding to lucene", e);
146            }
147        }
148    
149        public String getDescription() {
150            return "Lucene Auditor";
151        }
152    
153        public String[] findExchangesIDsByStatus(ExchangeStatus status) throws AuditorException {
154            String field = "org.apache.servicemix.exchangestatus";
155            return getExchangeIds(field, String.valueOf(status));
156        }
157    
158        public String[] findExchangesIDsByMessageContent(String type, String content) throws AuditorException {
159            String field = "org.apache.servicemix." + type + ".contents";
160            return getExchangeIds(field, content);
161        }
162    
163        public String[] findExchangesIDsByMessageProperty(String type, 
164                                                          String property, 
165                                                          String value) throws AuditorException {
166            if (property != null && !property.startsWith("org.apache.servicemix")) {
167                property = "org.apache.servicemix." + type + "." + property;
168            }
169            return getExchangeIds(property, value);
170        }
171    
172        protected Document createDocument(MessageExchange me) throws MessagingException {
173            try {
174                // This could be in a separated class (a LuceneDocumentProvider)
175                SourceTransformer st = new SourceTransformer();
176                Document d = new Document();
177                d.add(Field.Keyword("org.apache.servicemix.exchangeid", me.getExchangeId()));
178                d.add(Field.Keyword("org.apache.servicemix.exchangestatus", String.valueOf(me.getStatus())));
179    
180                String[] types = {"in", "out", "fault" };
181                for (int i = 0; i < types.length; i++) {
182                    String type = types[i];
183                    NormalizedMessage nm = me.getMessage(type);
184                    if (nm != null) {
185                        d.add(Field.UnStored("org.apache.servicemix." + type + ".contents", st.contentToString(nm)));
186                        addMessagePropertiesToDocument(nm, d, type);
187                    }
188                }
189                return d;
190            } catch (MessagingException mse) {
191                throw mse;
192            } catch (Exception ex) {
193                throw new MessagingException("Error while creating Lucene Document", ex);
194            }
195        }
196    
197        protected void addMessagePropertiesToDocument(NormalizedMessage nm, 
198                                                      Document document, 
199                                                      String type) throws MessagingException {
200            Set propertyNames = nm.getPropertyNames();
201            for (Iterator iter = propertyNames.iterator(); iter.hasNext();) {
202                String propertyName = (String) iter.next();
203                Object value = nm.getProperty(propertyName);
204                if (value instanceof String) {
205                    //org.apache.servicemix.out.myproperty
206                    document.add(Field.Keyword("org.apache.servicemix." + type + "." + propertyName, String.valueOf(value)));
207                }
208            }
209        }
210    
211        public String[] getExchangeIds(String queryContent, String field) throws AuditorException {
212            DefaultLuceneCallback dfc = new DefaultLuceneCallback(queryContent, field);
213            try {
214                return (String[]) luceneIndexer.search(dfc);
215            } catch (IOException e) {
216                throw new AuditorException("Error while getting Exchange IDs", e);
217            }
218        }
219    }