/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.web.deployers;

import java.io.IOException;
import java.util.List;

import org.jboss.deployers.spi.DeploymentException;
import org.jboss.deployers.spi.structure.ContextInfo;
import org.jboss.deployers.spi.structure.MetaDataType;
import org.jboss.deployers.vfs.plugins.structure.AbstractVFSArchiveStructureDeployer;
import org.jboss.deployers.vfs.spi.structure.StructureContext;
import org.jboss.vfs.VirtualFile;
import org.jboss.vfs.VirtualFileFilter;
import org.jboss.vfs.VisitorAttributes;
import org.jboss.vfs.util.SuffixMatchFilter;
import org.jboss.vfs.util.automount.Automounter;
import org.jboss.vfs.util.automount.MountOption;

/**
 * WARStructure.
 * 
 * @author <a href="adrian@jboss.com">Adrian Brock</a>
 * @author <a href="ales.justin@jboss.com">Ales Justin</a>
 * @version $Revision: 101688 $
 */
public class WARStructure extends AbstractVFSArchiveStructureDeployer
{
   /** The default filter which allows jars/jar directories */
   public static final VirtualFileFilter DEFAULT_WEB_INF_LIB_FILTER = new SuffixMatchFilter(".jar", VisitorAttributes.DEFAULT);
   
   /** The web-inf/lib filter */
   private VirtualFileFilter webInfLibFilter = DEFAULT_WEB_INF_LIB_FILTER;

   /** The web-inf/lib/[some-archive]/META-INF filter */
   private VirtualFileFilter webInfLibMetaDataFilter;

   /** Whether to include web-inf in the classpath */
   private boolean includeWebInfInClasspath;

   /**
    * Sets the default relative order 1000.
    *
    */
   public WARStructure()
   {
      setRelativeOrder(1000);
   }

   /**
    * Get the webInfLibFilter.
    * 
    * @return the webInfLibFilter.
    */
   public VirtualFileFilter getWebInfLibFilter()
   {
      return webInfLibFilter;
   }

   /**
    * Set the webInfLibFilter.
    * 
    * @param webInfLibFilter the webInfLibFilter.
    * @throws IllegalArgumentException for a null filter
    */
   public void setWebInfLibFilter(VirtualFileFilter webInfLibFilter)
   {
      if (webInfLibFilter == null)
         throw new IllegalArgumentException("Null filter");
      this.webInfLibFilter = webInfLibFilter;
   }

   /**
    * Get webInfLibMetaDataFilter
    *
    * @return the webInfLibMetaDataFilter
    */
   public VirtualFileFilter getWebInfLibMetaDataFilter()
   {
      return webInfLibMetaDataFilter;
   }

   /**
    * Set the webInfLibMetaDataFilter.
    *
    * @param webInfLibMetaDataFilter the webInfLibFilter.
    */
   public void setWebInfLibMetaDataFilter(VirtualFileFilter webInfLibMetaDataFilter)
   {
      this.webInfLibMetaDataFilter = webInfLibMetaDataFilter;
   }

   /**
    * Should we include web-inf in classpath.
    *
    * @param includeWebInfInClasspath the include web-inf flag
    */
   public void setIncludeWebInfInClasspath(boolean includeWebInfInClasspath)
   {
      this.includeWebInfInClasspath = includeWebInfInClasspath;
   }

   public boolean doDetermineStructure(StructureContext structureContext) throws DeploymentException
   {
      ContextInfo context = null;
      VirtualFile file = structureContext.getFile();
      try
      {
         boolean trace = log.isTraceEnabled();

         // the WEB-INF
         VirtualFile webinf;
         
         // We require either a WEB-INF or the name ends in .war
         if (hasValidSuffix(file.getName()) == false)
         {
           webinf = file.getChild("WEB-INF");
           if (webinf.exists())
           {
              if (trace)
                 log.trace("... ok - directory has a WEB-INF subdirectory");
           }
           else
           {
              if (trace)
                 log.trace("... no - doesn't look like a war and no WEB-INF subdirectory.");
              return false;
           }
         }
         else if (trace)
         {
            log.trace("... ok - name ends in .war.");
         } 

         // Check for a META-INF for metadata
         // FIXME: This is not spec legal, descriptors could be loaded from this location
         String[] metaDataLocations = new String[]{"WEB-INF", "WEB-INF/classes/META-INF"};
         // Create a context for this war file and all its metadata locations
         context = createContext(structureContext, metaDataLocations);

         // Add all children as locations for TLDs (except classes and lib), recursively
         webinf = file.getChild("WEB-INF");
         if (webinf.exists())
         {
            List<VirtualFile> children = webinf.getChildren();
            for (VirtualFile child : children)
            {
               if (!isLeaf(child) && (!"lib".equals(child.getName())) && (!"classes".equals(child.getName())))
               {
                  addMetaDataPath(structureContext, context, "WEB-INF/" + child.getName(), MetaDataType.ALTERNATIVE);
                  addPathsRecursively(structureContext, context, child, "WEB-INF/" + child.getName());
               }
            }
         }

         // Check for jars in WEB-INF/lib
         List<VirtualFile> archives = null;
         try
         {
            VirtualFile webinfLib = file.getChild("WEB-INF/lib");
            if (webinfLib.exists())
            {
               archives = webinfLib.getChildren(webInfLibFilter);
               // Add the jars' META-INF for metadata
               for (VirtualFile jar : archives)
               {
                  Automounter.mount(file, jar);
                  // either same as plain lib filter, null or accepts the jar
                  if (webInfLibMetaDataFilter == null || webInfLibMetaDataFilter == webInfLibFilter || webInfLibMetaDataFilter.accepts(jar))
                  {
                     VirtualFile metaInf = jar.getChild("META-INF");
                     if (metaInf.exists() && !isLeaf(metaInf))
                     {
                        addMetaDataPath(structureContext, context, "WEB-INF/lib/" + jar.getName() + "/META-INF", MetaDataType.ALTERNATIVE);
                        List<VirtualFile> children = metaInf.getChildren();
                        for (VirtualFile child : children)
                        {
                           if (!isLeaf(child) && (!"resources".equals(child.getName())))
                           {
                              addMetaDataPath(structureContext, context, "WEB-INF/lib/" + jar.getName() + "/META-INF/" + child.getName(), MetaDataType.ALTERNATIVE);
                              addPathsRecursively(structureContext, context, child, "WEB-INF/lib/" + jar.getName() + "/META-INF/" + child.getName());
                           }
                        }
                     }
                  }
               }
            }
         }
         catch (IOException e)
         {
            log.warn("Exception looking for WEB-INF/lib, " + file.getPathName() + ", " + e);
         }
         
         // Add the war manifest classpath entries
         addClassPath(structureContext, file, false, true, context);

         // Check for WEB-INF/classes
         VirtualFile classes = file.getChild("WEB-INF/classes");
         // Add WEB-INF/classes if present
         if (classes.exists())
            addClassPath(structureContext, classes, true, false, context);
         else if (trace)
            log.trace("No WEB-INF/classes for: " + file.getPathName());

         // and the top level jars in WEB-INF/lib
         if (archives != null)
         {
            for (VirtualFile jar : archives)
               addClassPath(structureContext, jar, true, true, context);
         }
         else if (trace)
         {
            log.trace("No WEB-INF/lib for: " + file.getPathName());
         }

         // do we include WEB-INF in classpath
         if (includeWebInfInClasspath && webinf != null)
         {
            addClassPath(structureContext, webinf, true, false, context);
         }

         // There are no subdeployments for wars
         return true;
      }
      catch (Exception e)
      {
         // Remove the invalid context
         if (context != null)
            structureContext.removeChild(context);

         throw DeploymentException.rethrowAsDeploymentException("Error determining structure: " + file.getName(), e);
      }
   }

   @Override
   protected boolean hasValidSuffix(String name)
   {
      return name.toLowerCase().endsWith(".war");
   }
   

   @Override
   protected void performMount(VirtualFile file) throws IOException
   {
      Automounter.mount(file, MountOption.EXPANDED, MountOption.COPY);
   }

   protected void addPathsRecursively(StructureContext context, ContextInfo info, VirtualFile parent, String path) throws IOException
   {
      List<VirtualFile> children = parent.getChildren();
      for (VirtualFile child : children)
      {
         if (!isLeaf(child))
         {
            String newPath = path + "/" + child.getName();
            addMetaDataPath(context, info, newPath, MetaDataType.ALTERNATIVE);
            addPathsRecursively(context, info, child, newPath);
         }
      }
   }
   
}
