com.cloudbees.clickstack.domain.metadata.Metadata.java Source code

Java tutorial

Introduction

Here is the source code for com.cloudbees.clickstack.domain.metadata.Metadata.java

Source

/*
 * Copyright 2010-2013, CloudBees 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 com.cloudbees.clickstack.domain.metadata;

import com.cloudbees.clickstack.util.Strings2;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

/**
 * It stores GenApp resources,
 * environment variables (given by -P with the SDK), and runtime parameters (given by -R with the SDK).
 * It also makes them accessible for other classes to (typically) write configuration files within ClickStacks.
 */

public class Metadata {

    @Nonnull
    private Map<String, Resource> resources;
    @Nonnull
    private Map<String, String> environment;
    @Nonnull
    private Map<String, RuntimeProperty> runtimeProperties;

    /**
     * This constructor is used by the Builder subclass to create a new Metadata instance
     *
     * @param resources         A map of the GenApp resources
     * @param environment       A map of the environment variables
     * @param runtimeProperties A map of RuntimeProperties
     */
    protected Metadata(Map<String, Resource> resources, Map<String, String> environment,
            Map<String, RuntimeProperty> runtimeProperties) {
        this.resources = Preconditions.checkNotNull(resources, "resources");
        this.environment = Preconditions.checkNotNull(environment, "environment");
        this.runtimeProperties = Preconditions.checkNotNull(runtimeProperties, "runtimeProperties");
    }

    public Metadata() {
        this.resources = new HashMap<>();
        this.environment = new HashMap<>();
        this.runtimeProperties = new HashMap<>();
    }

    @Nonnull
    public <R extends Resource> R getResource(String resourceName) {
        return (R) resources.get(resourceName);
    }

    /**
     * @return key resource entry key in metadata.json, value: the {@link com.cloudbees.clickstack.domain.metadata.Resource}
     */
    @Nonnull
    public Map<String, Resource> getResources() {
        return resources;
    }

    @Nonnull
    public <R extends Resource> Collection<R> getResources(@Nullable final Class<R> type) {
        if (type == null)
            return (Collection<R>) resources.values();

        return (Collection<R>) Collections2.filter(resources.values(), new Predicate<Resource>() {
            @Override
            public boolean apply(@Nullable Resource r) {
                return type.isAssignableFrom(r.getClass());
            }
        });
    }

    @Nonnull
    public <R extends Resource> Collection<R> getResourcesByType(@Nullable final String type) {
        if (type == null)
            return (Collection<R>) resources.values();

        return (Collection<R>) Collections2.filter(resources.values(), new Predicate<Resource>() {
            @Override
            public boolean apply(@Nullable Resource r) {
                return Objects.equals(type, r.getType());
            }
        });
    }

    /**
     * @param type
     * @return
     * @throws IllegalStateException more than 1 {@link com.cloudbees.clickstack.domain.metadata.Resource} matching given {@type found}
     */
    @Nullable
    public Resource getResourceByType(@Nullable final String type) throws IllegalStateException {

        Collection<Resource> matchingResources = getResourcesByType(type);

        Preconditions.checkState(matchingResources.size() <= 1, "More than 1 resource with type='%s': %s", type,
                matchingResources);

        return Iterables.getFirst(matchingResources, null);
    }

    @Nullable
    public String getEnvironmentVariable(String variableName) {
        return environment.get(variableName);
    }

    @Nonnull
    public Map<String, String> getEnvironment() {
        return environment;
    }

    public boolean hasSection(@Nullable String section) {
        return runtimeProperties.containsKey(section);
    }

    /**
     * @throws NullPointerException if section does not exist
     */
    @Nullable
    public RuntimeProperty getRuntimeProperty(String section) {
        RuntimeProperty runtimeProperty = runtimeProperties.get(section);
        if (runtimeProperty == null) {
            return null;
        }
        return runtimeProperty;
    }

    /**
     *
     * @param section
     * @param name
     * @return {@code null} if the section does not exist or if the parameter does not exist
     */
    @Nullable
    public String getRuntimeParameter(@Nullable String section, @Nullable String name) {
        RuntimeProperty runtimeProperty = runtimeProperties.get(section);
        if (runtimeProperty == null) {
            return null;
        }
        return runtimeProperty.getParameter(name);
    }

    /**
     *
     * @param section
     * @param name
     * @param defaultValue
     * @return {@code null} if the section does not exist or if the parameter does not exist and the {@code defaultValue} is {@code null}
     */
    @Nullable
    public String getRuntimeParameter(@Nullable String section, @Nullable String name,
            @Nullable String defaultValue) {
        RuntimeProperty runtimeProperty = runtimeProperties.get(section);
        if (runtimeProperty == null) {
            return defaultValue;
        }
        String value = runtimeProperty.getParameter(name);
        if (value == null) {
            return defaultValue;
        }
        return value;
    }

    /**
     *
     * @param parameter format {@code section + '.' + parameterName}
     * @param value
     */
    public void setRuntimeParameter(@Nonnull String parameter, @Nonnull String value) {
        String section = Strings2.substringBeforeFirst(parameter, '.');
        String property = Strings2.substringAfterFirst(parameter, '.');
        if (section == null) {
            throw new IllegalArgumentException("no section found in '" + parameter + "'");
        }

        if (property == null)
            throw new IllegalArgumentException("no property found in '" + parameter + "'");

        setRuntimeParameter(section, property, value);
    }

    public void setRuntimeParameter(@Nonnull String section, @Nonnull String name, @Nonnull String value) {
        RuntimeProperty runtimeProperty = this.runtimeProperties.get(section);
        if (runtimeProperty == null) {
            runtimeProperty = new RuntimeProperty(section);
            runtimeProperties.put(section, runtimeProperty);
        }
        runtimeProperty.setProperty(name, value);
    }

    /**
     * The Builder class creates a new Metadata instance from a metadata.json file.
     */
    public static class Builder {

        /**
         * This method parses a metadata.json file and returns a new Metadata instance containing the
         * metadata that has been parsed.
         *
         * @param metadataFile The absolute path to the metadata.json file to be parsed.
         * @return A new Metadata instance, containing the parameters from the metadata.json file.
         * @throws java.io.IOException
         */
        public static Metadata fromFile(File metadataFile) throws IOException {
            FileInputStream metadataInputStream = new FileInputStream(metadataFile);
            try {
                return fromStream(metadataInputStream);
            } finally {
                metadataInputStream.close();
            }
        }

        public static Metadata fromFile(@Nonnull Path metadataFile) throws IOException {
            if (!Files.exists(metadataFile))
                throw new IllegalArgumentException("Given metadata.json file does not exist: " + metadataFile);
            return fromStream(Files.newInputStream(metadataFile));
        }

        /**
         * This method is called from the fromFile method to parse json from a stream.
         *
         * @param metadataInputStream An InputStream to read the JSON metadata from.
         * @return A new Metadata instance, containing all resources parsed
         * from the JSON metadata given as input.
         * @throws java.io.IOException
         */
        public static Metadata fromStream(InputStream metadataInputStream) throws IOException {
            ObjectMapper metadataObjectMapper = new ObjectMapper();

            JsonNode metadataRootNode = metadataObjectMapper.readTree(metadataInputStream);

            return fromJson(metadataRootNode);
        }

        /**
         * This method is called from the fromStream method to parse json from a stream.
         *
         * @param metadataRootNode the JSON metadata from.
         * @return A new Metadata instance, containing all resources parsed
         * from the JSON metadata given as input.
         * @throws java.io.IOException
         */
        public static Metadata fromJson(JsonNode metadataRootNode) throws IOException {

            Builder metadataBuilder = new Builder();

            return metadataBuilder.buildResources(metadataRootNode);
        }

        /**
         * This method is called from the fromStream method to parse json from a stream.
         *
         * @param metadata the JSON metadata.
         * @return A new Metadata instance, containing all resources parsed
         * from the JSON metadata given as input.
         * @throws java.io.IOException
         */
        public static Metadata fromJsonString(String metadata, boolean allowSingleQuotes) throws IOException {

            ObjectMapper metadataObjectMapper = new ObjectMapper();
            metadataObjectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

            JsonNode metadataRootNode = metadataObjectMapper.readTree(metadata);

            return fromJson(metadataRootNode);
        }

        /**
         * This method is called from the fromStream method to parse JSON metadata into a new Metadata instance.
         *
         * @param metadataRootNode The root node of the JSON metadata to be parsed.
         * @return A new Metadata instance containing all parsed metadata.
         */
        private Metadata buildResources(JsonNode metadataRootNode) {

            Map<String, Resource> resources = new TreeMap<>();
            Map<String, String> environment = new TreeMap<>();
            Map<String, RuntimeProperty> runtimeProperties = new TreeMap<>();

            /**
             *  We iterate over all children of the root node, determining if they're resources,
             *  runtime parameters or are part of the "app" section.
             */
            for (Iterator<Map.Entry<String, JsonNode>> fields = metadataRootNode.fields(); fields.hasNext();) {

                Map.Entry<String, JsonNode> entry = fields.next();
                JsonNode content = entry.getValue();
                String id = entry.getKey();
                Map<String, String> entryMetadata = new HashMap<>();

                // We then iterate over all the key-value pairs present in the children node, and store them.
                for (Iterator<Map.Entry<String, JsonNode>> properties = content.fields(); properties.hasNext();) {
                    Map.Entry<String, JsonNode> property = properties.next();
                    String entryName = property.getKey();
                    JsonNode entryValueNode = property.getValue();

                    // We check if the entry is well-formed (i.e can be output to a String meaningfully).
                    if (entryValueNode.isTextual() || entryValueNode.isInt()) {
                        String entryValue = entryValueNode.asText();
                        entryMetadata.put(entryName, entryValue);
                    }

                    // We get environment variables from the metadata when we iterate over app.env
                    if (id.equals("app") && entryName.equals("env")) {
                        for (Iterator<Map.Entry<String, JsonNode>> envVariables = entryValueNode
                                .fields(); envVariables.hasNext();) {
                            Map.Entry<String, JsonNode> envVariable = envVariables.next();
                            String envName = envVariable.getKey();
                            JsonNode envValue = envVariable.getValue();
                            if (envValue.isTextual()) {
                                environment.put(envName, envValue.asText());
                            }
                        }
                    }
                }

                Resource resource = Resource.Builder.buildResource(entryMetadata);
                // We check if the children node we are currently iterating upon is a resource.
                if (resource != null) {
                    resources.put(resource.getName(), resource);
                    // Otherwise, if it wasn't a resource nor the "app" field, it is composed of runtime parameters.
                } else if (!id.equals("app")) {
                    runtimeProperties.put(id, new RuntimeProperty(id, entryMetadata));
                }
            }
            return new Metadata(resources, environment, runtimeProperties);
        }
    }
}