/******************************************************************************* * Copyright (c) 2015-2017 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.dash.model.requestmappings; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import org.springframework.ide.eclipse.boot.dash.BootDashActivator; import org.springframework.ide.eclipse.boot.dash.model.requestmappings.JLRMethodParser.JLRMethod; import org.springframework.ide.eclipse.boot.util.Log; import com.google.common.base.Objects; /** * Abstract implementation of a ActuatorClient. The actuar client connects * to an actuator endpoint retrieving some information from a running spring boot app. * <p> * This implementation is abstract because there is more than one way that we can * connect to an actuator endpoint and retrieve the data from it. The method * to retrieve the data is therefore an abstract method. * * @author Kris De Volder */ public abstract class ActuatorClient { private final TypeLookup typeLookup; public ActuatorClient(TypeLookup typeLookup) { this.typeLookup = typeLookup; } /** * Wraps a (key,value) pair from the json returned from the 'mappings' endpoint in the * actuator. * * @author Kris De Volder */ static class RequestMappingImpl extends AbstractRequestMapping { /* There are two styles of entries: 1) key is a 'path' String. May contain patters like "**" "/** /favicon.ico":{ "bean":"faviconHandlerMapping" } 2) key is a 'almost json' String "{[/bye],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}":{ "bean":"requestMappingHandlerMapping", "method":"public java.lang.String demo.MyController.bye()" } */ private JSONObject beanInfo; private String path; private JLRMethod methodData; RequestMappingImpl(String path, JSONObject beanInfo, TypeLookup typeLookup) { super(typeLookup); this.path = path; this.beanInfo = beanInfo; } @Override public String getPath() { return path; } @Override public String toString() { return "RequestMapping("+path+")"; } @Override public String getFullyQualifiedClassName() { JLRMethod m = getMethodData(); if (m!=null) { return m.getFQClassName(); } return null; } @Override public String getMethodName() { JLRMethod m = getMethodData(); if (m!=null) { return m.getMethodName(); } return null; } /** * Returns the raw string found in the requestmapping info. This is a 'toString' value * of java.lang.reflect.Method object. */ public String getMethodString() { try { if (beanInfo!=null) { if (beanInfo.has("method")) { return beanInfo.getString("method"); } } } catch (Exception e) { BootDashActivator.log(e); } return null; } private JLRMethod getMethodData() { if (methodData==null) { methodData = JLRMethodParser.parse(getMethodString()); } return methodData; } private static Stream<String> processOrPaths(String pathExp) { if (pathExp.contains("||")) { String[] paths = pathExp.split(Pattern.quote("||")); ArrayList<RequestMapping> list = new ArrayList<>(); return Stream.of(paths).map(String::trim); } else { return Stream.of(pathExp); } } private static String extractPath(String key) { if (key.startsWith("{[")) { //Case 2 (see above) //An almost json string. Unfortunately not really json so we can't //use org.json or jackson Mapper to properly parse this. int start = 2; //right after first '[' int end = key.indexOf(']'); if (end>=2) { return key.substring(start, end); } } //Case 1, or some unanticipated stuff. //Assume the key is the path, which is right for Case 1 // and probably more useful than null for 'unanticipated stuff'. return key; } public static Collection<RequestMappingImpl> create(String key, JSONObject value, TypeLookup typeLookup) { return processOrPaths(extractPath(key)) .map(path -> new RequestMappingImpl(path, value, typeLookup)) .collect(Collectors.toList()); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((beanInfo == null) ? 0 : beanInfo.hashCode()); result = prime * result + ((path == null) ? 0 : path.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; RequestMappingImpl other = (RequestMappingImpl) obj; return Objects.equal(this.path, other.path) && Objects.equal(this.getMethodString(), other.getMethodString()); } } @SuppressWarnings("unchecked") private List<RequestMapping> parse(String json) throws JSONException { JSONObject obj = new JSONObject(json); Iterator<String> keys = obj.keys(); List<RequestMapping> result = new ArrayList<>(); while (keys.hasNext()) { String rawKey = keys.next(); JSONObject value = obj.getJSONObject(rawKey); Collection<RequestMappingImpl> mappings = RequestMappingImpl.create(rawKey, value, typeLookup); result.addAll(mappings); } return result; } public List<RequestMapping> getRequestMappings() { try { String json = getRequestMappingData(); if (json!=null) { return parse(json); } } catch (Exception e) { Log.log(e); } return null; } protected abstract String getRequestMappingData() throws Exception; }