/**
 * Copyright (C) 2010 Orbeon, Inc.
 *
 * 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.1 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.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.resources;

import org.apache.log4j.Logger;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.util.LoggerFactory;
import org.orbeon.oxf.webapp.WebAppContext;

import java.io.*;
import java.net.URL;
import java.util.Map;

/**
 * The webapp resource manager is able to load resources from a WAR file. This
 * is very useful when distributing packaged applications.
 */
public class WebAppResourceManagerImpl extends ResourceManagerBase {

    public static final String WEB_APP_CONTEXT_KEY = WebAppResourceManagerImpl.class.getName() + "WebAppContext";
    public static final String ROOT_DIR = "oxf.resources.webapp.rootdir";

    private static Logger logger = LoggerFactory.createLogger(WebAppResourceManagerImpl.class);

    private WebAppContext webAppContext;
    private String rootDirectory;

    public WebAppResourceManagerImpl(Map props) {
        super(props);
        WebAppContext ctx = (WebAppContext) props.get(WEB_APP_CONTEXT_KEY);
        if (ctx == null)
            throw new OXFException("WebAppResourceManager needs a WebAppContext object in its map (key=" + WEB_APP_CONTEXT_KEY + ")");
        this.webAppContext = ctx;

        String root = (String) props.get(ROOT_DIR);
        if (root == null)
            throw new OXFException("WebAppResourceManager: property " + ROOT_DIR + " is null");
        this.rootDirectory = root;
    }

    /**
     * Returns a binary input stream for the specified key. The key could point
     * to any document type (text or binary).
     * @param key A Resource Manager key
     * @return a input stream
     */
    public InputStream getContentAsStream(String key) {
        if (logger.isDebugEnabled())
            logger.debug("getContentAsStream(" + key + ")");

        InputStream result = webAppContext.getResourceAsStream(rootDirectory + key);
        if (result == null)
            throw new ResourceNotFoundException(key);
        return result;
    }

    /**
     * Gets the last modified timestamp for the specified resource
     * @param key A Resource Manager key
     * @param doNotThrowResourceNotFound
     * @return a timestamp
     */
    protected long lastModifiedImpl(String key, boolean doNotThrowResourceNotFound) {
        try {
            long lm;
            String realPath = webAppContext.getRealPath(rootDirectory + key);
            if (realPath == null) {
                // Some application server do not return a real path, as they do
                // not uncompress the WAR file. This is in particular the case
                // when deploying compressed WAR files on Tomcat.
                URL url = webAppContext.getResource(rootDirectory + key);
                if (url == null) {
                    if (doNotThrowResourceNotFound) return -1;
                    else throw new ResourceNotFoundException(key);
                }
                lm = url.openConnection().getLastModified();
            } else {
                File file = new File(realPath);
                if (!file.canRead()) {
                    if (doNotThrowResourceNotFound) return -1;
                    else throw new ResourceNotFoundException(key);
                }
                lm = file.lastModified();
            }
            if (lm == 0) lm = 1;
            return lm;
        } catch (IOException e) {
            throw new OXFException(e);
        }
    }

    /**
     * Indicates if the resource manager implementation supports write operations
     * @return true if write operations are allowed
     */
    public boolean canWrite(String key) {
        return getRealPath(key) != null;
    }

    /**
     * Allows writing to the resource
     * @param key A Resource Manager key
     * @return an output stream
     */
    public OutputStream getOutputStream(String key) {

        final String realPath = getRealPath(key);
        if (realPath == null)
            throw new OXFException("Write Operation not supported");

        try {
            return new FileOutputStream(realPath);
        } catch (FileNotFoundException e) {
            throw new OXFException(e);
        }
    }

    /**
     * Allow writing to the resource
     * @param key A Resource Manager key
     * @return  a writer
     */
    public Writer getWriter(String key) {
        final String realPath = getRealPath(key);
        if (realPath == null)
            throw new OXFException("Write Operation not supported");

        try {
            return new FileWriter(realPath);
        } catch (IOException e) {
            throw new OXFException(e);
        }
    }

    /**
     * Returns the length of the file denoted by this abstract pathname.
     * @return The length, in bytes, of the file denoted by this abstract pathname, or 0L if the file does not exist
     */
    public int length(String key) {
        String realPath = webAppContext.getRealPath(rootDirectory + key);
        if (realPath == null) {
            // FIXME: this happens when the resources are in a WAR file in
            // WLS. In this case we should try to figure out the last modified of
            // the WAR itself.
            return 0;
        } else {
            return new Long(new File(realPath).length()).intValue();
        }
    }

    public String getRealPath(String key) {
        return webAppContext.getRealPath(rootDirectory + key);
        // Need an option for this as some callers call this for non-existing files
//        final String realPath = servletContext.getRealPath(rootDirectory + key);
//        if (realPath == null)
//            return null;
//
//        if (!new File(realPath).canRead())
//            throw new ResourceNotFoundException("Cannot read from file " + key);
//        else
//            return realPath;
    }
}