/*
 *    Copyright 2013 Adobe
 *
 *   Licensed 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 com.adobe.granite.maven.scq;

import static java.util.Arrays.asList;
import static com.google.inject.Guice.createInjector;
import static com.google.inject.Key.get;
import static com.google.inject.name.Names.named;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import com.adobe.granite.maven.scq.di.SecureCQComponentsModule;
import com.cognifide.securecq.AbstractTest;
import com.cognifide.securecq.Configuration;
import com.cognifide.securecq.TestResult;
import com.cognifide.securecq.markers.AuthorTest;
import com.cognifide.securecq.markers.DispatcherTest;
import com.cognifide.securecq.markers.PublishTest;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;

/**
 * Performs the SecureCQ test analysis.
 */
@Mojo(
    name = "securecq",
    defaultPhase = LifecyclePhase.TEST,
    threadSafe = true,
    requiresProject = false
)
public final class SecureCQMojo extends AbstractMojo {

    /**
     * The {@code author} CQ instance URL.
     */
    @Parameter(property = "scq.url.author", defaultValue = "http://localhost:4502")
    private String authorUrl;

    /**
     * The {@code publish} CQ instance URL.
     */
    @Parameter(property = "scq.url.publish", defaultValue = "")
    private String publishUrl;

    /**
     * The {@code dispatcher} CQ instance URL.
     */
    @Parameter(property = "scq.url.dispatcher", defaultValue = "")
    private String dispatcherUrl;

    /**
     * The list of tests have to be performed, {@code config-validation}, {@code default-passwords}, {@code dispatcher-access},
     * {@code shindig-proxy}, {@code etc-tools}, {@code content-grabbing}, {@code feed-selector}, {@code wcm-debug}, {@code webdav},
     * {@code webdav}, {@code geometrixx} and {@code redundant-selectors} by default.
     */
    @Parameter
    private String[] enabledTests;

    public void execute() throws MojoExecutionException, MojoFailureException {
        // setup tests
        Injector injector = createInjector(new SecureCQComponentsModule(authorUrl, publishUrl, dispatcherUrl));

        // discover all available predefined tests
        if (enabledTests == null || enabledTests.length == 0) {
            List<String> boundTests = new LinkedList<String>();

            TypeLiteral<AbstractTest> abstractTestTypeLiteral = new TypeLiteral<AbstractTest>(){};

            for (Entry<Key<?>, Binding<?>> binding : injector.getAllBindings().entrySet()) {
                Key<?> bindingKey = binding.getKey();

                if (abstractTestTypeLiteral.equals(bindingKey.getTypeLiteral())
                        && bindingKey.getAnnotation() != null
                        && Named.class.isAssignableFrom(bindingKey.getAnnotationType())) {
                    boundTests.add(((Named) bindingKey.getAnnotation()).value());
                }
            }

            enabledTests = boundTests.toArray(new String[boundTests.size()]);
        }

        getLog().debug("Performing tests: " + asList(enabledTests));

        // perform tests

        boolean success = true;

        for (String enabledTest : enabledTests) {
            AbstractTest test = injector.getInstance(get(AbstractTest.class, named(enabledTest)));

            if (test != null) {
                getLog().info("Performing security check '" + enabledTest + "'...");

                // configuration exists at that point
                Configuration configuration = injector.getInstance(get(Configuration.class, named(enabledTest)));

                // putting the `success` flag BEFORE, if false, doesn't evaluate the test!
                success = performTest(enabledTest, test, configuration) && success;
            } else {
                getLog().warn("Test '" + enabledTest + "' does not exist in this context, ignored it.");
            }
        }

        if (success) {
            getLog().info("Congratulations, all SequreCQ tests passed!");
        } else {
            throw new MojoFailureException("SequreCQ detected secutity vulnerabilities in your instances, see the log for details.");
        }
    }

    private boolean performTest(String componentName, AbstractTest test, Configuration configuration) throws MojoExecutionException {
        try {
            test.test();

            if (TestResult.DISABLED == test.getResult()) {
                if (test instanceof DispatcherTest) {
                    logDisabledTest(componentName, "authorUrl", "scq.url.author", authorUrl);
                } else if (test instanceof PublishTest) {
                    logDisabledTest(componentName, "publishUrl", "scq.url.publish", publishUrl);
                } else if (test instanceof AuthorTest) {
                    logDisabledTest(componentName, "dispatcherUrl", "scq.url.dispatcher", dispatcherUrl);
                } else {
                    getLog().info("'" + componentName + "' is disabled, skipping it - It may be possible that you haven't specified one of author/publish/dispatcher URL, please refere to plugin documentation.");
                }

                return true;
            }

            getLog().info("'" + componentName + "' result: " + test.getResult());

            if (!test.getErrorMessages().isEmpty()) {
                getLog().warn("'" + componentName + "' detected some failures:");

                for (String message : test.getErrorMessages()) {
                    getLog().warn(" - " + message);
                }
            }

            if (!test.getInfoMessages().isEmpty() && !"true".equals(configuration.getStringValue("hidePassed", "false"))) {
                getLog().info("'" + componentName + "' passed tests:");

                for (String message : test.getInfoMessages()) {
                    getLog().info(" - " + message);
                }
            }

            return TestResult.OK == test.getResult();
        } catch (IOException e) {
            throw new MojoExecutionException("An error occurred while loading test '"
                                             + componentName
                                             + "' configuration, see nested exceptions", e);
        }
    }

    private void logDisabledTest(String componentName, String parameterName, String propertyName, String instanceUrl) {
        getLog().info("Security check '"
                      + componentName
                      + "' is disabled, please make sure you have correctly set the <"
                      + parameterName
                      + " /> configuration property OR you have passed the -D"
                      + parameterName
                      + "=http://<host>:<port> property AND that instance located on '"
                      + instanceUrl
                      + "' is active and reachable.");
    }

}

@@ -1636,16 +1636,65 @@
 inding;%0A
+import com.google.inject.ConfigurationException;%0A
 import c
@@ -4218,24 +4218,119 @@
 ledTests) %7B%0A
+            getLog().info(%22Discovering test '%22 + enabledTest + %22'...%22);%0A%0A            try %7B%0A    
             
@@ -4421,40 +4421,8 @@
 );%0A%0A
-            if (test != null) %7B%0A
     
@@ -4853,36 +4853,64 @@
 ;%0A            %7D 
-else
+catch (ConfigurationException e)
  %7B%0A             

handle the case when a test that does not exist in the context is requested and the injector is not able to provide