/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.servicemix.jbi.audit.lucene;

import java.io.IOException;
import java.util.Iterator;
import java.util.Set;

import javax.jbi.JBIException;
import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.MessagingException;
import javax.jbi.messaging.NormalizedMessage;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.servicemix.jbi.audit.AbstractAuditor;
import org.apache.servicemix.jbi.audit.AuditorException;
import org.apache.servicemix.jbi.audit.AuditorMBean;
import org.apache.servicemix.jbi.audit.AuditorQueryMBean;
import org.apache.servicemix.jbi.event.ExchangeEvent;
import org.apache.servicemix.jbi.event.ExchangeListener;
import org.apache.servicemix.jbi.jaxp.SourceTransformer;

/**
 * Lucene AuditorQuery implementation. It uses Lucene as the indexing mechanism
 * for searching Exchanges and needs a delegated AuditorMBean to persist
 * Exchanges.
 * 
 * The Content of messages are stored as: 
 *  - org.apache.servicemix.in.contents
 *  - org.apache.servicemix.out.contents, if exists
 *  - org.apache.servicemix.fault.contents, if exists
 * 
 * Properties for IN Messages are stored as: 
 *  - org.apache.servicemix.in.propertyname
 *  - org.apache.servicemix.out.propertyname, if exists
 *  - org.apache.servicemix.fault.propertyname, if exists
 * 
 * @author George Gastaldi
 * @since 2.1
 * @version $Revision: 550578 $
 */
public class LuceneAuditor extends AbstractAuditor implements AuditorQueryMBean {

    private AuditorMBean delegatedAuditor;

    private LuceneIndexer luceneIndexer = new LuceneIndexer();

    protected void doStart() throws JBIException {
        super.doStart();
        if (delegatedAuditor == null) {
            throw new JBIException("A delegated auditor must be provided");
        }
        this.delegatedAuditor.start();
    }

    protected void doStop() throws JBIException {
        super.doStop();
        this.delegatedAuditor.stop();
    }

    /**
     * @return Returns the luceneIndexer.
     */
    public LuceneIndexer getLuceneIndexer() {
        return luceneIndexer;
    }

    /**
     * @param luceneIndexer
     *            The luceneIndexer to set.
     */
    public void setLuceneIndexer(LuceneIndexer luceneIndexer) {
        this.luceneIndexer = luceneIndexer;
    }

    /**
     * @return Returns the delegatedAuditor.
     */
    public AuditorMBean getDelegatedAuditor() {
        return delegatedAuditor;
    }

    /**
     * @param delegatedAuditor
     *            The delegatedAuditor to set.
     */
    public void setDelegatedAuditor(AuditorMBean delegatedAuditor) {
        this.delegatedAuditor = delegatedAuditor;
        if (delegatedAuditor instanceof AbstractAuditor) {
            ((AbstractAuditor) delegatedAuditor).setAsContainerListener(false);
        }
    }

    public int getExchangeCount() throws AuditorException {
        return this.delegatedAuditor.getExchangeCount();
    }

    public String[] getExchangeIdsByRange(int fromIndex, int toIndex) throws AuditorException {
        return this.delegatedAuditor.getExchangeIdsByRange(fromIndex, toIndex);
    }

    public MessageExchange[] getExchangesByIds(String[] ids) throws AuditorException {
        return this.delegatedAuditor.getExchangesByIds(ids);
    }

    public int deleteExchangesByRange(int fromIndex, int toIndex) throws AuditorException {
        // TODO: Remove ids from Lucene Index
        return this.delegatedAuditor.deleteExchangesByRange(fromIndex, toIndex);
    }

    public int deleteExchangesByIds(String[] ids) throws AuditorException {
        try {
            this.luceneIndexer.remove(ids);
        } catch (IOException io) {
            throw new AuditorException(io);
        }
        return this.delegatedAuditor.deleteExchangesByIds(ids);
    }

    public void exchangeSent(ExchangeEvent event) {
        MessageExchange exchange = event.getExchange();
        try {
            Document doc = createDocument(exchange);
            this.luceneIndexer.add(doc, exchange.getExchangeId());
            if (delegatedAuditor instanceof ExchangeListener) {
                ((ExchangeListener) delegatedAuditor).exchangeSent(event);
            }
        } catch (Exception e) {
            log.error("Error while adding to lucene", e);
        }
    }

    public String getDescription() {
        return "Lucene Auditor";
    }

    public String[] findExchangesIDsByStatus(ExchangeStatus status) throws AuditorException {
        String field = "org.apache.servicemix.exchangestatus";
        return getExchangeIds(field, String.valueOf(status));
    }

    public String[] findExchangesIDsByMessageContent(String type, String content) throws AuditorException {
        String field = "org.apache.servicemix." + type + ".contents";
        return getExchangeIds(field, content);
    }

    public String[] findExchangesIDsByMessageProperty(String type, 
                                                      String property, 
                                                      String value) throws AuditorException {
        if (property != null && !property.startsWith("org.apache.servicemix")) {
            property = "org.apache.servicemix." + type + "." + property;
        }
        return getExchangeIds(property, value);
    }

    protected Document createDocument(MessageExchange me) throws MessagingException {
        try {
            // This could be in a separated class (a LuceneDocumentProvider)
            SourceTransformer st = new SourceTransformer();
            Document d = new Document();
            d.add(Field.Keyword("org.apache.servicemix.exchangeid", me.getExchangeId()));
            d.add(Field.Keyword("org.apache.servicemix.exchangestatus", String.valueOf(me.getStatus())));

            String[] types = {"in", "out", "fault" };
            for (int i = 0; i < types.length; i++) {
                String type = types[i];
                NormalizedMessage nm = me.getMessage(type);
                if (nm != null) {
                    d.add(Field.UnStored("org.apache.servicemix." + type + ".contents", st.contentToString(nm)));
                    addMessagePropertiesToDocument(nm, d, type);
                }
            }
            return d;
        } catch (MessagingException mse) {
            throw mse;
        } catch (Exception ex) {
            throw new MessagingException("Error while creating Lucene Document", ex);
        }
    }

    protected void addMessagePropertiesToDocument(NormalizedMessage nm, 
                                                  Document document, 
                                                  String type) throws MessagingException {
        Set propertyNames = nm.getPropertyNames();
        for (Iterator iter = propertyNames.iterator(); iter.hasNext();) {
            String propertyName = (String) iter.next();
            Object value = nm.getProperty(propertyName);
            if (value instanceof String) {
                //org.apache.servicemix.out.myproperty
                document.add(Field.Keyword("org.apache.servicemix." + type + "." + propertyName, String.valueOf(value)));
            }
        }
    }

    public String[] getExchangeIds(String queryContent, String field) throws AuditorException {
        DefaultLuceneCallback dfc = new DefaultLuceneCallback(queryContent, field);
        try {
            return (String[]) luceneIndexer.search(dfc);
        } catch (IOException e) {
            throw new AuditorException("Error while getting Exchange IDs", e);
        }
    }
}
