/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2012 The eXist Project
 *  http://exist-db.org
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  $Id$
 */
package org.exist.xquery.functions.xmldb;

import java.net.URISyntaxException;
import org.apache.log4j.Logger;
import org.exist.dom.DocumentImpl;
import org.exist.dom.QName;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.util.MimeTable;
import org.exist.util.MimeType;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.AnyURIValue;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;

/**
 * @author Dannes Wessels (dannes@exist-db.org)
 */
public class XMLDBSetMimeType extends BasicFunction {
	protected static final Logger logger = Logger.getLogger(XMLDBSetMimeType.class);
	public final static FunctionSignature signature =
		new FunctionSignature(
			new QName("set-mime-type", XMLDBModule.NAMESPACE_URI, XMLDBModule.PREFIX),
			"Set the MIME type of the resource $resource-uri." +
            XMLDBModule.ANY_URI,
			new SequenceType[] {
                new FunctionParameterSequenceType("resource-uri", Type.ANY_URI, Cardinality.EXACTLY_ONE, "The resource URI"),
                new FunctionParameterSequenceType("mime-type", Type.STRING, Cardinality.ZERO_OR_ONE, "The new mime-type, use empty sequence to set default value.")
			},
			new SequenceType(Type.EMPTY, Cardinality.EMPTY)
		);
	
    public XMLDBSetMimeType(XQueryContext context, FunctionSignature signature) {
        super(context, signature);
    }

    @Override
    public Sequence eval(Sequence args[], Sequence contextSequence) throws XPathException {

        // Get handle to Mime-type info
        MimeTable mimeTable = MimeTable.getInstance();

        // Get first parameter
        String pathParameter = new AnyURIValue(args[0].itemAt(0).getStringValue()).toString();

        if (pathParameter.matches("^[a-z]+://.*")) {
            throw new XPathException("Can not set mime-type for resources outside the database.");
        }

        XmldbURI pathUri = null;
        try {
            pathUri = XmldbURI.xmldbUriFor(pathParameter);
        } catch (URISyntaxException ex) {
            logger.debug(ex.getMessage());
            throw new XPathException("Invalid path '" + pathParameter + "'");
        }

        // Verify mime-type input
        MimeType newMimeType = null;
        if (args[1].isEmpty()) {
            // No input, use default mimetype
            newMimeType = mimeTable.getContentTypeFor(pathParameter);

            if (newMimeType == null) {
                throw new XPathException("Unable to determine mimetype for '" + pathParameter + "'");
            }

        } else {
            // Mimetype is provided, check if valid
            newMimeType = mimeTable.getContentType(args[1].getStringValue());

            if (newMimeType == null) {
                throw new XPathException("mime-type '" + args[1].getStringValue() + "' is not supported.");
            }
        }

        // Get mime-type of resource
        MimeType currentMimeType = getMimeTypeStoredResource(pathUri);
        if (currentMimeType == null) {
            // stored resource has no mime-type (unexpected situation)
            // fall back to document name
            logger.debug("Resource '" + pathUri + "' has no mime-type, retrieve from document name.");
            currentMimeType = mimeTable.getContentTypeFor(pathUri);
            
            // if extension based lookup still fails
            if (currentMimeType == null) {
                throw new XPathException("Unable to determine mime-type from path '" + pathUri + "'.");
            }            
        } 

        // Check if mimeType are equivalent
        // in some cases value null is set, then allow to set to new value (repair action)
        if (newMimeType.isXMLType() != currentMimeType.isXMLType() ) {
            throw new XPathException("New mime-type must be a " + currentMimeType.getXMLDBType() + " mime-type");
        }

        // At this moment it is possible to update the mimetype
        DBBroker broker = context.getBroker();
        BrokerPool brokerPool = broker.getBrokerPool();

        DocumentImpl doc = null;
        TransactionManager txnManager = brokerPool.getTransactionManager();
        Txn txn = txnManager.beginTransaction();

        try {
            // relative collection Path: add the current base URI
            pathUri = context.getBaseURI().toXmldbURI().resolveCollectionPath(pathUri);

            // try to open the document and acquire a lock
            doc = (DocumentImpl) broker.getXMLResource(pathUri, Lock.WRITE_LOCK);
            if (doc == null) {
                // no document selected, abort
                txnManager.abort(txn);

            } else {
                // set new mime-type
                doc.getMetadata().setMimeType(newMimeType.getName());
                
                // store meta data into database
                broker.storeXMLResource(txn, doc);
                
                // commit changes
                txnManager.commit(txn);
            }

        } catch (Exception e) {
            txnManager.abort(txn);
            logger.error(e.getMessage());
            throw new XPathException(this, e);

        } finally {
            //release all locks
            if (doc != null) {
                doc.getUpdateLock().release(Lock.WRITE_LOCK);
            }
        }

        return Sequence.EMPTY_SEQUENCE;
    }

    /**
     * Determine mimetype of currently stored resource. Copied from
     * get-mime-type.
     */
    private MimeType getMimeTypeStoredResource(XmldbURI pathUri) throws XPathException {
        MimeType returnValue = null;
        DocumentImpl doc = null;
        try {
            // relative collection Path: add the current base URI
            pathUri = context.getBaseURI().toXmldbURI().resolveCollectionPath(pathUri);
            
        } catch (XPathException ex) {
            logger.debug("Unable to convert path " + pathUri);
            return returnValue;
        }

        try {
            // try to open the document and acquire a lock
            doc = (DocumentImpl) context.getBroker().getXMLResource(pathUri, Lock.READ_LOCK);
            if (doc == null) {
                throw new XPathException("Resource '" + pathUri + "' does not exist.");
            } else {
                String mimetype = ((DocumentImpl) doc).getMetadata().getMimeType();
                returnValue = MimeTable.getInstance().getContentType(mimetype);
            }

        } catch (PermissionDeniedException ex) {
            logger.debug(ex.getMessage());

        } finally {

            if (doc != null) {
                doc.getUpdateLock().release(Lock.READ_LOCK);
            }
        }

        return returnValue;
    }
}

@@ -6204,27 +6204,24 @@
 er.store
-XMLResource
+Metadata
 (txn, do

make mimetype changes persistent into database, fire trigger