/* * Copyright 2014 the original author or authors. * * 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 de.codecentric.boot.admin.model; import java.io.IOException; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; import org.springframework.util.Assert; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer; /** * The domain model for all registered application at the spring boot admin application. */ @JsonDeserialize(using = Application.Deserializer.class) public class Application implements Serializable { private static final long serialVersionUID = 2L; private final String id; private final String name; private final String managementUrl; private final String healthUrl; private final String serviceUrl; private final StatusInfo statusInfo; private final String source; @JsonSerialize(using = Application.MetadataSerializer.class) private final Map<String, String> metadata; private final Info info; protected Application(Builder builder) { Assert.hasText(builder.name, "name must not be empty!"); Assert.hasText(builder.healthUrl, "healthUrl must not be empty!"); Assert.notNull(builder.statusInfo, "statusInfo must not be null!"); this.healthUrl = builder.healthUrl; this.managementUrl = builder.managementUrl; this.serviceUrl = builder.serviceUrl; this.name = builder.name; this.id = builder.id; this.statusInfo = builder.statusInfo; this.source = builder.source; this.metadata = Collections.unmodifiableMap(new HashMap<>(builder.metadata)); this.info = builder.info; } public static Builder create(String name) { return new Builder(name); } public static Builder copyOf(Application application) { return new Builder(application); } public static class Builder { private String id; private String name; private String managementUrl; private String healthUrl; private String serviceUrl; private StatusInfo statusInfo = StatusInfo.ofUnknown(); private String source; private Map<String, String> metadata = new HashMap<>(); private Info info = Info.empty(); private Builder(String name) { this.name = name; } private Builder(Application application) { this.healthUrl = application.healthUrl; this.managementUrl = application.managementUrl; this.serviceUrl = application.serviceUrl; this.name = application.name; this.id = application.id; this.statusInfo = application.statusInfo; this.source = application.source; this.metadata.putAll(application.getMetadata()); this.info = application.info; } public Builder withName(String name) { this.name = name; return this; } public Builder withId(String id) { this.id = id; return this; } public Builder withServiceUrl(String serviceUrl) { this.serviceUrl = serviceUrl; return this; } public Builder withHealthUrl(String healthUrl) { this.healthUrl = healthUrl; return this; } public Builder withManagementUrl(String managementUrl) { this.managementUrl = managementUrl; return this; } public Builder withStatusInfo(StatusInfo statusInfo) { this.statusInfo = statusInfo; return this; } public Builder withSource(String source) { this.source = source; return this; } public Builder addMetadata(String key, String value) { this.metadata.put(key, value); return this; } public Builder withMetadata(Map<String, String> metadata) { this.metadata = metadata; return this; } public Builder withInfo(Info info) { this.info = info; return this; } public Application build() { return new Application(this); } } public String getId() { return id; } public String getName() { return name; } public String getManagementUrl() { return managementUrl; } public String getHealthUrl() { return healthUrl; } public String getServiceUrl() { return serviceUrl; } public StatusInfo getStatusInfo() { return statusInfo; } public String getSource() { return source; } public Map<String, String> getMetadata() { return metadata; } public Info getInfo() { return info; } @Override public String toString() { return "Application [id=" + id + ", name=" + name + ", managementUrl=" + managementUrl + ", healthUrl=" + healthUrl + ", serviceUrl=" + serviceUrl + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((healthUrl == null) ? 0 : healthUrl.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((managementUrl == null) ? 0 : managementUrl.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((serviceUrl == null) ? 0 : serviceUrl.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Application other = (Application) obj; if (healthUrl == null) { if (other.healthUrl != null) { return false; } } else if (!healthUrl.equals(other.healthUrl)) { return false; } if (id == null) { if (other.id != null) { return false; } } else if (!id.equals(other.id)) { return false; } if (managementUrl == null) { if (other.managementUrl != null) { return false; } } else if (!managementUrl.equals(other.managementUrl)) { return false; } if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } if (serviceUrl == null) { if (other.serviceUrl != null) { return false; } } else if (!serviceUrl.equals(other.serviceUrl)) { return false; } return true; } public static class Deserializer extends StdDeserializer<Application> { private static final long serialVersionUID = 1L; protected Deserializer() { super(Application.class); } @Override public Application deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode node = p.readValueAsTree(); Builder builder = create(node.get("name").asText()); if (node.has("url")) { String url = node.get("url").asText(); builder.withHealthUrl(url.replaceFirst("/+$", "") + "/health") .withManagementUrl(url); } else { if (node.has("healthUrl")) { builder.withHealthUrl(node.get("healthUrl").asText()); } if (node.has("managementUrl")) { builder.withManagementUrl(node.get("managementUrl").asText()); } if (node.has("serviceUrl")) { builder.withServiceUrl(node.get("serviceUrl").asText()); } } if (node.has("metadata")) { Iterator<Entry<String, JsonNode>> it = node.get("metadata").fields(); while (it.hasNext()) { Entry<String, JsonNode> entry = it.next(); builder.addMetadata(entry.getKey(), entry.getValue().asText()); } } return builder.build(); } } public static class MetadataSerializer extends StdSerializer<Map<String, String>> { private static final long serialVersionUID = 1L; private static Pattern[] keysToSanitize = createPatterns(".*password$", ".*secret$", ".*key$", ".*$token$", ".*credentials.*", ".*vcap_services$"); @SuppressWarnings("unchecked") public MetadataSerializer() { super((Class<Map<String, String>>) (Class<?>) Map.class); } private static Pattern[] createPatterns(String... keys) { Pattern[] patterns = new Pattern[keys.length]; for (int i = 0; i < keys.length; i++) { patterns[i] = Pattern.compile(keys[i]); } return patterns; } @Override public void serialize(Map<String, String> value, JsonGenerator gen, SerializerProvider provider) throws IOException { gen.writeStartObject(); for (Entry<String, String> entry : value.entrySet()) { gen.writeStringField(entry.getKey(), sanitize(entry.getKey(), entry.getValue())); } gen.writeEndObject(); } private String sanitize(String key, String value) { for (Pattern pattern : MetadataSerializer.keysToSanitize) { if (pattern.matcher(key).matches()) { return (value == null ? null : "******"); } } return value; } } }