diff --git a/fizz-common/src/main/java/we/util/MapUtil.java b/fizz-common/src/main/java/we/util/MapUtil.java index 5d3ea5b..c25e2f4 100644 --- a/fizz-common/src/main/java/we/util/MapUtil.java +++ b/fizz-common/src/main/java/we/util/MapUtil.java @@ -17,6 +17,7 @@ package we.util; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,6 +37,10 @@ import org.springframework.util.MultiValueMap; */ public class MapUtil { + private static final String KEY = "key"; + + private static final String VALUE = "value"; + public static HttpHeaders toHttpHeaders(Map params) { HttpHeaders headers = new HttpHeaders(); @@ -65,7 +70,6 @@ public class MapUtil { return headers; } - public static MultiValueMap toMultiValueMap(Map params) { MultiValueMap mvmap = new LinkedMultiValueMap<>(); @@ -96,7 +100,7 @@ public class MapUtil { return mvmap; } - + public static MultiValueMap toMultipartDataMap(Map params) { MultiValueMap mvmap = new LinkedMultiValueMap<>(); if (params == null || params.isEmpty()) { @@ -124,15 +128,16 @@ public class MapUtil { return mvmap; } - /** * Extract form data from multipart map exclude file + * * @param params * @param fileKeyPrefix - * @param filePartMap Map + * @param filePartMap Map * @return */ - public static Map extractFormData(MultiValueMap params, String fileKeyPrefix, Map filePartMap) { + public static Map extractFormData(MultiValueMap params, String fileKeyPrefix, + Map filePartMap) { HashMap m = new HashMap<>(); if (params == null || params.isEmpty()) { return m; @@ -148,7 +153,7 @@ public class MapUtil { formFieldValues.add(p.value()); } else if (part instanceof FilePart) { FilePart fp = (FilePart) part; - String k = fileKeyPrefix + UUIDUtil.getUUID() + "-" + fp.filename(); + String k = fileKeyPrefix + UUIDUtil.getUUID() + "-" + fp.filename(); formFieldValues.add(k); filePartMap.put(k, fp); } @@ -171,26 +176,28 @@ public class MapUtil { } return m; } - + /** * Replace file field with FilePart object + * * @param params * @param fileKeyPrefix * @param filePartMap */ - public static void replaceWithFilePart(MultiValueMap params, String fileKeyPrefix, Map filePartMap) { + public static void replaceWithFilePart(MultiValueMap params, String fileKeyPrefix, + Map filePartMap) { if (params == null || params.isEmpty() || filePartMap == null || filePartMap.isEmpty()) { return; } - + for (Entry> entry : params.entrySet()) { List list = entry.getValue(); if (list != null && list.size() > 0) { - List newlist = new ArrayList<>(); + List newlist = new ArrayList<>(); for (int i = 0; i < list.size(); i++) { if (list.get(i).toString().startsWith(fileKeyPrefix)) { newlist.add(filePartMap.get(list.get(i).toString())); - }else { + } else { newlist.add(list.get(i)); } } @@ -198,7 +205,7 @@ public class MapUtil { } } } - + public static Map toHashMap(MultiValueMap params) { HashMap m = new HashMap<>(); @@ -219,7 +226,7 @@ public class MapUtil { return m; } - + public static Map headerToHashMap(HttpHeaders headers) { HashMap m = new HashMap<>(); @@ -240,7 +247,7 @@ public class MapUtil { return m; } - + public static Map upperCaseKey(Map m) { HashMap rs = new HashMap<>(); @@ -254,79 +261,96 @@ public class MapUtil { return rs; } + + public static MultiValueMap upperCaseKey(MultiValueMap m) { + MultiValueMap rs = new LinkedMultiValueMap<>(); + + if (m == null || m.isEmpty()) { + return rs; + } + + for (Entry> entry : m.entrySet()) { + rs.put(entry.getKey().toUpperCase(), entry.getValue()); + } + + return rs; + } /** * Set value by path,support multiple levels,eg:a.b.c
* Do NOT use this method if field name contains a dot
- * @param data + * + * @param data * @param path * @param value */ @SuppressWarnings("unchecked") public static void set(Map data, String path, Object value) { String[] fields = path.split("\\."); - if(fields.length < 2) { + if (fields.length < 2) { data.put(path, value); - }else { + } else { Map next = data; for (int i = 0; i < fields.length - 1; i++) { Map val = (Map) next.get(fields[i]); - if(val == null) { + if (val == null) { val = new HashMap<>(); next.put(fields[i], val); } - if(i == fields.length - 2) { - val.put(fields[i+1], value); + if (i == fields.length - 2) { + val.put(fields[i + 1], value); break; } next = val; } } } - + /** * Get value by path, support multiple levels,eg:a.b.c
* Do NOT use this method if field name contains a dot
+ * * @param data * @param path * @return */ public static Object get(Map data, String path) { String[] fields = path.split("\\."); - if(fields.length < 2) { + if (fields.length < 2) { return data.get(path); - }else { + } else { Map next = data; for (int i = 0; i < fields.length - 1; i++) { - if(!(next.get(fields[i]) instanceof Map)) { + if (!(next.get(fields[i]) instanceof Map)) { return null; } Map val = (Map) next.get(fields[i]); - if(val == null) { + if (val == null) { return null; } - if(i == fields.length - 2) { - return val.get(fields[i+1]); + if (i == fields.length - 2) { + return val.get(fields[i + 1]); } next = val; } } return null; } - + /** * Merge maps, merge src to target + * * @param target * @param src * @return */ @SuppressWarnings("unchecked") public static Map merge(Map target, Map src) { - if(src == null || src.isEmpty()) { + if (src == null || src.isEmpty()) { return target; } src.forEach((key, value) -> { - if(value != null) { + if (value != null) { target.merge(key, value, (oldValue, newValue) -> { if (oldValue instanceof Map && newValue instanceof Map) { oldValue = merge((Map) oldValue, (Map) newValue); @@ -338,5 +362,71 @@ public class MapUtil { }); return target; } - + + /** + * Convert list to map and merge multiple values
+ * Example:
+ * List as:
+ * [{"key": "abc", "value": "aaa"},{"key": "abc", "value": "xyz"},{"key": + * "a123", "value": 666}]
+ * Merge Result:
+ * {"abc": ["aaa","xyz"], "a123": 666}
+ * + * @param obj + * @return + */ + @SuppressWarnings("unchecked") + public static Map list2Map(Object obj) { + // Compatible with older version configuration + if (obj instanceof Map) { + return (Map) obj; + } + if (obj instanceof List) { + Map rs = new HashMap<>(); + List> list = (List>) obj; + if (list == null || list.size() == 0) { + return rs; + } + for (Map m : list) { + String k = m.get(KEY).toString(); + Object v = m.get(VALUE); + if (rs.containsKey(k)) { + List vals = null; + if (rs.get(k) instanceof List) { + vals = (List) rs.get(k); + } else { + vals = new ArrayList<>(); + vals.add(rs.get(k)); + } + vals.add(v); + rs.put(k, vals); + } else { + rs.put(k, v); + } + } + return rs; + } + return null; + } + + /** + * Convert list to MultiValueMap
+ * List format example:
+ * [{"key": "abc", "value": "aaa"},{"key": "abc", "value": "xyz"},,{"key": + * "a123", "value": 666}]
+ * + * @param list + * @return + */ + public static MultiValueMap listToMultiValueMap(List> list) { + MultiValueMap mvmap = new LinkedMultiValueMap<>(); + if (list == null || list.size() == 0) { + return mvmap; + } + for (Map m : list) { + mvmap.add(m.get(KEY).toString(), m.get(VALUE)); + } + return mvmap; + } + } diff --git a/fizz-core/src/main/java/we/filter/FilterExceptionHandlerConfig.java b/fizz-core/src/main/java/we/filter/FilterExceptionHandlerConfig.java index aa48502..71d9d10 100644 --- a/fizz-core/src/main/java/we/filter/FilterExceptionHandlerConfig.java +++ b/fizz-core/src/main/java/we/filter/FilterExceptionHandlerConfig.java @@ -32,6 +32,7 @@ import reactor.core.publisher.Mono; import we.exception.ExecuteScriptException; import we.exception.RedirectException; import we.exception.StopAndResponseException; +import we.fizz.exception.FizzRuntimeException; import we.flume.clients.log4j2appender.LogService; import we.legacy.RespEntity; import we.util.JacksonUtils; @@ -81,6 +82,19 @@ public class FilterExceptionHandlerConfig { return resp.writeWith(Mono.just(resp.bufferFactory().wrap(rs.toString().getBytes()))); } } + if (t instanceof FizzRuntimeException) { + FizzRuntimeException ex = (FizzRuntimeException) t; + resp.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + RespEntity rs = null; + String reqId = exchange.getRequest().getId(); + if (ex.getStepContext() != null && ex.getStepContext().returnContext()) { + rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId, ex.getStepContext()); + return resp.writeWith(Mono.just(resp.bufferFactory().wrap(JacksonUtils.writeValueAsString(rs).getBytes()))); + } else { + rs = new RespEntity(HttpStatus.INTERNAL_SERVER_ERROR.value(), t.getMessage(), reqId); + return resp.writeWith(Mono.just(resp.bufferFactory().wrap(rs.toString().getBytes()))); + } + } Mono vm; Object fc = exchange.getAttributes().get(WebUtils.FILTER_CONTEXT); if (fc == null) { // t came from flow control filter diff --git a/fizz-core/src/main/java/we/fizz/exception/FizzRuntimeException.java b/fizz-core/src/main/java/we/fizz/exception/FizzRuntimeException.java index 92643f8..929fe1c 100644 --- a/fizz-core/src/main/java/we/fizz/exception/FizzRuntimeException.java +++ b/fizz-core/src/main/java/we/fizz/exception/FizzRuntimeException.java @@ -1,11 +1,37 @@ package we.fizz.exception; +import we.fizz.StepContext; + public class FizzRuntimeException extends RuntimeException { + + private StepContext stepContext; + public FizzRuntimeException(String message) { super(message); } public FizzRuntimeException(String message, Throwable cause) { super(message, cause); + this.setStackTrace(cause.getStackTrace()); } + + public FizzRuntimeException(String message, StepContext stepContext) { + super(message); + this.stepContext = stepContext; + } + + public FizzRuntimeException(String message, Throwable cause, StepContext stepContext) { + super(message, cause); + this.setStackTrace(cause.getStackTrace()); + this.stepContext = stepContext; + } + + public StepContext getStepContext() { + return stepContext; + } + + public void setStepContext(StepContext stepContext) { + this.stepContext = stepContext; + } + } diff --git a/fizz-core/src/main/java/we/fizz/input/PathMapping.java b/fizz-core/src/main/java/we/fizz/input/PathMapping.java index 91b1a92..a1d22ce 100644 --- a/fizz-core/src/main/java/we/fizz/input/PathMapping.java +++ b/fizz-core/src/main/java/we/fizz/input/PathMapping.java @@ -19,13 +19,13 @@ package we.fizz.input; import java.util.*; import java.util.Map.Entry; -import java.util.Optional; import java.util.stream.Collectors; import org.noear.snack.ONode; import we.constants.CommonConstants; import we.fizz.StepContext; +import we.fizz.exception.FizzRuntimeException; import we.util.MapUtil; /** @@ -105,14 +105,15 @@ public class PathMapping { return target.toObject(Map.class); } + @SuppressWarnings("unchecked") public static ONode transform(ONode ctxNode, Map rules, boolean supportMultiLevels) { ONode target = ONode.load(new HashMap()); if (rules.isEmpty()) { return target; } - Map rs = new HashMap<>(); - Map types = new HashMap<>(); + Map rs = new HashMap<>(); + Map types = new HashMap<>(); for (Entry entry : rules.entrySet()) { if (entry.getValue() instanceof String) { String val = (String) entry.getValue(); @@ -123,6 +124,25 @@ public class PathMapping { } else { rs.put(entry.getKey(), val); } + } else if (entry.getValue() instanceof List) { + List values = (List) entry.getValue(); + List vList = new ArrayList<>(); + List tList = new ArrayList<>(); + for (Object v : values) { + if (v instanceof String) { + String val = (String) v; + Optional optType = typeList.stream().filter(s -> val.startsWith(s + " ")).findFirst(); + if (optType.isPresent()) { + vList.add(val.substring(optType.get().length() + 1)); + tList.add(optType.get()); + } else { + vList.add(val); + tList.add(null); + } + } + } + rs.put(entry.getKey(), vList); + types.put(entry.getKey(), tList); } } @@ -134,8 +154,46 @@ public class PathMapping { Object starValObj = null; String starEntryKey = null; - for (Entry entry : rs.entrySet()) { - String path = entry.getValue(); + for (Entry entry : rs.entrySet()) { + if (entry.getValue() instanceof String) { + String path = (String) entry.getValue(); + String type = (String) types.get(entry.getKey()); + Object obj = getRefValue(ctxNode, type, path); + if (CommonConstants.WILDCARD_STAR.equals(entry.getKey())) { + starValObj = obj; + starEntryKey = entry.getKey(); + } else { + setByPath(target, entry.getKey(), obj, supportMultiLevels); + } + } else if (entry.getValue() instanceof List) { + List refs = (List) entry.getValue(); + List tList = (List) types.get(entry.getKey()); + List refValList = new ArrayList<>(); + for (int i = 0; i < refs.size(); i++) { + String path = refs.get(i); + String type = tList.get(i); + Object obj = getRefValue(ctxNode, type, path); + // Only header form-data and query Parameter support multiple values, merge result into + // one a list + if (obj instanceof List) { + refValList.addAll((List) obj); + } else { + refValList.add(obj); + } + } + setByPath(target, entry.getKey(), refValList, supportMultiLevels); + } + } + + if(starEntryKey != null) { + setByPath(target, starEntryKey, starValObj, supportMultiLevels); + } + return target; + } + + private static Object getRefValue(ONode ctxNode, String type, String path) { + Object obj = null; + try { String p = path; String defaultValue = null; if (path.indexOf("|") != -1) { @@ -143,82 +201,77 @@ public class PathMapping { defaultValue = path.substring(path.indexOf("|") + 1); } ONode val = select(ctxNode, handlePath(p)); - - Object obj = null; if (val != null && !val.isNull()) { obj = val; } else { obj = defaultValue; } - if (obj != null && types.containsKey(entry.getKey())) { - switch (types.get(entry.getKey())) { - case "Integer": - case "int": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getInt(); - } else { - obj = Integer.valueOf(obj.toString()); - } - break; - } - case "Boolean": - case "boolean": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getBoolean(); - } else { - obj = Boolean.valueOf(obj.toString()); - } - break; - } - case "Float": - case "float": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getFloat(); - } else { - obj = Float.valueOf(obj.toString()); - } - break; - } - case "Double": - case "double": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getDouble(); - } else { - obj = Double.valueOf(obj.toString()); - } - break; - } - case "String": - case "string": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getString(); - } - break; - } - case "Long": - case "long": { - if (obj instanceof ONode) { - obj = ((ONode) obj).val().getLong(); - } else { - obj = Long.valueOf(obj.toString()); - } - break; - } - } + if (obj != null && type != null) { + obj = cast(obj, type); } - if (CommonConstants.WILDCARD_STAR.equals(entry.getKey())) { - starValObj = obj; - starEntryKey = entry.getKey(); + } catch (Exception e) { + e.printStackTrace(); + throw new FizzRuntimeException(String.format("path mapping errer: %s , path mapping data: %s %s", e.getMessage(), type, path), e); + } + return obj; + } + + private static Object cast(Object obj, String type) { + switch (type) { + case "Integer": + case "int": { + if (obj instanceof ONode) { + obj = ((ONode) obj).val().getInt(); } else { - setByPath(target, entry.getKey(), obj, supportMultiLevels); + obj = Integer.valueOf(obj.toString()); } + break; } - - if(starEntryKey != null) { - setByPath(target, starEntryKey, starValObj, supportMultiLevels); + case "Boolean": + case "boolean": { + if (obj instanceof ONode) { + obj = ((ONode) obj).val().getBoolean(); + } else { + obj = Boolean.valueOf(obj.toString()); + } + break; } - - return target; + case "Float": + case "float": { + if (obj instanceof ONode) { + obj = ((ONode) obj).val().getFloat(); + } else { + obj = Float.valueOf(obj.toString()); + } + break; + } + case "Double": + case "double": { + if (obj instanceof ONode) { + obj = ((ONode) obj).val().getDouble(); + } else { + obj = Double.valueOf(obj.toString()); + } + break; + } + case "String": + case "string": { + if (obj instanceof ONode) { + obj = ((ONode) obj).val().getString(); + } + break; + } + case "Long": + case "long": { + if (obj instanceof ONode) { + obj = ((ONode) obj).val().getLong(); + } else { + obj = Long.valueOf(obj.toString()); + } + break; + } + } + return obj; } public static ONode select(ONode ctxNode, String path) { @@ -270,7 +323,14 @@ public class PathMapping { } Map rs = new HashMap<>(); for (Entry entry : rules.entrySet()) { - if (!(entry.getValue() instanceof String)) { + if (entry.getValue() instanceof List) { + List values = (List) entry.getValue(); + for (Object v : values) { + if (!(v instanceof String)) { + rs.put(entry.getKey(), v); + } + } + } else if (!(entry.getValue() instanceof String) && entry.getValue() instanceof Map) { rs.put(entry.getKey(), entry.getValue()); } } @@ -418,31 +478,35 @@ public class PathMapping { */ public static Map transform(ONode ctxNode, StepContext stepContext, Map fixed, Map mappingRules, boolean supportMultiLevels) { - if (fixed != null && fixed.containsKey(CommonConstants.WILDCARD_TILDE)) { - Object val = fixed.get(CommonConstants.WILDCARD_TILDE); - fixed = new HashMap<>(); - fixed.put(CommonConstants.WILDCARD_TILDE, val); - } - if (mappingRules != null && mappingRules.containsKey(CommonConstants.WILDCARD_TILDE)) { - Object val = mappingRules.get(CommonConstants.WILDCARD_TILDE); - mappingRules = new HashMap<>(); - mappingRules.put(CommonConstants.WILDCARD_TILDE, val); - } - Map result = new HashMap<>(); - if (fixed != null) { - result.putAll((Map) convertPath(fixed, supportMultiLevels)); - } - if (mappingRules != null) { - // 路径映射 - ONode target = PathMapping.transform(ctxNode, mappingRules, supportMultiLevels); - // 脚本转换 - Map scriptRules = PathMapping.getScriptRules(mappingRules); - Map scriptResult = ScriptHelper.executeScripts(target, scriptRules, ctxNode, stepContext, supportMultiLevels); - if (scriptResult != null && !scriptResult.isEmpty()) { - result = MapUtil.merge(result, scriptResult); + try { + if (fixed != null && fixed.containsKey(CommonConstants.WILDCARD_TILDE)) { + Object val = fixed.get(CommonConstants.WILDCARD_TILDE); + fixed = new HashMap<>(); + fixed.put(CommonConstants.WILDCARD_TILDE, val); } + if (mappingRules != null && mappingRules.containsKey(CommonConstants.WILDCARD_TILDE)) { + Object val = mappingRules.get(CommonConstants.WILDCARD_TILDE); + mappingRules = new HashMap<>(); + mappingRules.put(CommonConstants.WILDCARD_TILDE, val); + } + Map result = new HashMap<>(); + if (fixed != null) { + result.putAll((Map) convertPath(fixed, supportMultiLevels)); + } + if (mappingRules != null) { + // 路径映射 + ONode target = transform(ctxNode, mappingRules, supportMultiLevels); + // 脚本转换 + Map scriptRules = getScriptRules(mappingRules); + Map scriptResult = ScriptHelper.executeScripts(target, scriptRules, ctxNode, stepContext, supportMultiLevels); + if (scriptResult != null && !scriptResult.isEmpty()) { + result = MapUtil.merge(result, scriptResult); + } + } + return result; + }catch(FizzRuntimeException e) { + throw new FizzRuntimeException(e.getMessage(), e, stepContext); } - return result; } public static Map convertPath(Map fixed, boolean supportMultiLevels) { diff --git a/fizz-core/src/main/java/we/fizz/input/extension/request/RequestInput.java b/fizz-core/src/main/java/we/fizz/input/extension/request/RequestInput.java index d848fd7..762714b 100644 --- a/fizz-core/src/main/java/we/fizz/input/extension/request/RequestInput.java +++ b/fizz-core/src/main/java/we/fizz/input/extension/request/RequestInput.java @@ -143,8 +143,8 @@ public class RequestInput extends RPCInput implements IInput{ // headers Map headers = PathMapping.transform(ctxNode, stepContext, - MapUtil.upperCaseKey((Map) requestMapping.get("fixedHeaders")), - MapUtil.upperCaseKey((Map) requestMapping.get("headers")), false); + MapUtil.upperCaseKey(MapUtil.list2Map(requestMapping.get("fixedHeaders"))), + MapUtil.upperCaseKey(MapUtil.list2Map(requestMapping.get("headers"))), false); if (headers.containsKey(CommonConstants.WILDCARD_TILDE) && headers.get(CommonConstants.WILDCARD_TILDE) instanceof Map) { request.put("headers", headers.get(CommonConstants.WILDCARD_TILDE)); @@ -154,8 +154,8 @@ public class RequestInput extends RPCInput implements IInput{ // params params.putAll(PathMapping.transform(ctxNode, stepContext, - (Map) requestMapping.get("fixedParams"), - (Map) requestMapping.get("params"), false)); + MapUtil.list2Map(requestMapping.get("fixedParams")), + MapUtil.list2Map(requestMapping.get("params")), false)); if (params.containsKey(CommonConstants.WILDCARD_TILDE) && params.get(CommonConstants.WILDCARD_TILDE) instanceof Map) { request.put("params", params.get(CommonConstants.WILDCARD_TILDE)); @@ -170,8 +170,8 @@ public class RequestInput extends RPCInput implements IInput{ supportMultiLevels = false; } Map body = PathMapping.transform(ctxNode, stepContext, - (Map) requestMapping.get("fixedBody"), - (Map) requestMapping.get("body"), supportMultiLevels); + MapUtil.list2Map(requestMapping.get("fixedBody")), + MapUtil.list2Map(requestMapping.get("body")), supportMultiLevels); if (body.containsKey(CommonConstants.WILDCARD_TILDE)) { request.put("body", body.get(CommonConstants.WILDCARD_TILDE)); } else {