co.cask.cdap.internal.app.deploy.SandboxConfigurator.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.internal.app.deploy.SandboxConfigurator.java

Source

/*
 * Copyright  2014 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.deploy;

import co.cask.cdap.app.deploy.ConfigResponse;
import co.cask.cdap.app.deploy.Configurator;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.io.Files;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;

import java.io.File;

/**
 * SandboxConfigurator spawns a seperate JVM to run configuration of an Application.
 * <p>
 * This class is responsible for starting the process of generating configuration
 * by passing in the JAR file of Application be configured by a seperate JVM.
 * </p>
 *
 * @see InMemoryConfigurator
 */
public final class SandboxConfigurator implements Configurator {
    /**
     * Prefix of temporary file.
     */
    private static final String PREFIX = "app-specification";
    /**
     * Extension of temporary.
     */
    private static final String EXT = ".json";

    /**
     * Name of JAR file.
     */
    private final File jarFilename;

    /**
     * Sandbox process.
     */
    private Process process;

    /**
     * Constructor.
     *
     * @param jarFilename Name of the JAR file.
     */
    public SandboxConfigurator(File jarFilename) {
        Preconditions.checkNotNull(jarFilename);
        this.jarFilename = jarFilename;
    }

    /**
     * Helper for simplifying creating {@link SandboxConfigurator}.
     *
     * @param jarFilename Name of the file.
     * @return An instance of {@link ListenableFuture}
     */
    public static ListenableFuture<ConfigResponse> config(File jarFilename) {
        SandboxConfigurator sc = new SandboxConfigurator(jarFilename);
        return sc.config();
    }

    /**
     * Runs the <code>Application.configure()</code> in a sandbox JVM
     * with high level of security.
     *
     * @return An instance of {@link ListenableFuture}
     */
    @Override
    public ListenableFuture<ConfigResponse> config() {
        final SettableFuture<ConfigResponse> result = SettableFuture.create();
        final File outputFile;

        try {
            outputFile = File.createTempFile(PREFIX, EXT);

            // Run the command in seperate JVM.
            process = Runtime.getRuntime().exec(getCommand(outputFile));

            // Add future to handle the case when the future is cancelled.
            // OnSuccess, we don't do anything other than cleaning the output.
            // onFailure, we make sure that process is destroyed.
            Futures.addCallback(result, new FutureCallback<ConfigResponse>() {

                private void deleteOutput() {
                    if (outputFile.exists()) {
                        outputFile.delete();
                    }
                }

                @Override
                public void onSuccess(final ConfigResponse result) {
                    // Delete the output file on delete.
                    deleteOutput();
                }

                @Override
                public void onFailure(final Throwable t) {
                    // In case the future was cancelled, we have to
                    // destroy the process.
                    if (result.isCancelled()) {
                        process.destroy();
                    }
                    deleteOutput();
                }
            });
        } catch (Exception e) {
            // Returns a {@code ListenableFuture} which has an exception set immediately
            // upon construction.
            return Futures.immediateFailedFuture(e);
        }

        // Start a thread that waits for command execution to complete or till it's cancelled.
        new Thread() {
            @Override
            public void run() {
                try {
                    // Wait for process to exit and extract the return. If cancelled the process will
                    // be shutdown.
                    process.waitFor();
                    int exit = process.exitValue();
                    if (exit == 0) {
                        result.set(
                                new DefaultConfigResponse(0, Files.newReaderSupplier(outputFile, Charsets.UTF_8)));
                    } else {
                        result.set(new DefaultConfigResponse(exit, null));
                    }
                } catch (Exception e) {
                    result.setException(e);
                }
            }
        }.start();

        return result;
    }

    /**
     * @return Returns the command used to execute configure using <code>outputFile</code> in which
     *         the output of run would be stored.
     */
    private String getCommand(File outputFile) {
        return String.format("--jar %s --output %s", jarFilename.getAbsolutePath(), outputFile.getAbsolutePath());
    }
}