/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file 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.as.test.integration.logging.perdeploy;


import java.io.InputStream;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.Deque;

import org.jboss.arquillian.container.test.api.Deployer;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.OperateOnDeployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.jboss.as.arquillian.api.ServerSetup;
import org.jboss.as.arquillian.container.ManagementClient;
import org.jboss.as.controller.client.helpers.Operations;
import org.jboss.as.test.integration.logging.util.AbstractLoggingTest;
import org.jboss.as.test.integration.logging.util.LoggingBean;
import org.jboss.as.test.integration.management.base.AbstractMgmtServerSetupTask;
import org.jboss.dmr.ModelNode;
import org.jboss.osgi.metadata.ManifestBuilder;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Note that the servlet uses a reflection hack to get the size of the log context map. This is fragile and may break,
 * but should be rather obvious if it does.
 * <p/>
 * This tests that after a undeploy the {@link org.jboss.logmanager.LogContext log contexts} and class loaders are
 * cleaned up during an undeploy. Attempts to find leaking class loaders.
 *
 * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
 */
@ServerSetup(LogContextTestCase.LoggingProfileSetup.class)
@RunWith(Arquillian.class)
public class LogContextTestCase extends AbstractLoggingTest {
    private static final String LOG_FILE_NAME = "log-context-profile-test.log";

    @Deployment(name = "logcontext", testable = false)
    public static WebArchive createDeployment() {
        return logContextDeployment("logcontext-test.war");
    }

    @Deployment(name = "logging", testable = false, managed = false)
    public static WebArchive createLoggingDeployment() {
        return loggingDeployment("logging-test.war")
                .addAsManifestResource(LoggingBean.class.getPackage(),
                        "logging.properties", "logging.properties");
    }

    @Deployment(name = "logging-ear", testable = false, managed = false)
    public static EnterpriseArchive createEarDeployment() {
        return ShrinkWrap.create(EnterpriseArchive.class, "logging.ear")
                .addAsManifestResource(LoggingBean.class.getPackage(),
                        "logging.properties", "logging.properties")
                .setManifest(new Asset() {
                    public InputStream openStream() {
                        ManifestBuilder builder = ManifestBuilder.newInstance();
                        builder.addManifestHeader("Dependencies", "org.jboss.logmanager, org.jboss.as.logging");
                        return builder.openStream();
                    }
                })
                .addAsModules(logContextDeployment("logcontext-in-ear.war"), loggingDeployment("logging-in-ear.war"));
    }

    @Deployment(name = "logging-profile", testable = false, managed = false)
    public static WebArchive createLoggingProfileDeployment() {
        return loggingDeployment("logging-profile-test.war")
                .setManifest(new Asset() {
                    public InputStream openStream() {
                        ManifestBuilder builder = ManifestBuilder.newInstance();
                        builder.addManifestHeader("Logging-Profile", "test");
                        return builder.openStream();
                    }
                });
    }

    @Deployment(name = "logging-profile-ear", testable = false, managed = false)
    public static EnterpriseArchive createEarProfileDeployment() {
        return ShrinkWrap.create(EnterpriseArchive.class, "logging-profile.ear")
                .addAsManifestResource(LoggingBean.class.getPackage(),
                        "logging.properties", "logging.properties")
                .setManifest(new Asset() {
                    public InputStream openStream() {
                        ManifestBuilder builder = ManifestBuilder.newInstance();
                        builder.addManifestHeader("Dependencies", "org.jboss.logmanager, org.jboss.as.logging");
                        builder.addManifestHeader("Logging-Profile", "test");
                        return builder.openStream();
                    }
                })
                .addAsModules(logContextDeployment("logcontext-profile-in-ear.war"), loggingDeployment("logging-profile-in-ear.war"));
    }

    @ArquillianResource
    public Deployer deployer;

    @Test
    @OperateOnDeployment("logcontext")
    public void contextTest(@ArquillianResource(LogContextHackServlet.class) final URL url) throws Exception {
        final String u = UrlBuilder.of(url, LogContextHackServlet.SERVLET_URL).build();
        String result = performCall(u);
        Assert.assertEquals("No LogContexts should exist in WAR deployment", 0, Integer.valueOf(result).intValue());
        deployer.deploy("logging");
        result = performCall(u);
        Assert.assertEquals("One LogContexts should exist in WAR deployment", 1, Integer.valueOf(result).intValue());
        deployer.undeploy("logging");
        result = performCall(u);
        Assert.assertEquals("No LogContexts should exist in WAR deployment", 0, Integer.valueOf(result).intValue());

        // Deploy an EAR
        deployer.deploy("logging-ear");
        result = performCall(u);
        Assert.assertEquals("Three LogContexts should exist in EAR deployment", 3, Integer.valueOf(result).intValue());
        deployer.undeploy("logging-ear");
        result = performCall(u);
        Assert.assertEquals("No LogContexts should exist in EAR deployment", 0, Integer.valueOf(result).intValue());

        // Deploy with a profile
        deployer.deploy("logging-profile");
        result = performCall(u);
        Assert.assertEquals("One LogContexts should exist in WAR deployment with logging-profile", 1, Integer.valueOf(result).intValue());
        deployer.undeploy("logging-profile");
        result = performCall(u);
        Assert.assertEquals("No LogContexts should exist in WAR deployment with logging-profile", 0, Integer.valueOf(result).intValue());

        // Deploy an EAR with a profile
        deployer.deploy("logging-profile-ear");
        result = performCall(u);
        Assert.assertEquals("Three LogContexts should exist in EAR deployment with logging-profile", 3, Integer.valueOf(result).intValue());
        deployer.undeploy("logging-profile-ear");
        result = performCall(u);
        Assert.assertEquals("No LogContexts should exist in EAR deployment with logging-profile", 0, Integer.valueOf(result).intValue());
    }

    private static WebArchive loggingDeployment(final String name) {
        return ShrinkWrap
                .create(WebArchive.class, name)
                .addClasses(LoggingBean.class);
    }

    private static WebArchive logContextDeployment(final String name) {
        return ShrinkWrap
                .create(WebArchive.class, name)
                .addClasses(LogContextHackServlet.class)
                .setManifest(new Asset() {
                    public InputStream openStream() {
                        ManifestBuilder builder = ManifestBuilder.newInstance();
                        builder.addManifestHeader("Dependencies", "org.jboss.logmanager, org.jboss.as.logging");
                        return builder.openStream();
                    }
                });
    }

    static class LoggingProfileSetup extends AbstractMgmtServerSetupTask {
        private final Deque<ModelNode> tearDownOps;

        public LoggingProfileSetup() {
            tearDownOps = new ArrayDeque<ModelNode>();
        }

        @Override
        protected void doSetup(final ManagementClient managementClient) throws Exception {
            // Setup a logging-profile
            final ModelNode loggingProfileAddress = AddressBuilder.create()
                    .add("logging-profile", "test")
                    .build();

            // Add the profile and add a remove operation
            ModelNode op = Operations.createAddOperation(loggingProfileAddress);
            executeOperation(op);
            tearDownOps.addFirst(Operations.createRemoveOperation(loggingProfileAddress));

            final ModelNode fileHandlerAddress = AddressBuilder.create(loggingProfileAddress)
                    .add("file-handler", "test-handler")
                    .build();
            op = Operations.createAddOperation(fileHandlerAddress);
            op.get("append").set("true");
            final ModelNode file = op.get("file");
            file.get("relative-to").set("jboss.server.log.dir");
            file.get("path").set(LOG_FILE_NAME);
            op.get("formatter").set("[test-profile] %d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n");
            executeOperation(op);
            tearDownOps.addFirst(Operations.createRemoveOperation(fileHandlerAddress));

            final ModelNode rootLoggerAddress = AddressBuilder.create(loggingProfileAddress)
                    .add("root-logger", "ROOT")
                    .build();
            op = Operations.createAddOperation(rootLoggerAddress);
            op.get("level").set("INFO");
            final ModelNode handlers = op.get("handlers").setEmptyList();
            handlers.add("test-handler");
            executeOperation(op);
            tearDownOps.addFirst(Operations.createRemoveOperation(rootLoggerAddress));
        }

        @Override
        public void tearDown(final ManagementClient managementClient, final String containerId) throws Exception {
            ModelNode op;
            while ((op = tearDownOps.poll()) != null) {
                try {
                    executeOperation(op);
                } catch (Exception ignore) {
                }
            }
        }
    }

}
