/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.elasticsearch.bootstrap;

import org.apache.lucene.util.TestSecurityManager;
import org.elasticsearch.bootstrap.Bootstrap;
import org.elasticsearch.bootstrap.ESPolicy;
import org.elasticsearch.bootstrap.Security;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.logging.Loggers;

import java.io.FilePermission;
import java.net.URL;
import java.nio.file.Path;
import java.security.Permissions;
import java.security.Policy;
import java.util.Map;
import java.util.Objects;

import static com.carrotsearch.randomizedtesting.RandomizedTest.systemPropertyAsBoolean;

/** 
 * Initializes natives and installs test security manager
 * (init'd early by base classes to ensure it happens regardless of which
 * test case happens to be first, test ordering, etc). 
 * <p>
 * The idea is to mimic as much as possible what happens with ES in production
 * mode (e.g. assign permissions and install security manager the same way)
 */
public class BootstrapForTesting {
    
    // TODO: can we share more code with the non-test side here
    // without making things complex???

    static {
        // make sure java.io.tmpdir exists always (in case code uses it in a static initializer)
        Path javaTmpDir = PathUtils.get(Objects.requireNonNull(System.getProperty("java.io.tmpdir"),
                                                               "please set ${java.io.tmpdir} in pom.xml"));
        try {
            Security.ensureDirectoryExists(javaTmpDir);
        } catch (Exception e) {
            throw new RuntimeException("unable to create test temp directory", e);
        }

        // just like bootstrap, initialize natives, then SM
        Bootstrap.initializeNatives(javaTmpDir, true, true, true);

        // initialize probes
        Bootstrap.initializeProbes();
        
        // check for jar hell
        try {
            JarHell.checkJarHell();
        } catch (Exception e) {
            throw new RuntimeException("found jar hell in test classpath", e);
        }

        // install security manager if requested
        if (systemPropertyAsBoolean("tests.security.manager", true)) {
            try {
                Security.setCodebaseProperties();
                // initialize paths the same exact way as bootstrap
                Permissions perms = new Permissions();
                // add permissions to everything in classpath
                for (URL url : JarHell.parseClassPath()) {
                    Path path = PathUtils.get(url.toURI());
                    // resource itself
                    perms.add(new FilePermission(path.toString(), "read,readlink"));
                    // classes underneath
                    perms.add(new FilePermission(path.toString() + path.getFileSystem().getSeparator() + "-", "read,readlink"));

                    // crazy jython...
                    String filename = path.getFileName().toString();
                    if (filename.contains("jython") && filename.endsWith(".jar")) {
                        // just enough so it won't fail when it does not exist
                        perms.add(new FilePermission(path.getParent().toString(), "read,readlink"));
                        perms.add(new FilePermission(path.getParent().resolve("Lib").toString(), "read,readlink"));
                    }
                }
                // java.io.tmpdir
                Security.addPath(perms, "java.io.tmpdir", javaTmpDir, "read,readlink,write,delete");
                // custom test config file
                if (Strings.hasLength(System.getProperty("tests.config"))) {
                    perms.add(new FilePermission(System.getProperty("tests.config"), "read,readlink"));
                }
                // jacoco coverage output file
                if (Boolean.getBoolean("tests.coverage")) {
                    Path coverageDir = PathUtils.get(System.getProperty("tests.coverage.dir"));
                    perms.add(new FilePermission(coverageDir.resolve("jacoco.exec").toString(), "read,write"));
                    // in case we get fancy and use the -integration goals later:
                    perms.add(new FilePermission(coverageDir.resolve("jacoco-it.exec").toString(), "read,write"));
                }

                final Policy policy;
                // if its a plugin with special permissions, we use a wrapper policy impl to try
                // to simulate what happens with a real distribution
                String artifact = System.getProperty("tests.artifact");
                // in case we are running from the IDE:
                if (artifact == null && System.getProperty("tests.maven") == null) {
                    // look for plugin classname as a resource to determine what project we are.
                    // while gross, this will work with any IDE.
                    for (Map.Entry<String,String> kv : Security.SPECIAL_PLUGINS.entrySet()) {
                        String resource = kv.getValue().replace('.', '/') + ".class";
                        if (BootstrapForTesting.class.getClassLoader().getResource(resource) != null) {
                            artifact = kv.getKey();
                            break;
                        }
                    }
                }
                String pluginProp = Security.getPluginProperty(artifact);
                if (pluginProp != null) {
                    policy = new MockPluginPolicy(perms, pluginProp);
                } else {
                    policy = new ESPolicy(perms);
                }
                Policy.setPolicy(policy);
                System.setSecurityManager(new TestSecurityManager());
                Security.selfTest();

                if (pluginProp != null) {
                    // initialize the plugin class, in case it has one-time hacks (unit tests often won't do this)
                    String clazz = Security.getPluginClass(artifact);
                    Class.forName(clazz);
                }
            } catch (Exception e) {
                throw new RuntimeException("unable to install test security manager", e);
            }
        }
    }

    // does nothing, just easy way to make sure the class is loaded.
    public static void ensureInitialized() {}
}

@@ -5065,32 +5065,311 @@
                %7D
+%0A                // intellij hack: intellij test runner wants setIO and will%0A                // screw up all test logging without it!%0A                if (System.getProperty(%22tests.maven%22) == null) %7B%0A                    perms.add(new RuntimePermission(%22setIO%22));%0A                %7D
 %0A%0A              

Fix intellij test logging