private void processJson(ConfigurableEnvironment environment, String json) {
   try {
     JsonParser parser = JsonParserFactory.getJsonParser();
     Map<String, Object> map = parser.parseMap(json);
     if (!map.isEmpty()) {
       addJsonPropertySource(
           environment, new MapPropertySource("spring.application.json", flatten(map)));
     }
   } catch (Exception ex) {
     logger.warn("Cannot parse JSON for spring.application.json: " + json, ex);
   }
 }
/**
 * An {@link EnvironmentPostProcessor} that knows where to find VCAP (a.k.a. Cloud Foundry) meta
 * data in the existing environment. It parses out the VCAP_APPLICATION and VCAP_SERVICES meta data
 * and dumps it in a form that is easily consumed by {@link Environment} users. If the app is
 * running in Cloud Foundry then both meta data items are JSON objects encoded in OS environment
 * variables. VCAP_APPLICATION is a shallow hash with basic information about the application (name,
 * instance id, instance index, etc.), and VCAP_SERVICES is a hash of lists where the keys are
 * service labels and the values are lists of hashes of service instance meta data. Examples are:
 *
 * <pre class="code">
 * VCAP_APPLICATION: {"instance_id":"2ce0ac627a6c8e47e936d829a3a47b5b","instance_index":0,
 *   "version":"0138c4a6-2a73-416b-aca0-572c09f7ca53","name":"foo",
 *   "uris":["foo.cfapps.io"], ...}
 * VCAP_SERVICES: {"rds-mysql-1.0":[{"name":"mysql","label":"rds-mysql-1.0","plan":"10mb",
 *   "credentials":{"name":"d04fb13d27d964c62b267bbba1cffb9da","hostname":"mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com",
 *   "host":"mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com","port":3306,"user":"******",
 *   "username":"******","password":"******"}
 * }]}
 * </pre>
 *
 * These objects are flattened into properties. The VCAP_APPLICATION object goes straight to {@code
 * vcap.application.*} in a fairly obvious way, and the VCAP_SERVICES object is unwrapped so that it
 * is a hash of objects with key equal to the service instance name (e.g. "mysql" in the example
 * above), and value equal to that instances properties, and then flattened in the same way. E.g.
 *
 * <pre class="code">
 * vcap.application.instance_id: 2ce0ac627a6c8e47e936d829a3a47b5b
 * vcap.application.version: 0138c4a6-2a73-416b-aca0-572c09f7ca53
 * vcap.application.name: foo
 * vcap.application.uris[0]: foo.cfapps.io
 *
 * vcap.services.mysql.name: mysql
 * vcap.services.mysql.label: rds-mysql-1.0
 * vcap.services.mysql.credentials.name: d04fb13d27d964c62b267bbba1cffb9da
 * vcap.services.mysql.credentials.port: 3306
 * vcap.services.mysql.credentials.host: mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com
 * vcap.services.mysql.credentials.username: urpRuqTf8Cpe6
 * vcap.services.mysql.credentials.password: pxLsGVpsC9A5S
 * ...
 * </pre>
 *
 * N.B. this initializer is mainly intended for informational use (the application and instance ids
 * are particularly useful). For service binding you might find that Spring Cloud is more convenient
 * and more robust against potential changes in Cloud Foundry.
 *
 * @author Dave Syer
 * @author Andy Wilkinson
 */
public class CloudFoundryVcapEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {

  private static final Log logger =
      LogFactory.getLog(CloudFoundryVcapEnvironmentPostProcessor.class);

  private static final String VCAP_APPLICATION = "VCAP_APPLICATION";

  private static final String VCAP_SERVICES = "VCAP_SERVICES";

  // Before ConfigFileEnvironmentPostProcessor so values there can use these ones
  private int order = ConfigFileEnvironmentPostProcessor.DEFAULT_ORDER - 1;

  private final JsonParser parser = JsonParserFactory.getJsonParser();

  public void setOrder(int order) {
    this.order = order;
  }

  @Override
  public int getOrder() {
    return this.order;
  }

  @Override
  public void postProcessEnvironment(
      ConfigurableEnvironment environment, SpringApplication application) {
    if (CloudPlatform.CLOUD_FOUNDRY.isActive(environment)) {
      Properties properties = new Properties();
      addWithPrefix(properties, getPropertiesFromApplication(environment), "vcap.application.");
      addWithPrefix(properties, getPropertiesFromServices(environment), "vcap.services.");
      MutablePropertySources propertySources = environment.getPropertySources();
      if (propertySources.contains(CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME)) {
        propertySources.addAfter(
            CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME,
            new PropertiesPropertySource("vcap", properties));
      } else {
        propertySources.addFirst(new PropertiesPropertySource("vcap", properties));
      }
    }
  }

  private void addWithPrefix(Properties properties, Properties other, String prefix) {
    for (String key : other.stringPropertyNames()) {
      String prefixed = prefix + key;
      properties.setProperty(prefixed, other.getProperty(key));
    }
  }

  private Properties getPropertiesFromApplication(Environment environment) {
    Properties properties = new Properties();
    try {
      String property = environment.getProperty(VCAP_APPLICATION, "{}");
      Map<String, Object> map = this.parser.parseMap(property);
      extractPropertiesFromApplication(properties, map);
    } catch (Exception ex) {
      logger.error("Could not parse VCAP_APPLICATION", ex);
    }
    return properties;
  }

  private Properties getPropertiesFromServices(Environment environment) {
    Properties properties = new Properties();
    try {
      String property = environment.getProperty(VCAP_SERVICES, "{}");
      Map<String, Object> map = this.parser.parseMap(property);
      extractPropertiesFromServices(properties, map);
    } catch (Exception ex) {
      logger.error("Could not parse VCAP_SERVICES", ex);
    }
    return properties;
  }

  private void extractPropertiesFromApplication(Properties properties, Map<String, Object> map) {
    if (map != null) {
      flatten(properties, map, "");
    }
  }

  private void extractPropertiesFromServices(Properties properties, Map<String, Object> map) {
    if (map != null) {
      for (Object services : map.values()) {
        @SuppressWarnings("unchecked")
        List<Object> list = (List<Object>) services;
        for (Object object : list) {
          @SuppressWarnings("unchecked")
          Map<String, Object> service = (Map<String, Object>) object;
          String key = (String) service.get("name");
          if (key == null) {
            key = (String) service.get("label");
          }
          flatten(properties, service, key);
        }
      }
    }
  }

  @SuppressWarnings("unchecked")
  private void flatten(Properties properties, Map<String, Object> input, String path) {
    for (Entry<String, Object> entry : input.entrySet()) {
      String key = getFullKey(path, entry.getKey());
      Object value = entry.getValue();
      if (value instanceof Map) {
        // Need a compound key
        flatten(properties, (Map<String, Object>) value, key);
      } else if (value instanceof Collection) {
        // Need a compound key
        Collection<Object> collection = (Collection<Object>) value;
        properties.put(key, StringUtils.collectionToCommaDelimitedString(collection));
        int count = 0;
        for (Object item : collection) {
          String itemKey = "[" + (count++) + "]";
          flatten(properties, Collections.singletonMap(itemKey, item), key);
        }
      } else if (value instanceof String) {
        properties.put(key, value);
      } else if (value instanceof Number) {
        properties.put(key, value.toString());
      } else if (value instanceof Boolean) {
        properties.put(key, value.toString());
      } else {
        properties.put(key, value == null ? "" : value);
      }
    }
  }

  private String getFullKey(String path, String key) {
    if (!StringUtils.hasText(path)) {
      return key;
    }
    if (key.startsWith("[")) {
      return path + key;
    }
    return path + "." + key;
  }
}