/*
 * TaminoMailRepository.java
 *
 * Created on 8. April 2002, 20:33
 */

package org.apache.james.mailrepository;

import org.w3c.dom.Element;
import java.io.ByteArrayOutputStream;
import com.softwareag.tamino.db.api.objectModel.dom.TDOMObjectModel;
import com.softwareag.tamino.db.api.objectModel.TXMLObjectModel;
import com.softwareag.tamino.db.api.objectModel.TXMLObject;
import org.apache.james.core.MailImpl;

import com.softwareag.tamino.db.api.response.TResponse;
import com.softwareag.tamino.db.api.connection.TConnectionFactory;
import com.softwareag.tamino.db.api.connection.TConnection;
import com.softwareag.tamino.db.api.accessor.TXMLObjectAccessor;
import com.softwareag.tamino.db.api.accessor.TAccessLocation;
import com.softwareag.tamino.db.api.accessor.TQuery;
import chaudhuri.util.DOMUtil;

/**
 *
 * @author  hch
 */
public class TaminoMailRepository extends org.apache.avalon.framework.logger.AbstractLoggable implements org.apache.james.services.MailRepository, org.apache.avalon.framework.configuration.Configurable, org.apache.avalon.framework.context.Contextualizable, org.apache.avalon.framework.activity.Initializable, org.apache.avalon.framework.component.Composable, org.apache.avalon.framework.component.Component {
    
    protected org.apache.avalon.framework.context.Context context;
    protected String taminourl;
    protected String collection;
    protected String repository;
    private org.apache.james.util.Lock lock;
    
    public void compose(org.apache.avalon.framework.component.ComponentManager componentManager) throws org.apache.avalon.framework.component.ComponentException {
        //        getLogger().debug(this.getClass().getName() + ".compose()");
    }
    
    public void configure(org.apache.avalon.framework.configuration.Configuration conf) throws org.apache.avalon.framework.configuration.ConfigurationException {
        //getLogger().debug(this.getClass().getName() + ".configure()");
        String destination = conf.getAttribute("destinationURL");
        getLogger().debug(getClass().getName()+".destinationURL: " + destination);
        
        taminourl = conf.getChild("dburl").getValue();
        if(taminourl.endsWith("/"))
            taminourl = taminourl.substring(0, taminourl.length()-1);
        getLogger().debug(getClass().getName()+".taminoURL: " + taminourl);
        
        collection = conf.getAttribute("type");
        if (! (collection.equals("MAIL") || collection.equals("SPOOL")) ) {
            getLogger().warn( "Attempt to configure "+getClass().getName()+" as " +
            collection);
            throw new org.apache.avalon.framework.configuration.ConfigurationException("Attempt to configure "+getClass().getName()+" as " + collection);
        }
        getLogger().debug(getClass().getName()+".collection: " + collection);
        
        repository = destination.substring(destination.indexOf(':')+1);
        if(!repository.endsWith("/"))
            repository = repository + "/";
        if(repository.startsWith("//"))
            repository = repository.substring(2);
        getLogger().debug(getClass().getName()+".repository: " + repository);
    }
    
    public void contextualize(org.apache.avalon.framework.context.Context context) throws org.apache.avalon.framework.context.ContextException {
        //        getLogger().debug(this.getClass().getName() + ".contextualize()");
        this.context = context;
    }
    
    protected void initializeTamino() throws Exception {
        //getLogger().debug(getClass().getName() + "connecting to "+taminourl);
        TConnectionFactory tcf = TConnectionFactory.getInstance();
        TConnection tc = tcf.newConnection(taminourl);
        TXMLObjectModel model = TDOMObjectModel.getInstance();
        //getLogger().debug(getClass().getName() + "opening collection '"+collection+"'");
        
        //check whether the database is initialized
        boolean found = false;
        
        com.softwareag.tamino.db.api.accessor.TSchemaDefinition3Accessor tsda = tc.newSchemaDefinition3Accessor(model);
        java.util.Iterator collections = tsda.getCollectionNames();
        //check whether the required collection exists
        while(collections.hasNext()) {
            String c = collections.next().toString();
            if(this.collection.equals(c)) {
                getLogger().info(getClass().getName()+" collection "+collection+" exists");
                //collection found. Check whether the doctype exists
                java.util.Iterator schemas = tsda.getSchemaNames(collection);
                while(schemas.hasNext()) {
                    String schema = schemas.next().toString();
                    java.util.Iterator doctypes = tsda.getDoctypeNames(collection, schema);
                    while(doctypes.hasNext()) {
                        String doctype = doctypes.next().toString();
                        if("Message".equals(doctype))
                            found = true;
                    }
                }
            }
        }
        
        if(!found) {
            getLogger().info(getClass().getName()+" defining schema");
            //doctype not found -> let's define the schema
            java.io.InputStream is = getClass().getResourceAsStream("James.TSD");
            com.softwareag.tamino.db.api.objectModel.TXMLObject txo = com.softwareag.tamino.db.api.objectModel.TXMLObject.newInstance(is);
            tsda.define(txo);
        }
    }
    
    public void initialize() throws java.lang.Exception {
        getLogger().debug(this.getClass().getName() + ".initialize()");
        lock = new org.apache.james.util.Lock();
        
        try {
            initializeTamino();
        }
        catch(Exception e) {
            getLogger().error("Error during store: ", e);
            e.printStackTrace();
        }
    }
    
    /**
     * List string keys of messages in repository.
     *
     */
    public java.util.Iterator list() {
        getLogger().debug(this.getClass().getName() + ".list()");
        java.util.ArrayList result = new java.util.ArrayList();
        try {
            //getLogger().debug(getClass().getName() + "connecting to "+taminourl);
            TConnectionFactory tcf = TConnectionFactory.getInstance();
            TConnection tc = tcf.newConnection(taminourl);
            TXMLObjectModel model = TDOMObjectModel.getInstance();
            //getLogger().debug(getClass().getName() + "opening collection '"+collection+"'");
            TXMLObjectAccessor txoa = tc.newXMLObjectAccessor(TAccessLocation.newInstance(collection), model);
            
            TResponse tr = txoa.query(TQuery.newInstance("/Message[@repository=\""+repository+"\"]/@name"));
            com.softwareag.tamino.db.api.objectModel.TXMLObjectIterator txoi = tr.getXMLObjectIterator();
            while(txoi.hasNext()) {
                TXMLObject txo = txoi.next();
                result.add(((Element)txo.getElement()).getAttribute("name"));
                //System.out.println(chaudhuri.util.DOMUtil.toXML((org.w3c.dom.Element)txo.getElement()));
            }
        }
        catch(Exception e) {
            getLogger().error("Error during store: ", e);
            e.printStackTrace();
        }
        getLogger().debug(this.getClass().getName() + " "+result.size()+" messages found");
        return result.iterator();
    }
    
    /**
     * Obtains a lock on a message identified by key
     */
    public boolean lock(String key) {
        //getLogger().debug(this.getClass().getName() + ".lock()");
        if (lock.lock(key)) {
            synchronized (this) {
                notifyAll();
            }
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * Releases a lock on a message identified the key
     */
    public boolean unlock(String key) {
        getLogger().debug(this.getClass().getName() + ".unlock()");
        if (lock.unlock(key)) {
            synchronized (this) {
                notifyAll();
            }
            return true;
        } else {
            return false;
        }
    }
    
    protected void removeMessage(String key) {
        try {
            TConnectionFactory tcf = TConnectionFactory.getInstance();
            TConnection tc = tcf.newConnection(taminourl);
            TXMLObjectModel model = TDOMObjectModel.getInstance();
            //getLogger().debug(getClass().getName() + "opening collection '"+collection+"'");
            TXMLObjectAccessor txoa = tc.newXMLObjectAccessor(TAccessLocation.newInstance(collection), model);
            
            TResponse tr = txoa.delete(TQuery.newInstance("/Message[@name=\""+key+"\" and @repository=\""+repository+"\"]"));
            //            System.out.println("remove Tamino returncode "+tr.getReturnValue());
            getLogger().debug(getClass().getName()+".remove Tamino returncode "+tr.getReturnValue());
        }
        catch(Exception e) {
            getLogger().error("Error during remove: ", e);
            e.printStackTrace();
        }
    }
    
    /**
     * Removes a message identified by key.
     */
    public void remove(String key) {
        getLogger().debug(this.getClass().getName() + ".remove("+key+")");
        if (lock(key)) {
            try {
                removeMessage(key);
            } finally {
                unlock(key);
            }
        } else {
            throw new RuntimeException("Cannot lock " + key + " to remove it");
        }
    }
    
    /**
     * Removes a specified message
     */
    public void remove(MailImpl mc) {
        getLogger().debug(this.getClass().getName() + ".remove("+mc+")");
        remove(mc.getName());
    }
    
    /**
     * Retrieves a message given a key. At the moment, keys can be obtained
     * from list() in superinterface Store.Repository
     */
    public MailImpl retrieve(String key) {
        getLogger().debug(this.getClass().getName() + ".retrieve('"+key+"')");
        MailImpl mc = null;
        try {
            //getLogger().debug(getClass().getName() + "connecting to "+taminourl);
            TConnectionFactory tcf = TConnectionFactory.getInstance();
            TConnection tc = tcf.newConnection(taminourl);
            TXMLObjectModel model = TDOMObjectModel.getInstance();
            //getLogger().debug(getClass().getName() + "opening collection '"+collection+"'");
            TXMLObjectAccessor txoa = tc.newXMLObjectAccessor(TAccessLocation.newInstance(collection), model);
            TResponse tr = txoa.query(TQuery.newInstance("/Message[@name=\""+key+"\" and @repository=\""+repository+"\"]"));
            if(tr.hasFirstXMLObject()) {
                TXMLObject txo = tr.getFirstXMLObject();
                Element message = (Element)txo.getElement();
                mc = new MailImpl();
                mc.setName(key);
                
                Element envelope = (Element)DOMUtil.getChildByTagName(message, "envelope");
                mc.setState(DOMUtil.getContentText(DOMUtil.getChildByTagName(envelope, "state")));
                mc.setErrorMessage(DOMUtil.getContentText(DOMUtil.getChildByTagName(envelope, "errorMessage")));
                mc.setSender(new org.apache.mailet.MailAddress(DOMUtil.getContentText(DOMUtil.getChildByTagName(envelope, "from"))));
                org.w3c.dom.NodeList rcp = envelope.getElementsByTagName("to");
                java.util.Set rcphash = new java.util.HashSet();
                for(int i=0;i<rcp.getLength();i++) {
                    org.w3c.dom.Node x = rcp.item(i);
                    rcphash.add( new org.apache.mailet.MailAddress(DOMUtil.getContentText(x)) );
                }
                mc.setRecipients(rcphash);
                mc.setRemoteHost(DOMUtil.getContentText(DOMUtil.getChildByTagName(envelope, "remoteHost")));
                mc.setRemoteAddr(DOMUtil.getContentText(DOMUtil.getChildByTagName(envelope, "remoteAddr")));
                mc.setLastUpdated(parseDate(message.getAttribute("lastUpdated")));
                
                TaminoMimeMessageSource source = new TaminoMimeMessageSource(message);
                org.apache.james.core.MimeMessageWrapper mimemessage = new org.apache.james.core.MimeMessageWrapper(source);
                mc.setMessage(mimemessage);
            }
        }
        catch(Exception e) {
            getLogger().error("Error during retrieve: ", e);
            e.printStackTrace();
        }
        return mc;
    }
    
    protected java.util.Date parseDate(String date) throws java.text.ParseException {
        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", java.util.Locale.US);
        return sdf.parse(date);
    }
    
    protected void storeMessage(org.apache.james.core.MailImpl mc) {
        try {
            //getLogger().debug(getClass().getName() + "connecting to "+taminourl);
            TConnectionFactory tcf = TConnectionFactory.getInstance();
            TConnection tc = tcf.newConnection(taminourl);
            TXMLObjectModel model = TDOMObjectModel.getInstance();
            //getLogger().debug(getClass().getName() + "opening collection '"+collection+"'");
            TXMLObjectAccessor txoa = tc.newXMLObjectAccessor(TAccessLocation.newInstance(collection), model);
            
            //check if mail exists
            //if exists, update
            //otherwise store
            TResponse tr = txoa.query(TQuery.newInstance("/Message[@name=\""+mc.getName()+"\" and @repository=\""+repository+"\"]"));
            TXMLObject messageobj = null;
            Element message = null;
            org.w3c.dom.Document doc = null;
            if(tr.hasFirstXMLObject()) {
                messageobj = tr.getFirstXMLObject();
                message = (Element)messageobj.getElement();
                doc = message.getOwnerDocument();
            }
            else {
                javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
                javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
                doc = db.newDocument();
                message = doc.createElement("Message");
                messageobj = TXMLObject.newInstance(message);
            }
            
            messageobj.setDocname(repository+mc.getName());
            message.setAttribute("name", mc.getName());
            message.setAttribute("repository", repository);
            message.setAttribute("lastUpdated", mc.getLastUpdated().toString());
            message.setAttribute("messageSize", String.valueOf(mc.getMessageSize()));
            
            Element envelope = (Element)DOMUtil.getChildByTagName(message, "envelope", true);
            if(mc.getErrorMessage()!=null) {
                Element error = doc.createElement("errorMessage");
                error.appendChild(doc.createTextNode(mc.getErrorMessage()));
                envelope.appendChild(error);
            }
            java.util.Iterator rxit = mc.getRecipients().iterator();
            while(rxit.hasNext()) {
                Element rx = doc.createElement("to");
                rx.appendChild(doc.createTextNode(rxit.next().toString()));
                envelope.appendChild(rx);
            }
            
            {
                Element tx = doc.createElement("from");
                tx.appendChild(doc.createTextNode(mc.getSender().toString()));
                envelope.appendChild(tx);
            }
            
            {
                Element tx = doc.createElement("remoteHost");
                tx.appendChild(doc.createTextNode(mc.getRemoteHost()));
                envelope.appendChild(tx);
            }
            
            {
                Element tx = doc.createElement("remoteAddr");
                tx.appendChild(doc.createTextNode(mc.getRemoteAddr()));
                envelope.appendChild(tx);
            } {
                Element tx = doc.createElement("state");
                tx.appendChild(doc.createTextNode(mc.getState()));
                envelope.appendChild(tx);
            }
            
            //mc.getMessage().writeTo(System.out);
            if(mc.getMessage() != null) {
                ByteArrayOutputStream headeros = new ByteArrayOutputStream();
                ByteArrayOutputStream bodyos = new ByteArrayOutputStream();
                org.apache.james.core.MimeMessageWrapper.writeTo(mc.getMessage(), headeros, bodyos);
                
                Element e;
                e= doc.createElement("body");
                e.appendChild(doc.createTextNode(bodyos.toString()));
                message.appendChild(e);
                
                e = doc.createElement("header");
                e.appendChild(doc.createTextNode(headeros.toString()));
                message.appendChild(e);
            }
            
            if(messageobj.hasId())
                txoa.update(messageobj);
            else
                txoa.insert(messageobj);
            
            //System.out.println(chaudhuri.util.DOMUtil.toXML(message));
        }
        catch(Exception e) {
            getLogger().error("Error during store: ", e);
            e.printStackTrace();
        }
    }
    
    /**
     * Stores a message in this repository. Shouldn't this return the key
     * under which it is stored?
     */
    public void store(MailImpl mc) {
        getLogger().debug(getClass().getName() + ".store()");
        try {
            String key = mc.getName();
            //Remember whether this key was locked
            boolean wasLocked = lock.isLocked(key);
            
            if (!wasLocked) {
                //If it wasn't locked, we want a lock during the store
                lock.lock(key);
            }
            try {
                storeMessage(mc);
            } finally {
                if (!wasLocked) {
                    //If it wasn't locked, we need to now unlock
                    lock.unlock(key);
                }
            }
            getLogger().debug("Mail " + key + " stored." );
            synchronized (this) {
                notifyAll();
            }
        } catch (Exception e) {
            getLogger().error("Exception storing mail: " + e);
            e.printStackTrace();
            throw new RuntimeException("Exception caught while storing Message Container: " + e);
        }
    }
    
}