/*
 * 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.jetspeed.deployment.impl;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.configuration.PropertiesConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.jetspeed.deployment.DeploymentEvent;
import org.apache.jetspeed.deployment.DeploymentEventListener;
import org.apache.jetspeed.deployment.DeploymentException;
import org.apache.jetspeed.deployment.DeploymentStatus;
import org.apache.jetspeed.util.DirectoryHelper;

/**
 * <p>
 * DirectFolderEventListener
 * </p>
 * 
 * @author <a href="mailto:weaver@apache.org">Scott T. Weaver </a>
 * @version $Id: DeployDecoratorEventListener.java 767444 2009-04-22 10:08:53Z ate $
 */
public class DeployDecoratorEventListener implements DeploymentEventListener
{
    protected static final Logger log = LoggerFactory.getLogger("deployment");
    protected String           deployToDir;

    public DeployDecoratorEventListener(String deployToDir) throws FileNotFoundException
    {
        File checkFile = new File(deployToDir);
        if (checkFile.exists())
        {
            try
            {
                this.deployToDir = checkFile.getCanonicalPath();
            }
            catch (IOException e) {}
        }
        else
        {
            throw new FileNotFoundException("The deployment directory, " + checkFile.getAbsolutePath()
                                            + ", does not exist");
        }
    }

    public void initialize()
    {
    // nothing to do
    }

    /**
     * <p>
     * invokeDeploy
     * </p>
     * 
     * @see org.apache.jetspeed.deployment.DeploymentEventListener#invokeDeploy(org.apache.jetspeed.deployment.DeploymentEvent)
     * @param event
     * @throws DeploymentException
     */
    public void invokeDeploy(DeploymentEvent event) throws DeploymentException
    {
        String fileName = event.getName();
        if (!fileName.endsWith(".jar") && !fileName.endsWith(".zip"))
        {
            return;
        }

        // get decorator configuration if available
        PropertiesConfiguration conf = getDecoratorConfiguration(event);
        // silently return if configuration not available, (assumes
        // probably not a decorator)
        if (conf == null)
        {
            return;
        }

        // process decorator by id
        String id = conf.getString("id");
        if (id == null)
        {
            throw new DeploymentException("Unable to deploy decorator, \"id\" attribute not defined in configuration");
        }
        
        log.info("Found decorator deployment archive " + id);

        try
        {
            // construct decorator deploy path
            String baseDeployPath = getBaseDeployPath(conf);
            String deployPath = baseDeployPath + File.separator + id;
            File deployPathFile = new File(deployPath);

            // undeploy decorator if it already exists and is a redeploy or
            // skip deployment if initial deployment
            if (deployPathFile.exists())
            {
                invokeUndeploy(deployPathFile);
            }

            // redeploy/deploy decorator w/o META_INF jar metadata
            log.info("Deploying decorator " + id + " to " + deployPath);
            JarExpander.expand(event.getDeploymentObject().getFile(), deployPathFile);
            File metaInf = new File(deployPathFile, "META-INF");
            if (metaInf.exists())
            {
                DirectoryHelper cleanup = new DirectoryHelper(metaInf);
                cleanup.remove();
                cleanup.close();
            }

            // detect language/country localized decorator components
            final List localeSpecificDeployPathsList = getLocaleSpecificDeployPaths(deployPathFile);

            // deploy individual locale specific decorator components
            Iterator deployPathsIter = localeSpecificDeployPathsList.iterator();
            while (deployPathsIter.hasNext())
            {
                File localeDeployPathFile = (File) deployPathsIter.next();

                // deploy to locale specific location
                File deployToPathFile = new File(baseDeployPath
                                                 + localeDeployPathFile.getPath().substring(deployPath.length())
                                                 + File.separator + id);
                log.info("Deploying locale specific decorator component to " + deployToPathFile.getPath());
                deployToPathFile.mkdirs();

                // deploy decorator components by moving from deployed decorator
                File[] filesToDeploy = localeDeployPathFile.listFiles(new FileFilter()
                {
                    public boolean accept(File pathname)
                    {
                        return !localeSpecificDeployPathsList.contains(pathname);
                    }
                });
                for (int i = 0; (i < filesToDeploy.length); i++)
                {
                    filesToDeploy[i].renameTo(new File(deployToPathFile, filesToDeploy[i].getName()));
                }
            }

            // cleanup locale specific deployment directories
            Iterator cleanupDeployPathsIter = localeSpecificDeployPathsList.iterator();
            while (cleanupDeployPathsIter.hasNext())
            {
                File cleanupLocaleDeployPathFile = (File) cleanupDeployPathsIter.next();
                if (cleanupLocaleDeployPathFile.exists())
                {
                    DirectoryHelper cleanup = new DirectoryHelper(cleanupLocaleDeployPathFile);
                    cleanup.remove();
                    cleanup.close();
                }
            }

            log.info("Decorator " + id + " deployed successfuly.");
            event.setStatus(DeploymentStatus.STATUS_OKAY);
        }
        catch (DeploymentException de)
        {
            throw de;
        }
        catch (Exception e)
        {
            throw new DeploymentException("Error deploying decorator " + id, e);
        }
    }

    /**
     * <p>
     * invokeUndeploy
     * </p>
     * 
     * @throws DeploymentException
     */
    public void invokeUndeploy(File deployPathFile) throws DeploymentException
    {

        if (deployPathFile == null || !deployPathFile.exists() ||
            !deployPathFile.isDirectory() || deployPathFile.getParentFile() == null ||
            !deployToDir.equals(deployPathFile.getParentFile().getParent()))
        {
            throw new DeploymentException("Cannot undeploy decorator at " + deployPathFile + ": invalid decorator path");
        }

        String id = deployPathFile.getName();

        try
        {
            // undeploy decorator
            log.info("Undeploying decorator " + id + " at " + deployPathFile.getAbsolutePath());

            // detect language/country localized decorator components
            final List localeSpecificDeployPathsList = getLocaleSpecificDeployPaths(deployPathFile.getParentFile());

            // undeploy individual locale specific decorator components depth first
            for (int i = localeSpecificDeployPathsList.size() - 1; i > -1; i--)
            {
                File localeDeployPathFile = new File((File) localeSpecificDeployPathsList.get(i), id);
                if (localeDeployPathFile.exists())
                {
                    log.info("Undeploying locale specific decorator component at " + localeDeployPathFile.getPath());
                    DirectoryHelper cleanup = new DirectoryHelper(localeDeployPathFile);
                    cleanup.remove();
                    cleanup.close();
                    localeDeployPathFile.getParentFile().delete();
                }
            }

            // now undeploy the decorator root itself
            DirectoryHelper cleanup = new DirectoryHelper(deployPathFile);
            cleanup.remove();
            cleanup.close();

            log.info("Decorator " + id + " undeployed successfuly.");
        }
        catch (Exception e)
        {
            throw new DeploymentException("Error undeploying decorator " + id, e);
        }
    }

    /**
     * <p>
     * getDecorationConfiguration
     * </p>
     * 
     * @param event @
     * @return configuration
     * @throws DeploymentException
     */
    private PropertiesConfiguration getDecoratorConfiguration(DeploymentEvent event) throws DeploymentException
    {
        InputStream stream = null;
        try
        {
            if (event.getDeploymentObject() == null)
            {
                return null;
            }
            stream = event.getDeploymentObject().getConfiguration("decorator.properties");
            if (stream == null)
            {
                return null;
            }
            else
            {
                PropertiesConfiguration configuration = new PropertiesConfiguration();
                configuration.load(stream);
                return configuration;
            }
        }
        catch (Exception e1)
        {
            throw new DeploymentException("Error reading decorator.properties from " + event.getPath(), e1);
        }
        finally
        {
            if (stream != null)
            {
                try
                {
                    stream.close();
                }
                catch (IOException e)
                {

                }
            }
        }
    }

    /**
     * <p>
     * getBaseDeployPath
     * </p>
     * 
     * @param configuration
     * @return base deploy path
     */
    private String getBaseDeployPath(PropertiesConfiguration configuration)
    {
        // construct decorator deploy base path
        String layoutType = configuration.getString("decorates", "generic");
        if (layoutType.equalsIgnoreCase("any"))
        {
            layoutType = "generic";
        }
        return deployToDir + File.separator + layoutType ;
    }

    /**
     * <p>
     * getLocaleSpecificDeployPaths
     * </p>
     * 
     * @param rootPath
     * @return locale paths list
     */
    private List getLocaleSpecificDeployPaths(File rootPath)
    {
        // detect language/country localized deploy paths
        List localeSpecificDeployPathsList = new ArrayList();
        File[] localeLanguageSpecificRoots = rootPath.listFiles(new FileFilter()
        {
            public boolean accept(File pathname)
            {
                // filter language code dirs, (assume length test is accurate enough)
                return (pathname.isDirectory() && (pathname.getName().length() == 2));
            }
        });
        for (int i = 0; (i < localeLanguageSpecificRoots.length); i++)
        {
            localeSpecificDeployPathsList.add(localeLanguageSpecificRoots[i]);
            File[] localeCountrySpecificPaths = localeLanguageSpecificRoots[i].listFiles(new FileFilter()
            {
                public boolean accept(File pathname)
                {
                    // filter country code dirs, (assume length test is accurate enough)
                    return (pathname.isDirectory() && (pathname.getName().length() == 2));
                }
            });
            for (int j = 0; (j < localeCountrySpecificPaths.length); j++)
            {
                localeSpecificDeployPathsList.add(localeCountrySpecificPaths[j]);
            }
        }
        return localeSpecificDeployPathsList;
    }
}