/*
 * 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.pluto.util.assemble.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.pluto.util.assemble.Assembler;

/** 
 * Utility class responsible for accepting a JarInputStream representing a web application archive,
 * iterating over each JarEntry in the input stream and assembling the WAR web.xml for portlet
 * deployment.
 */
public class JarStreamingAssembly {
    
    private static final Logger LOG = LoggerFactory.getLogger( JarStreamingAssembly.class );

    /**
     * Reads the source JarInputStream, copying entries to the destination JarOutputStream. 
     * The web.xml and portlet.xml are cached, and after the entire archive is copied 
     * (minus the web.xml) a re-written web.xml is generated and written to the 
     * destination JAR.
     * 
     * @param source the WAR source input stream
     * @param dest the WAR destination output stream
     * @param dispatchServletClass the name of the dispatch class
     * @throws IOException
     */
    public static void assembleStream(JarInputStream source, JarOutputStream dest, String dispatchServletClass) throws IOException {
        
        try {
            //Need to buffer the web.xml and portlet.xml files for the rewritting
            JarEntry servletXmlEntry = null;
            byte[] servletXmlBuffer = null;
            byte[] portletXmlBuffer = null;

            JarEntry originalJarEntry;                         
            
            //Read the source archive entry by entry
            while ((originalJarEntry = source.getNextJarEntry()) != null) {

                final JarEntry newJarEntry = smartClone(originalJarEntry);
                originalJarEntry = null;

                //Capture the web.xml JarEntry and contents as a byte[], don't write it out now or
                //update the CRC or length of the destEntry.
                if (Assembler.SERVLET_XML.equals(newJarEntry.getName())) {
                    servletXmlEntry = newJarEntry;
                    servletXmlBuffer = IOUtils.toByteArray(source);
                }
                
                //Capture the portlet.xml contents as a byte[]
                else if (Assembler.PORTLET_XML.equals(newJarEntry.getName())) {
                    portletXmlBuffer = IOUtils.toByteArray(source);
                    dest.putNextEntry(newJarEntry);
                    IOUtils.write(portletXmlBuffer, dest);
                }
                
                //Copy all other entries directly to the output archive
                else {
                    dest.putNextEntry(newJarEntry);
                    IOUtils.copy(source, dest);                    
                }      
                    
                dest.closeEntry();
                dest.flush();

            }

            // If no portlet.xml was found in the archive, skip the assembly step.
            if (portletXmlBuffer != null) {
                // container for assembled web.xml bytes
                final byte[] webXmlBytes;
                
                // Checks to make sure the web.xml was found in the archive
                if (servletXmlBuffer == null) {
                    throw new FileNotFoundException("File '" + Assembler.SERVLET_XML + "' could not be found in the source input stream.");
                }

                //Create streams of the byte[] data for the updater method
                final InputStream webXmlIn = new ByteArrayInputStream(servletXmlBuffer);
                final InputStream portletXmlIn = new ByteArrayInputStream(portletXmlBuffer);
                final ByteArrayOutputStream webXmlOut = new ByteArrayOutputStream(servletXmlBuffer.length);

                //Update the web.xml
                WebXmlStreamingAssembly.assembleStream(webXmlIn, portletXmlIn, webXmlOut, dispatchServletClass);
                IOUtils.copy( webXmlIn, webXmlOut );
                webXmlBytes = webXmlOut.toByteArray();
                
                //If no compression is being used (STORED) we have to manually update the size and crc
                if (servletXmlEntry.getMethod() == ZipEntry.STORED) {
                    servletXmlEntry.setSize(webXmlBytes.length);
                    final CRC32 webXmlCrc = new CRC32();
                    webXmlCrc.update(webXmlBytes);
                    servletXmlEntry.setCrc(webXmlCrc.getValue());
                }

                //write out the assembled web.xml entry and contents
                dest.putNextEntry(servletXmlEntry);
                IOUtils.write(webXmlBytes, dest);
                
                if ( LOG.isDebugEnabled() ) {
                    LOG.debug( "Jar stream " + source + " successfully assembled." );
                }
            } else {                
                if ( LOG.isDebugEnabled() ) {
                    LOG.debug( "No portlet XML file was found, assembly was not required." );
                }                

                //copy the original, unmodified web.xml entry to the destination
                dest.putNextEntry(servletXmlEntry);
                IOUtils.write(servletXmlBuffer, dest);
                
                if ( LOG.isDebugEnabled() ) {
                    LOG.debug( "Jar stream " + source + " successfully assembled." );
                }
            }            
            
        } finally {
            
            dest.flush();
            dest.close();
            
        }
    }
    
    private static JarEntry smartClone(JarEntry originalJarEntry) {
        final JarEntry newJarEntry = new JarEntry(originalJarEntry.getName());
        newJarEntry.setComment(originalJarEntry.getComment());
        newJarEntry.setExtra(originalJarEntry.getExtra());
        newJarEntry.setMethod(originalJarEntry.getMethod());
        newJarEntry.setTime(originalJarEntry.getTime());

        //Must set size and CRC for STORED entries
        if (newJarEntry.getMethod() == ZipEntry.STORED) {
            newJarEntry.setSize(originalJarEntry.getSize());
            newJarEntry.setCrc(originalJarEntry.getCrc());
        }

        return newJarEntry;
    }

}
