001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package io.jboot.web.json;
017
018import com.alibaba.fastjson.JSON;
019import com.alibaba.fastjson.JSONArray;
020import com.alibaba.fastjson.JSONObject;
021import com.jfinal.aop.Interceptor;
022import com.jfinal.aop.Invocation;
023import com.jfinal.core.ActionException;
024import com.jfinal.core.Controller;
025import com.jfinal.kit.LogKit;
026import com.jfinal.render.RenderManager;
027import io.jboot.aop.InterceptorBuilder;
028import io.jboot.aop.Interceptors;
029import io.jboot.aop.annotation.AutoLoad;
030import io.jboot.utils.ClassUtil;
031import io.jboot.utils.ObjectUtil;
032import io.jboot.utils.StrUtil;
033import io.jboot.web.controller.JbootController;
034
035import java.lang.reflect.*;
036import java.util.Collection;
037import java.util.HashSet;
038import java.util.Map;
039import java.util.Set;
040
041@AutoLoad
042public class JsonBodyParseInterceptor implements Interceptor, InterceptorBuilder {
043
044    private static final String startOfArray = "[";
045    private static final String endOfArray = "]";
046
047    @Override
048    public void intercept(Invocation inv) {
049
050        Controller controller = inv.getController();
051        Method method = inv.getMethod();
052        Parameter[] parameters = method.getParameters();
053        Type[] paraTypes = method.getGenericParameterTypes();
054
055        Object jsonObjectOrArray = StrUtil.isBlank(controller.getRawData()) ? null : JSON.parse(controller.getRawData());
056
057        for (int index = 0; index < parameters.length; index++) {
058            JsonBody jsonBody = parameters[index].getAnnotation(JsonBody.class);
059            if (jsonBody != null) {
060                Class<?> paraClass = parameters[index].getType();
061                Object result = null;
062                try {
063                    Type paraType = paraTypes[index];
064                    if (paraType instanceof TypeVariable) {
065                        Type variableRawType = getTypeVariableRawType(controller.getClass(), ((TypeVariable<?>) paraType));
066                        if (variableRawType != null) {
067                            paraClass = (Class<?>) variableRawType;
068                            paraType = variableRawType;
069                        }
070                    }
071                    result = parseJsonBody(jsonObjectOrArray, paraClass, paraType, jsonBody.value());
072                } catch (Exception e) {
073                    String message = "Can not parse \"" + parameters[index].getType()
074                            + "\" in method " + ClassUtil.buildMethodString(method) + ", Cause: " + e.getMessage();
075                    if (jsonBody.skipConvertError()) {
076                        LogKit.error(message);
077                    } else {
078                        throw new ActionException(400, RenderManager.me().getRenderFactory().getErrorRender(400), message);
079                    }
080                }
081
082                inv.setArg(index, result);
083            }
084        }
085
086        inv.invoke();
087    }
088
089
090    /**
091     * 获取方法里的泛型参数 T 对于的真实的 Class 类
092     *
093     * @param defClass
094     * @param typeVariable
095     * @return
096     */
097    private static Type getTypeVariableRawType(Class<?> defClass, TypeVariable<?> typeVariable) {
098        Type type = defClass.getGenericSuperclass();
099        if (type instanceof ParameterizedType) {
100            Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
101            if (typeArguments.length == 1) {
102                return typeArguments[0];
103            } else if (typeArguments.length > 1) {
104                TypeVariable<?>[] typeVariables = typeVariable.getGenericDeclaration().getTypeParameters();
105                for (int i = 0; i < typeVariables.length; i++) {
106                    if (typeVariable.getName().equals(typeVariables[i].getName())) {
107                        return typeArguments[i];
108                    }
109                }
110            }
111        }
112        return null;
113    }
114
115
116    public static Object parseJsonBody(Object jsonObjectOrArray, Class<?> paraClass, Type paraType, String jsonKey) throws InstantiationException, IllegalAccessException {
117        if (jsonObjectOrArray == null) {
118            return paraClass.isPrimitive() ? ObjectUtil.getPrimitiveDefaultValue(paraClass) : null;
119        }
120        if (Collection.class.isAssignableFrom(paraClass) || paraClass.isArray()) {
121            return parseArray(jsonObjectOrArray, paraClass, paraType, jsonKey);
122        } else {
123            return parseObject((JSONObject) jsonObjectOrArray, paraClass, paraType, jsonKey);
124        }
125    }
126
127
128    private static Object parseObject(JSONObject rawObject, Class<?> paraClass, Type paraType, String jsonKey) throws IllegalAccessException, InstantiationException {
129        if (StrUtil.isBlank(jsonKey)) {
130            return toJavaObject(rawObject, paraClass, paraType);
131        }
132
133        Object result = null;
134        String[] keys = jsonKey.split("\\.");
135        for (int i = 0; i < keys.length; i++) {
136            if (rawObject != null && !rawObject.isEmpty()) {
137                String key = keys[i].trim();
138                if (StrUtil.isNotBlank(key)) {
139                    //the last
140                    if (i == keys.length - 1) {
141                        if (key.endsWith(endOfArray) && key.contains(startOfArray)) {
142                            String realKey = key.substring(0, key.indexOf(startOfArray));
143                            JSONArray jarray = rawObject.getJSONArray(realKey.trim());
144                            if (jarray != null && jarray.size() > 0) {
145                                String arrayString = key.substring(key.indexOf(startOfArray) + 1, key.length() - 1);
146                                int arrayIndex = StrUtil.isBlank(arrayString) ? 0 : Integer.parseInt(arrayString.trim());
147                                result = arrayIndex >= jarray.size() ? null : jarray.get(arrayIndex);
148                            }
149                        } else {
150                            result = rawObject.get(key);
151                        }
152                    }
153                    //not last
154                    else {
155                        rawObject = getJSONObjectByKey(rawObject, key);
156                    }
157                }
158            }
159        }
160
161        if (result == null || StrUtil.EMPTY.equals(result)) {
162            return paraClass.isPrimitive() ? ObjectUtil.getPrimitiveDefaultValue(paraClass) : null;
163        }
164
165        if (paraClass == String.class && paraClass == paraType) {
166            return result.toString();
167        }
168
169        // JSONObject 类型
170        if (result instanceof JSONObject) {
171            return toJavaObject((JSONObject) result, paraClass, paraType);
172        }
173
174        return ObjectUtil.convert(result, paraClass);
175    }
176
177
178    private static Object parseArray(Object rawJsonObjectOrArray, Class<?> typeClass, Type type, String jsonKey) {
179        JSONArray jsonArray = null;
180        if (StrUtil.isBlank(jsonKey)) {
181            if (rawJsonObjectOrArray instanceof JSONArray) {
182                jsonArray = (JSONArray) rawJsonObjectOrArray;
183            }
184        } else {
185            if (rawJsonObjectOrArray instanceof JSONObject) {
186                JSONObject rawObject = (JSONObject) rawJsonObjectOrArray;
187                String[] keys = jsonKey.split("\\.");
188                for (int i = 0; i < keys.length; i++) {
189                    if (rawObject == null || rawObject.isEmpty()) {
190                        break;
191                    }
192                    String key = keys[i].trim();
193                    if (StrUtil.isNotBlank(key)) {
194                        //the last
195                        if (i == keys.length - 1) {
196                            if (key.endsWith(endOfArray) && key.contains(startOfArray)) {
197                                String realKey = key.substring(0, key.indexOf(startOfArray));
198                                JSONArray jarray = rawObject.getJSONArray(realKey.trim());
199                                if (jarray == null || jarray.isEmpty()) {
200                                    return null;
201                                }
202                                String subKey = key.substring(key.indexOf(startOfArray) + 1, key.length() - 1).trim();
203                                if (StrUtil.isBlank(subKey)) {
204                                    throw new IllegalStateException("Sub key can not empty: " + jsonKey);
205                                }
206
207                                JSONArray newJsonArray = new JSONArray();
208                                for (int j = 0; j < jarray.size(); j++) {
209                                    Object value = jarray.getJSONObject(j).get(subKey);
210                                    if (value != null) {
211                                        newJsonArray.add(value);
212                                    }
213                                }
214                                jsonArray = newJsonArray;
215                            } else {
216                                jsonArray = rawObject.getJSONArray(key);
217                            }
218                        }
219                        //not last
220                        else {
221                            rawObject = getJSONObjectByKey(rawObject, key);
222                        }
223                    }
224                }
225            }
226        }
227
228        if (jsonArray == null || jsonArray.isEmpty()) {
229            return null;
230        }
231
232        //非泛型 set
233        if ((typeClass == Set.class || typeClass == HashSet.class) && typeClass == type) {
234            return new HashSet<>(jsonArray);
235        }
236
237        //直接获取 JsonArray
238        if (typeClass == type && typeClass == JSONArray.class) {
239            return jsonArray;
240        }
241
242        return jsonArray.toJavaObject(type);
243    }
244
245
246    private static JSONObject getJSONObjectByKey(JSONObject jsonObject, String key) {
247        if (key.endsWith(endOfArray) && key.contains(startOfArray)) {
248            String realKey = key.substring(0, key.indexOf(startOfArray));
249            JSONArray jarray = jsonObject.getJSONArray(realKey.trim());
250            if (jarray == null || jarray.isEmpty()) {
251                return null;
252            }
253            String arrayString = key.substring(key.indexOf(startOfArray) + 1, key.length() - 1);
254            int arrayIndex = StrUtil.isBlank(arrayString) ? 0 : Integer.parseInt(arrayString.trim());
255            return arrayIndex >= jarray.size() ? null : jarray.getJSONObject(arrayIndex);
256        } else {
257            return jsonObject.getJSONObject(key);
258        }
259    }
260
261
262    private static Object toJavaObject(JSONObject rawObject, Class<?> paraClass, Type paraType) throws IllegalAccessException, InstantiationException {
263        if (rawObject.isEmpty()) {
264            return paraClass.isPrimitive() ? ObjectUtil.getPrimitiveDefaultValue(paraClass) : null;
265        }
266
267        //非泛型 的 map
268        if ((paraClass == Map.class || paraClass == JSONObject.class) && paraClass == paraType) {
269            return rawObject;
270        }
271
272        //非泛型 的 map
273        if (Map.class.isAssignableFrom(paraClass) && paraClass == paraType && canNewInstance(paraClass)) {
274            Map map = (Map) paraClass.newInstance();
275            map.putAll(rawObject);
276            return map;
277        }
278
279        return rawObject.toJavaObject(paraType);
280    }
281
282
283    private static boolean canNewInstance(Class<?> clazz) {
284        int modifiers = clazz.getModifiers();
285        return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers);
286    }
287
288
289    @Override
290    public void build(Class<?> targetClass, Method method, Interceptors interceptors) {
291        if (Util.isController(targetClass)) {
292            Parameter[] parameters = method.getParameters();
293            if (parameters != null && parameters.length > 0) {
294                for (Parameter p : parameters) {
295                    if (p.getAnnotation(JsonBody.class) != null) {
296                        Class<?> typeClass = p.getType();
297                        if ((Map.class.isAssignableFrom(typeClass) || Collection.class.isAssignableFrom(typeClass) || typeClass.isArray())
298                                && !JbootController.class.isAssignableFrom(targetClass)) {
299                            throw new IllegalArgumentException("Can not use @JsonBody for Map/List(Collection)/Array type if your controller not extends JbootController, method: " + ClassUtil.buildMethodString(method));
300                        }
301
302                        interceptors.addIfNotExist(this);
303                        return;
304                    }
305                }
306            }
307        }
308    }
309}