co.cask.cdap.common.conf.ArtifactConfigReader.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.common.conf.ArtifactConfigReader.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.common.conf;

import co.cask.cdap.api.plugin.PluginClass;
import co.cask.cdap.common.InvalidArtifactException;
import co.cask.cdap.proto.Id;
import co.cask.cdap.proto.artifact.ArtifactRange;
import co.cask.cdap.proto.artifact.InvalidArtifactRangeException;
import com.google.common.base.Charsets;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
 * Reads files into {@link ArtifactConfig ArtifactConfigs} for a specific namespace.
 */
public class ArtifactConfigReader {
    private final LoadingCache<Id.Namespace, Gson> gsonCache;

    public ArtifactConfigReader() {
        this.gsonCache = CacheBuilder.newBuilder().build(new CacheLoader<Id.Namespace, Gson>() {
            @Override
            public Gson load(Id.Namespace namespace) throws Exception {
                return new GsonBuilder()
                        .registerTypeAdapter(ArtifactRange.class, new ArtifactRangeDeserializer(namespace))
                        .registerTypeAdapter(ArtifactConfig.class, new ArtifactConfigDeserializer())
                        .registerTypeAdapter(PluginClass.class, new PluginClassDeserializer()).create();
            }
        });
    }

    /**
     * Read the contents of the given file and deserialize it into an ArtifactConfig.
     *
     * @param namespace the namespace of the artifact this config file is for
     * @param configFile the config file to read
     * @return the contents of the file parsed as an ArtifactConfig
     * @throws IOException if there was a problem reading the file, for example if the file does not exist.
     * @throws InvalidArtifactException if there was a problem deserializing the file contents due to some invalid
     *                                  format or unexpected value.
     */
    public ArtifactConfig read(Id.Namespace namespace, File configFile)
            throws IOException, InvalidArtifactException {
        String fileName = configFile.getName();
        try (Reader reader = Files.newReader(configFile, Charsets.UTF_8)) {
            try {
                ArtifactConfig config = gsonCache.getUnchecked(namespace).fromJson(reader, ArtifactConfig.class);

                // check namespaces in parents are either system or the specified namespace
                for (ArtifactRange parent : config.getParents()) {
                    Id.Namespace parentNamespace = parent.getNamespace();
                    if (!parentNamespace.equals(Id.Namespace.SYSTEM) && !parent.getNamespace().equals(namespace)) {
                        throw new InvalidArtifactException(
                                String.format("Invalid parent %s. Parents must be in the same "
                                        + "namespace or a system artifact.", parent));
                    }
                }
                return config;
            } catch (JsonSyntaxException e) {
                throw new InvalidArtifactException(
                        String.format("%s contains invalid json: %s", fileName, e.getMessage()), e);
            } catch (JsonParseException e) {
                throw new InvalidArtifactException(
                        String.format("Unable to parse %s: %s", fileName, e.getMessage()), e);
            }
        }
    }

    /**
     * Deserializer for ArtifactRange in a ArtifactConfig. Artifact ranges are expected to be able to be
     * parsed via {@link ArtifactRange#parse(String)} or {@link ArtifactRange#parse(Id.Namespace, String)}.
     */
    private static class ArtifactRangeDeserializer implements JsonDeserializer<ArtifactRange> {
        private final Id.Namespace namespace;

        public ArtifactRangeDeserializer(Id.Namespace namespace) {
            this.namespace = namespace;
        }

        @Override
        public ArtifactRange deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            if (!json.isJsonPrimitive()) {
                throw new JsonParseException("ArtifactRange should be a string.");
            }

            String rangeStr = json.getAsString();
            try {
                if (rangeStr.indexOf(':') > 0) {
                    return ArtifactRange.parse(rangeStr);
                } else {
                    return ArtifactRange.parse(namespace, rangeStr);
                }
            } catch (InvalidArtifactRangeException e) {
                throw new JsonParseException(e.getMessage());
            }
        }
    }

    /**
     * Deserializer for ArtifactConfig. Used to make sure collections are set to empty collections
     * instead of null.
     */
    private static final class ArtifactConfigDeserializer implements JsonDeserializer<ArtifactConfig> {
        private static final Type PLUGINS_TYPE = new TypeToken<Set<PluginClass>>() {
        }.getType();
        private static final Type PARENTS_TYPE = new TypeToken<Set<ArtifactRange>>() {
        }.getType();
        private static final Type PROPERTIES_TYPE = new TypeToken<Map<String, String>>() {
        }.getType();

        @Override
        public ArtifactConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            if (!json.isJsonObject()) {
                throw new JsonParseException("Config file must be a JSON Object.");
            }
            JsonObject obj = json.getAsJsonObject();

            // deserialize fields
            Set<ArtifactRange> parents = context.deserialize(obj.get("parents"), PARENTS_TYPE);
            parents = parents == null ? Collections.<ArtifactRange>emptySet() : parents;
            Set<PluginClass> plugins = context.deserialize(obj.get("plugins"), PLUGINS_TYPE);
            plugins = plugins == null ? Collections.<PluginClass>emptySet() : plugins;
            Map<String, String> properties = context.deserialize(obj.get("properties"), PROPERTIES_TYPE);
            properties = properties == null ? Collections.<String, String>emptyMap() : properties;

            return new ArtifactConfig(parents, plugins, properties);
        }
    }
}