co.cask.cdap.internal.app.runtime.batch.distributed.ContainerLauncherGenerator.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.internal.app.runtime.batch.distributed.ContainerLauncherGenerator.java

Source

/*
 * Copyright  2015 Cask Data, Inc.
 *
 * 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 co.cask.cdap.internal.app.runtime.batch.distributed;

import co.cask.cdap.internal.asm.Methods;
import com.google.common.base.Preconditions;
import com.google.common.io.OutputSupplier;
import com.google.common.io.Resources;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URL;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;

/**
 * Helper class to generate main classes for MapReduce processes using ASM. Those classes need to be generated
 * instead of being part of source code is to avoid infinite recursion since we need classes to be of the same
 * class names as the classes in MapReduce (e.g. MRAppMaster) in order to intercept the main() method call.
 */
public final class ContainerLauncherGenerator {

    /**
     * Generates a JAR file for launching MapReduce containers. The generated jar contains three classes inside
     *
     * <ul>
     *   <li>{@link org.apache.hadoop.mapreduce.v2.app.MRAppMaster}</li>
     *   <li>{@link org.apache.hadoop.mapred.YarnChild YarnChild}</li>
     *   <li>{@link MapReduceContainerLauncher}</li>
     * </ul>
     *
     * @see MapReduceContainerLauncher
     */
    public static void generateLauncherJar(String launcherClassPath, String classLoaderName,
            OutputSupplier<? extends OutputStream> outputSupplier) throws IOException {
        try (JarOutputStream output = new JarOutputStream(outputSupplier.getOutput())) {
            generateLauncherClass(launcherClassPath, classLoaderName,
                    "org.apache.hadoop.mapreduce.v2.app.MRAppMaster", output);
            generateLauncherClass(launcherClassPath, classLoaderName, "org.apache.hadoop.mapred.YarnChild", output);

            // Includes the launcher class in the JAR as well. No need to trace dependency as the launcher
            // class must be dependency free.
            String containerLauncherName = Type.getInternalName(MapReduceContainerLauncher.class) + ".class";
            output.putNextEntry(new JarEntry(containerLauncherName));
            URL launcherURL = ContainerLauncherGenerator.class.getClassLoader().getResource(containerLauncherName);

            // Can never be null
            Preconditions.checkState(launcherURL != null);
            Resources.copy(launcherURL, output);
        }
    }

    /**
     * Generates a JAR file that contains a class with a static main method.
     *
     * @param mainClassName Name of the generated class
     * @param mainDelegatorClass the actual class that the main method will delegate to
     * @param outputSupplier the {@link OutputSupplier} for the jar file
     */
    public static void generateLauncherJar(String mainClassName, Class<?> mainDelegatorClass,
            OutputSupplier<? extends OutputStream> outputSupplier) throws IOException {
        try (JarOutputStream output = new JarOutputStream(outputSupplier.getOutput())) {
            generateMainClass(mainClassName, Type.getType(mainDelegatorClass), output);
        }
    }

    /**
     * Generates the bytecode for a main class and writes to the given {@link JarOutputStream}.
     * The generated class looks like this:
     *
     * <pre>{@code
     * class className {
     *   public static void main(String[] args) {
     *     MapReduceContainerLauncher.launch(launcherClassPath, classLoaderName, className, args);
     *   }
     * }
     * }
     * </pre>
     *
     * The {@code launcherClassPath}, {@code classLoaderName} and {@code className} are represented as
     * string literals in the generated class.
     */
    private static void generateLauncherClass(String launcherClassPath, String classLoaderName, String className,
            JarOutputStream output) throws IOException {
        String internalName = className.replace('.', '/');

        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, internalName, null,
                Type.getInternalName(Object.class), null);

        Method constructor = Methods.getMethod(void.class, "<init>");

        // Constructor
        // MRAppMaster()
        GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, constructor, null, null, classWriter);

        mg.loadThis();
        mg.invokeConstructor(Type.getType(Object.class), constructor);
        mg.returnValue();
        mg.endMethod();

        // Main method.
        // public static void main(String[] args) {
        //   MapReduceContainerLauncher.launch(launcherClassPath, classLoaderName, className, args);
        // }
        Method mainMethod = Methods.getMethod(void.class, "main", String[].class);
        mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, mainMethod, null,
                new Type[] { Type.getType(Exception.class) }, classWriter);

        mg.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class));
        mg.visitLdcInsn("Launch class " + className);
        mg.invokeVirtual(Type.getType(PrintStream.class), Methods.getMethod(void.class, "println", String.class));

        // The Launcher classpath, classloader name and main classname are stored as string literal in the generated class
        mg.visitLdcInsn(launcherClassPath);
        mg.visitLdcInsn(classLoaderName);
        mg.visitLdcInsn(className);
        mg.loadArg(0);
        mg.invokeStatic(Type.getType(MapReduceContainerLauncher.class),
                Methods.getMethod(void.class, "launch", String.class, String.class, String.class, String[].class));
        mg.returnValue();
        mg.endMethod();

        classWriter.visitEnd();

        output.putNextEntry(new JarEntry(internalName + ".class"));
        output.write(classWriter.toByteArray());
    }

    /**
     * Generates a class that has a static main method which delegates the call to a static method in the given delegator
     * class with method signature {@code public static void launch(String className, String[] args)}
     *
     * @param className the classname of the generated class
     * @param mainDelegator the class to delegate the main call to
     * @param output for writing the generated bytes
     */
    private static void generateMainClass(String className, Type mainDelegator, JarOutputStream output)
            throws IOException {
        String internalName = className.replace('.', '/');

        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, internalName, null,
                Type.getInternalName(Object.class), null);

        // Generate the default constructor, which just call super()
        Method constructor = Methods.getMethod(void.class, "<init>");
        GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, constructor, null, null, classWriter);
        mg.loadThis();
        mg.invokeConstructor(Type.getType(Object.class), constructor);
        mg.returnValue();
        mg.endMethod();

        // Generate the main method
        // public static void main(String[] args) {
        //   System.out.println("Launch class .....");
        //   <MainDelegator>.launch(<className>, args);
        // }
        Method mainMethod = Methods.getMethod(void.class, "main", String[].class);
        mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, mainMethod, null,
                new Type[] { Type.getType(Exception.class) }, classWriter);

        mg.getStatic(Type.getType(System.class), "out", Type.getType(PrintStream.class));
        mg.visitLdcInsn("Launch class " + className + " by calling " + mainDelegator.getClassName() + ".launch");
        mg.invokeVirtual(Type.getType(PrintStream.class), Methods.getMethod(void.class, "println", String.class));

        // The main classname is stored as string literal in the generated class
        mg.visitLdcInsn(className);
        mg.loadArg(0);
        mg.invokeStatic(mainDelegator, Methods.getMethod(void.class, "launch", String.class, String[].class));
        mg.returnValue();
        mg.endMethod();

        classWriter.visitEnd();

        output.putNextEntry(new JarEntry(internalName + ".class"));
        output.write(classWriter.toByteArray());
    }

    private ContainerLauncherGenerator() {
    }
}