/*
* 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 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.FileSystems;
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 {
private Map<String, Resource> resources;
private Map<String, String> environment;
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 = resources;
this.environment = environment;
this.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);
}
@Nonnull
public Map<String, Resource> getResources() {
return resources;
}
@Nonnull
public <R extends Resource> Collection<R> getResources(final Class<R> type) {
return (Collection<R>) Collections2.filter(resources.values(), new Predicate<Resource>() {
@Override
public boolean apply(@Nullable Resource r) {
return type.isAssignableFrom(r.getClass());
}
});
}
public String getEnvironmentVariable(String variableName) {
return environment.get(variableName);
}
public Map<String, String> getEnvironment() {
return environment;
}
public boolean hasSection(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;
}
/**
* @throws NullPointerException if section does not exist
*/
@Nullable
public String getRuntimeParameter(String section, String name) {
RuntimeProperty runtimeProperty = runtimeProperties.get(section);
if (runtimeProperty == null) {
return null;
}
return runtimeProperty.getParameter(name);
}
public String getRuntimeParameter(String section, String name, String defaultValue) {
RuntimeProperty runtimeProperty = runtimeProperties.get(section);
if (runtimeProperty == null) {
return defaultValue;
}
String value = runtimeProperty.getParameter(name);
if (value == null) {
return defaultValue;
}
return value;
}
public void setRuntimeParameter(String parameter, String value) {
String section = Strings2.substringBeforeFirst(parameter, '.');
String property = Strings2.substringAfterFirst(parameter, '.');
if (section == null) {
throw new IllegalArgumentException("no key found in '" + parameter + "'");
}
if (property == null)
throw new IllegalArgumentException("no property found in '" + parameter + "'");
setRuntimeParameter(section, property, value);
}
public void setRuntimeParameter(String section, String name, 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);
}
}
}