Java tutorial
/** * Cloud Foundry 2012.02.03 Beta Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). You may not use this product * except in compliance with the License. * * This product includes a number of subcomponents with separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. */ package org.cloudfoundry.identity.varz; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.management.MBeanServerConnection; import javax.management.ObjectName; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.jackson.map.ObjectMapper; import org.springframework.context.EnvironmentAware; import org.springframework.context.expression.MapAccessor; import org.springframework.core.env.Environment; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class VarzEndpoint implements EnvironmentAware { private static Log logger = LogFactory.getLog(VarzEndpoint.class); private MBeanServerConnection server; private Map<String, Object> statix = new LinkedHashMap<String, Object>(); private Environment environment; private String baseUrl; private Properties environmentProperties = new Properties(); private Properties gitProperties = new Properties(); private Properties buildProperties = new Properties(); private ObjectMapper objectMapper = new ObjectMapper(); public VarzEndpoint() { try { gitProperties = PropertiesLoaderUtils.loadAllProperties("git.properties"); } catch (IOException e) { // Ignore } try { buildProperties = PropertiesLoaderUtils.loadAllProperties("build.properties"); } catch (IOException e) { // Ignore } } /** * Hard-coded baseUrl for absolute links. * @param baseUrl the baseUrl to set */ public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; } /** * @param environmentProperties the environment properties to set */ public void setEnvironmentProperties(Properties environmentProperties) { this.environmentProperties = hidePasswords(environmentProperties); } @Override public void setEnvironment(Environment environment) { this.environment = environment; } public void setStaticValues(Map<String, Object> statics) { this.statix = new LinkedHashMap<String, Object>(statics); } public void setServer(MBeanServerConnection server) { this.server = server; } @RequestMapping(value = "/info") @ResponseBody public Map<String, ?> getInfo(@ModelAttribute("baseUrl") String baseUrl) throws Exception { Map<String, Object> result = new LinkedHashMap<String, Object>(statix); if (!buildProperties.isEmpty()) { result.put("app", getMapFromProperties(buildProperties, "build.")); } if (!gitProperties.isEmpty()) { result.put("commit_id", gitProperties.getProperty("git.commit.id.abbrev", "UNKNOWN")); result.put("timestamp", gitProperties.getProperty("git.commit.time", new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date()))); } return result; } @RequestMapping(value = "/") @ResponseBody public Map<String, ?> getVarz(@ModelAttribute("baseUrl") String baseUrl) throws Exception { Map<String, Object> result = new LinkedHashMap<String, Object>(statix); Map<String, String> links = new HashMap<String, String>(getLinks(baseUrl, getMBeanDomains())); links.put("env", getLink(baseUrl, "env")); result.put("links", links); Map<String, ?> memory = pullUpMap("java.lang", "type=Memory"); result.put("mem", getValueFromMap(memory, "memory.heap_memory_usage.used", Long.class) / 1024); result.put("memory", getValueFromMap(memory, "memory")); Map<String, ?> tomcat = getDomain("Catalina", "*"); putIfNotNull(result, "thread_pool", tomcat.get("thread_pool")); putIfNotNull(result, "global_request_processor", tomcat.get("global_request_processor")); Map<String, ?> spring = pullUpMap("spring.application", "*"); if (spring != null) { // Information about users (counts etc) putIfNotNull(result, "scim", getValueFromMap(spring, "#this['scim_user_endpoints']?.scim_user_endpoints")); // Information about clients (counts etc) putIfNotNull(result, "client_admin", getValueFromMap(spring, "#this['client_admin_endpoints']?.client_admin_endpoints")); // Information about tokens (counts etc) putIfNotNull(result, "token_store", getValueFromMap(spring, "#this['token_store']?.token_store")); // Information about audit (counts) putIfNotNull(result, "audit_service", getValueFromMap(spring, "#this['logging_audit_service']?.logging_audit_service")); // Information about data source putIfNotNull(result, "data_source", getValueFromMap(spring, "#this['data_source']?.data_source")); } // Application config properties putIfNotNull(result, "config", environmentProperties); if (environment != null) { result.put("spring.profiles.active", environment.getActiveProfiles()); } return sanitize(result); } private Map<String, String> getLinks(String baseUrl, Collection<String> paths) { Map<String, String> map = new LinkedHashMap<String, String>(); for (String domain : paths) { map.put(domain, getLink(baseUrl, domain)); } return map; } private String getLink(String baseUrl, String path) { String url = path; if (url.endsWith("/")) { url = url.substring(0, url.lastIndexOf("/")); } return String.format("%s/%s", baseUrl, path); } /** * Compute a base url for links. * * @param request the current request * @return a computed base url for this application, or the hard-coded {@link #setBaseUrl(String) value} if set */ @ModelAttribute("baseUrl") public String getBaseUrl(HttpServletRequest request) { if (this.baseUrl != null) { return this.baseUrl; } String scheme = request.getScheme(); StringBuffer url = new StringBuffer(scheme + "://"); url.append(request.getServerName()); int port = request.getServerPort(); if ((scheme.equals("http") && port != 80) || (scheme.equals("https") && port != 443)) { url.append(":" + port); } url.append(request.getContextPath()); return url.toString(); } private void putIfNotNull(Map<String, Object> result, String key, Object value) { if (value != null) { result.put(key, value); } } private Map<String, ?> pullUpMap(String domain, String pattern) throws Exception { @SuppressWarnings("unchecked") Map<String, ?> map = (Map<String, ?>) getMBeans(domain, pattern).get(domain); return map == null ? Collections.<String, Object>emptyMap() : map; } private <T> T getValueFromMap(Map<String, ?> map, String path, Class<T> type) throws Exception { @SuppressWarnings("unchecked") T result = (T) getValueFromMap(map, path); return result; } private Object getValueFromMap(Map<String, ?> map, String path) throws Exception { @SuppressWarnings("unchecked") MapWrapper wrapper = new MapWrapper((Map<String, Object>) map); return wrapper.get(path); } @RequestMapping("/env") @ResponseBody public Map<String, ?> getEnv() throws Exception { Map<String, Object> result = new LinkedHashMap<String, Object>(statix); result.putAll(getHostProperties()); if (environment != null) { result.put("spring.profiles.active", environment.getActiveProfiles()); } return result; } @RequestMapping("/{domain}") @ResponseBody public Map<String, ?> getDomain(@PathVariable String domain, @RequestParam(required = false, defaultValue = "*") String pattern) throws Exception { Map<String, Object> result = new LinkedHashMap<String, Object>(); // Prevent known stack overflows introspecting tomcat mbeans if (domain.equals("Catalina") || domain.equals("*") || domain.equals("tomcat")) { // Restrict the types that can be used Map<String, Object> beans = new LinkedHashMap<String, Object>(); List<String> types = Arrays.asList("GlobalRequestProcessor", "ThreadPool"); if (pattern.contains("type=GlobalRequestProcessor")) { types = Arrays.asList("GlobalRequestProcessor"); } else if (pattern.contains("type=ThreadPool")) { types = Arrays.asList("ThreadPool"); } else if (pattern.contains("type=")) { beans.put("ignored_pattern", pattern); beans.put("message", "Tomcat MBeans are not available except 'type=GlobalRequestProcessor,*' or 'type=ThreadPool,*'"); pattern = "*"; // ignore other types types = Collections.emptyList(); } for (String type : types) { Map<String, ?> tomcat = getMBeans("*", pattern.contains("type=") ? pattern : "type=" + type + "," + pattern); if (!tomcat.isEmpty()) { if (tomcat.size() == 1) { // tomcat 6.0.23, 6.0.35 have different default domains so normalize... @SuppressWarnings("unchecked") Map<String, ?> map = (Map<String, ?>) tomcat.values().iterator().next(); beans.putAll(map); } else { beans.putAll(tomcat); } } } result.put("Catalina", beans); } else { result.putAll(getMBeans(domain, pattern)); } // Don't need the key if there's only the domain (normally the case) if (result.size() == 1) { result = getMap(result, domain); } return sanitize(result); } private Map<String, ?> getMBeans(String domain, String pattern) throws Exception { Set<ObjectName> names = server.queryNames(ObjectName.getInstance(domain + ":" + pattern), null); Map<String, Object> result = new LinkedHashMap<String, Object>(); for (ObjectName name : names) { Map<String, Object> map = new MBeanMap(server, name); Map<String, Object> objects = getMap((Map<String, Object>) result, domain); String type = name.getKeyProperty("type"); if (type != null) { type = VarzStringUtils.camelToUnderscore(type); objects = getMap(objects, type); } String key = name.getKeyProperty("name"); if (key != null) { key = VarzStringUtils.camelToUnderscore(key); objects = getMap(objects, key); } for (String property : name.getKeyPropertyList().keySet()) { if (property.equals("type") || property.equals("name")) { continue; } key = VarzStringUtils.camelToUnderscore(property); objects = getMap(objects, key); String value = name.getKeyProperty(property); objects = getMap(objects, value); } if (key == null) { key = type; } if (key == null) { key = domain; } objects.putAll(map); } return result; } @RequestMapping("/domains") @ResponseBody public Set<String> getMBeanDomains() throws IOException { Set<String> result = new HashSet<String>(); Set<ObjectName> names = server.queryNames(null, null); for (ObjectName name : names) { result.add(name.getDomain()); } return result; } private Map<String, ?> sanitize(Map<String, Object> input) { Map<String, Object> result = new LinkedHashMap<String, Object>(input); doSanitize(result); return result; } private void doSanitize(Map<String, Object> result) { LinkedHashSet<String> keys = new LinkedHashSet<String>(result.keySet()); for (String key : keys) { Object value = result.remove(key); key = unquote(key); if (value instanceof Map) { @SuppressWarnings("unchecked") Map<String, Object> map = new LinkedHashMap<String, Object>((Map<String, Object>) value); doSanitize(map); result.put(key, map); } else { try { result.put(key, objectMapper.readValue(objectMapper.writeValueAsString(value), Object.class)); } catch (Exception e) { result.put(key, "error:<" + e.getMessage() + ">"); } } } } private String unquote(String key) { if (key.startsWith("\"")) { key = key.substring(1); } if (key.endsWith("\"")) { key = key.substring(0, key.length() - 1); } return key; } private Map<String, Object> getMap(Map<String, Object> result, String key) { if (!result.containsKey(key)) { result.put(key, new MBeanMap()); } @SuppressWarnings("unchecked") Map<String, Object> objects = (Map<String, Object>) result.get(key); return objects; } private Map<String, String> getHostProperties() { Map<String, String> env = new LinkedHashMap<String, String>(); try { Map<String, String> values = System.getenv(); for (String key : values.keySet()) { env.put(key, values.get(key)); } } catch (Exception e) { logger.warn("Could not obtain OS environment", e); } return env; } class MapWrapper { private final SpelExpressionParser parser; private final StandardEvaluationContext context; private final Map<String, Object> target; public MapWrapper(Map<String, Object> target) throws Exception { this.target = target; context = new StandardEvaluationContext(); context.addPropertyAccessor(new MapAccessor()); parser = new SpelExpressionParser(); } public Map<String, Object> getMap() { return target; } public Object get(String expression) throws Exception { return get(expression, Object.class); } public <T> T get(String expression, Class<T> type) throws Exception { return parser.parseExpression(expression).getValue(context, target, type); } @Override public String toString() { return target.toString(); } } /** * @param properties * @return new properties with no plaintext passwords */ public static Properties hidePasswords(Properties properties) { Properties result = new Properties(); result.putAll(properties); for (String key : properties.stringPropertyNames()) { if (isPassword(key)) { result.put(key, "#"); } } return result; } private static boolean isPassword(String key) { return key.endsWith("password") || key.endsWith("secret") || key.endsWith("signing-key"); } /** * Extract a Map from some properties by removing a prefix from the key names. * * @param properties the properties to use * @param prefix the prefix to strip from key names * @return a map of String values */ public static Map<String, ?> getMapFromProperties(Properties properties, String prefix) { Map<String, Object> result = new HashMap<String, Object>(); for (String key : properties.stringPropertyNames()) { if (key.startsWith(prefix)) { String name = key.substring(prefix.length()); result.put(name, properties.getProperty(key)); } } return result; } }