Java tutorial
/* * 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); } } }