/*
 * 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.struts2.util.fs;

import org.apache.struts2.FileManager;
import org.apache.struts2.FileManagerFactory;
import org.apache.struts2.XWorkTestCase;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.apache.struts2.util.fs.JarEntryRevision;
import org.apache.struts2.util.fs.Revision;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.jar.Attributes;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

public class JarEntryRevisionTest extends XWorkTestCase {

    private FileManager fileManager;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        fileManager = container.getInstance(FileManagerFactory.class).getFileManager();
    }

    private String createJarFile(long time) throws Exception {
        Manifest manifest = new Manifest();
        manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
        Path jarPath = Paths.get(Thread.currentThread().getContextClassLoader()
                .getResource("xwork-jar.jar").toURI()).getParent();
        File jarFile = jarPath.resolve("JarEntryRevisionTest_testNeedsReloading.jar").toFile();
        FileOutputStream fos = new FileOutputStream(jarFile, false);
        JarOutputStream target = new JarOutputStream(fos, manifest);
        target.putNextEntry(new ZipEntry("org/apache/struts2/util/fs/"));
        ZipEntry entry = new ZipEntry("org/apache/struts2/util/fs/JarEntryRevisionTest.class");
        entry.setTime(time);
        target.putNextEntry(entry);
        InputStream source = getClass().getResourceAsStream("/org/apache/struts2/util/fs/JarEntryRevisionTest.class");
        IOUtils.copy(source, target);
        source.close();
        target.closeEntry();
        target.close();
        fos.close();

        return jarFile.toURI().toURL().toExternalForm();
    }

    public void testNeedsReloading() throws Exception {
        long now = System.currentTimeMillis();

        URL url = new URL("jar:" + createJarFile(now) + "!/org/apache/struts2/util/fs/JarEntryRevisionTest.class");
        Revision entry = JarEntryRevision.build(url, fileManager);
        assert entry != null;
        assertFalse(entry.needsReloading());

        createJarFile(now + 60000);
        assertTrue(entry.needsReloading());
    }

    public void testNeedsReloadingWithContainerProvidedURLConnection() throws Exception {
        long now = System.currentTimeMillis();

        URL url = new URL(null,
                "jar:" + createJarFile(now) + "!/org/apache/struts2/util/fs/JarEntryRevisionTest.class",
                new ContainerProvidedURLStreamHandler());
        Revision entry = JarEntryRevision.build(url, fileManager);
        assert entry != null;
        assertFalse(entry.needsReloading());

        createJarFile(now + 60000);
        assertTrue(entry.needsReloading());
    }

    public void testNeedsReloadingWithContainerProvidedURLConnectionEmptyProtocol() throws Exception {
        long now = System.currentTimeMillis();

        String targetUrlStr = createJarFile(now);
        if (targetUrlStr.startsWith("file:")) {
            targetUrlStr = targetUrlStr.substring(5);//emptying protocol; we expect framework will fix it
        }
        if (targetUrlStr.startsWith("/")) {
            targetUrlStr = targetUrlStr.substring(1);//we expect framework will fix it also
        }
        URL url = new URL(null,
                "zip:" + targetUrlStr + "!/org/apache/struts2/util/fs/JarEntryRevisionTest.class",
                new ContainerProvidedURLStreamHandler());
        Revision entry = JarEntryRevision.build(url, fileManager);
        assert entry != null;
        assertFalse(entry.needsReloading());

        createJarFile(now + 60000);
        assertTrue(entry.needsReloading());
    }

    @Override
    protected void tearDown() throws Exception {
        Path tmpFile = Files.createTempFile("jar_cache", null);
        Path tmpFolder = tmpFile.getParent();
        int count = FileUtils.listFiles(tmpFolder.toFile(), new WildcardFileFilter("jar_cache*"),
                null).size();
        if (tmpFile.toFile().delete()) {
            count--;
        }
        assertEquals(0, count);

        super.tearDown();
    }


    /**
     * WW-4901 Simulating container implementation of {@link URL#openConnection()}
     * @since 2.5.15
     */
    private class ContainerProvidedURLStreamHandler extends URLStreamHandler {

        @Override
        protected URLConnection openConnection(URL u) throws IOException {
            return new ContainerProvidedURLConnection(u);
        }
    }

    /**
     * WW-4901 Simulating container implementation of {@link URLConnection}
     * e.g. like IBM WebSphere com.ibm.ws.classloader.Handler$ClassLoaderURLConnection
     * @since 2.5.15
     */
    private class ContainerProvidedURLConnection extends URLConnection {

        ContainerProvidedURLConnection(URL url) {
            super(url);
        }

        @Override
        public void connect() throws IOException {
            throw new IllegalStateException("This is not expected (should not coupled to underlying implementation)");
        }
    }
}
