/*
 * Copyright 2005-2006 The Apache Software Foundation.
 *
 * Licensed 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: 383702 $
 */
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[] getExchangeIds(int fromIndex, int toIndex) throws AuditorException {
		return this.delegatedAuditor.getExchangeIds(fromIndex,toIndex);
	}

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

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

	public int deleteExchanges(String[] ids) throws AuditorException {
		try {
			this.luceneIndexer.remove(ids);
		} catch (IOException io) {
			throw new AuditorException(io);
		}
		return this.delegatedAuditor.deleteExchanges(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);
		}
	}
}
