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

Java tutorial

Introduction

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

Source

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

import co.cask.cdap.api.Config;
import co.cask.cdap.api.app.Application;
import co.cask.cdap.api.app.ApplicationSpecification;
import co.cask.cdap.app.DefaultAppConfigurer;
import co.cask.cdap.app.DefaultApplicationContext;
import co.cask.cdap.app.deploy.ConfigResponse;
import co.cask.cdap.app.deploy.Configurator;
import co.cask.cdap.app.program.ManifestFields;
import co.cask.cdap.common.conf.CConfiguration;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.common.io.CaseInsensitiveEnumTypeAdapterFactory;
import co.cask.cdap.common.lang.jar.BundleJarUtil;
import co.cask.cdap.common.utils.DirUtils;
import co.cask.cdap.internal.app.ApplicationSpecificationAdapter;
import co.cask.cdap.internal.app.runtime.artifact.ArtifactRepository;
import co.cask.cdap.internal.app.runtime.artifact.Artifacts;
import co.cask.cdap.internal.app.runtime.artifact.CloseableClassLoader;
import co.cask.cdap.internal.app.runtime.plugin.PluginInstantiator;
import co.cask.cdap.internal.io.ReflectionSchemaGenerator;
import co.cask.cdap.proto.Id;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.io.CharStreams;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import org.apache.twill.filesystem.Location;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.jar.Manifest;
import javax.annotation.Nullable;

/**
 * In Memory Configurator doesn't spawn a external process, but
 * does this in memory.
 *
 * @see SandboxConfigurator
 */
public final class InMemoryConfigurator implements Configurator {
    private static final Logger LOG = LoggerFactory.getLogger(InMemoryConfigurator.class);

    /**
     * JAR file path.
     */
    private final Location artifact;
    private final CConfiguration cConf;
    private final String configString;
    private final File baseUnpackDir;
    // this is the namespace that the app will be in, which may be different than the namespace of the artifact.
    // if the artifact is a system artifact, the namespace will be the system namespace.
    private final Id.Namespace appNamespace;

    private ArtifactRepository artifactRepository;

    // these field provided if going through artifact code path, but not through template code path
    // this is temporary until we can remove templates. (CDAP-2662).
    private String appClassName;
    private Id.Artifact artifactId;

    public InMemoryConfigurator(CConfiguration cConf, Id.Namespace appNamespace, Id.Artifact artifactId,
            String appClassName, Location artifact, @Nullable String configString,
            ArtifactRepository artifactRepository) {
        Preconditions.checkNotNull(artifact);
        this.cConf = cConf;
        this.appNamespace = appNamespace;
        this.artifactId = artifactId;
        this.appClassName = appClassName;
        this.artifact = artifact;
        this.configString = configString;
        this.artifactRepository = artifactRepository;
        this.baseUnpackDir = new File(cConf.get(Constants.CFG_LOCAL_DATA_DIR),
                cConf.get(Constants.AppFabric.TEMP_DIR)).getAbsoluteFile();
    }

    /**
     * Executes the <code>Application.configure</code> within the same JVM.
     * <p>
     * This method could be dangerous and should be used only in standalone mode.
     * </p>
     *
     * @return A instance of {@link ListenableFuture}.
     */
    @Override
    public ListenableFuture<ConfigResponse> config() {
        SettableFuture<ConfigResponse> result = SettableFuture.create();

        try {
            if (appClassName == null) {
                readAppClassName();
            }

            try (CloseableClassLoader artifactClassLoader = artifactRepository
                    .createArtifactClassLoader(artifact)) {
                Object appMain = artifactClassLoader.loadClass(appClassName).newInstance();
                if (!(appMain instanceof Application)) {
                    throw new IllegalStateException(String.format("Application main class is of invalid type: %s",
                            appMain.getClass().getName()));
                }

                Application app = (Application) appMain;
                ConfigResponse response = createResponse(app);
                result.set(response);
            }

            return result;
        } catch (Throwable t) {
            LOG.error(t.getMessage(), t);
            return Futures.immediateFailedFuture(t);
        }
    }

    // remove once app templates are gone
    private void readAppClassName() throws IOException {
        // Load the JAR using the JAR class load and load the manifest file.
        Manifest manifest = BundleJarUtil.getManifest(artifact);
        Preconditions.checkArgument(manifest != null, "Failed to load manifest from %s", artifact);
        Preconditions.checkArgument(manifest.getMainAttributes() != null,
                "Failed to load manifest attributes from %s", artifact);

        appClassName = manifest.getMainAttributes().getValue(ManifestFields.MAIN_CLASS);
        Preconditions.checkArgument(appClassName != null && !appClassName.isEmpty(),
                "Main class attribute cannot be empty");
    }

    private ConfigResponse createResponse(Application app)
            throws InstantiationException, IllegalAccessException, IOException {
        String specJson = getSpecJson(app, configString);
        return new DefaultConfigResponse(0, CharStreams.newReaderSupplier(specJson));
    }

    private <T extends Config> String getSpecJson(Application<T> app, final String configString)
            throws IllegalAccessException, InstantiationException, IOException {

        File tempDir = DirUtils.createTempDir(baseUnpackDir);
        // This Gson cannot be static since it is used to deserialize user class.
        // Gson will keep a static map to class, hence will leak the classloader
        Gson gson = new GsonBuilder().registerTypeAdapterFactory(new CaseInsensitiveEnumTypeAdapterFactory())
                .create();
        // Now, we call configure, which returns application specification.
        DefaultAppConfigurer configurer;
        try (PluginInstantiator pluginInstantiator = new PluginInstantiator(cConf, app.getClass().getClassLoader(),
                tempDir)) {
            configurer = new DefaultAppConfigurer(appNamespace, artifactId, app, configString, artifactRepository,
                    pluginInstantiator);
            T appConfig;
            Type configType = Artifacts.getConfigType(app.getClass());
            if (Strings.isNullOrEmpty(configString)) {
                //noinspection unchecked
                appConfig = ((Class<T>) configType).newInstance();
            } else {
                try {
                    appConfig = gson.fromJson(configString, configType);
                } catch (JsonSyntaxException e) {
                    throw new IllegalArgumentException(
                            "Invalid JSON configuration was provided. Please check the syntax.", e);
                }
            }

            app.configure(configurer, new DefaultApplicationContext<>(appConfig));
        } finally {
            try {
                DirUtils.deleteDirectoryContents(tempDir);
            } catch (IOException e) {
                LOG.warn("Exception raised when deleting directory {}", tempDir, e);
            }
        }
        ApplicationSpecification specification = configurer.createSpecification();

        // Convert the specification to JSON.
        // We write the Application specification to output file in JSON format.
        // TODO: The SchemaGenerator should be injected
        return ApplicationSpecificationAdapter.create(new ReflectionSchemaGenerator()).toJson(specification);
    }
}