001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.jsonwebservice.action;
016    
017    import com.liferay.portal.json.data.FileData;
018    import com.liferay.portal.json.transformer.BeanAnalyzerTransformer;
019    import com.liferay.portal.kernel.exception.PortalException;
020    import com.liferay.portal.kernel.javadoc.JavadocManagerUtil;
021    import com.liferay.portal.kernel.javadoc.JavadocMethod;
022    import com.liferay.portal.kernel.json.JSONFactoryUtil;
023    import com.liferay.portal.kernel.json.JSONSerializable;
024    import com.liferay.portal.kernel.json.JSONSerializer;
025    import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceAction;
026    import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceActionMapping;
027    import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceActionsManagerUtil;
028    import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceNaming;
029    import com.liferay.portal.kernel.util.GetterUtil;
030    import com.liferay.portal.kernel.util.MethodParameter;
031    import com.liferay.portal.kernel.util.ParamUtil;
032    import com.liferay.portal.kernel.util.ReleaseInfo;
033    import com.liferay.portal.kernel.util.StringBundler;
034    import com.liferay.portal.kernel.util.StringPool;
035    import com.liferay.portal.kernel.util.StringUtil;
036    import com.liferay.portal.kernel.util.Validator;
037    
038    import java.io.File;
039    import java.io.Serializable;
040    
041    import java.lang.reflect.Method;
042    import java.lang.reflect.Modifier;
043    import java.lang.reflect.ParameterizedType;
044    import java.lang.reflect.Type;
045    
046    import java.util.ArrayList;
047    import java.util.Collection;
048    import java.util.Date;
049    import java.util.HashMap;
050    import java.util.LinkedHashMap;
051    import java.util.List;
052    import java.util.Locale;
053    import java.util.Map;
054    import java.util.TimeZone;
055    
056    import javax.servlet.ServletContext;
057    import javax.servlet.http.HttpServletRequest;
058    
059    import jodd.util.ReflectUtil;
060    
061    /**
062     * @author Igor Spasic
063     * @author Raymond Aug??
064     */
065    public class JSONWebServiceDiscoverAction implements JSONWebServiceAction {
066    
067            public JSONWebServiceDiscoverAction(HttpServletRequest request) {
068                    _basePath = request.getServletPath();
069                    _baseURL = String.valueOf(request.getRequestURL());
070    
071                    ServletContext servletContext = request.getServletContext();
072    
073                    _contextName = GetterUtil.getString(
074                            ParamUtil.getString(
075                                    request, "contextName", servletContext.getServletContextName()),
076                            StringPool.BLANK);
077                    _jsonWebServiceNaming =
078                            JSONWebServiceActionsManagerUtil.getJSONWebServiceNaming();
079            }
080    
081            @Override
082            public JSONWebServiceActionMapping getJSONWebServiceActionMapping() {
083                    return null;
084            }
085    
086            @Override
087            public Object invoke() throws Exception {
088                    Map<String, Object> resultsMap = new LinkedHashMap<>();
089    
090                    resultsMap.put("contextName", _contextName);
091                    resultsMap.put("basePath", _basePath);
092                    resultsMap.put("baseURL", _baseURL);
093                    resultsMap.put("services", _buildJsonWebServiceActionMappingMaps());
094                    resultsMap.put("types", _buildTypes());
095                    resultsMap.put("version", ReleaseInfo.getVersion());
096    
097                    return new DiscoveryContent(resultsMap);
098            }
099    
100            public static class DiscoveryContent implements JSONSerializable {
101    
102                    public DiscoveryContent(Map<String, Object> resultsMap) {
103                            _resultsMap = resultsMap;
104                    }
105    
106                    @Override
107                    public String toJSONString() {
108                            JSONSerializer jsonSerializer =
109                                    JSONFactoryUtil.createJSONSerializer();
110    
111                            jsonSerializer.include("types");
112    
113                            return jsonSerializer.serializeDeep(_resultsMap);
114                    }
115    
116                    private final Map<String, Object> _resultsMap;
117    
118            }
119    
120            private List<Map<String, Object>> _buildJsonWebServiceActionMappingMaps()
121                    throws PortalException {
122    
123                    List<JSONWebServiceActionMapping> jsonWebServiceActionMappings =
124                            JSONWebServiceActionsManagerUtil.getJSONWebServiceActionMappings(
125                                    _contextName);
126    
127                    List<Map<String, Object>> jsonWebServiceActionMappingMaps =
128                            new ArrayList<>(jsonWebServiceActionMappings.size());
129    
130                    for (JSONWebServiceActionMapping jsonWebServiceActionMapping :
131                                    jsonWebServiceActionMappings) {
132    
133                            String path = jsonWebServiceActionMapping.getPath();
134    
135                            Map<String, Object> jsonWebServiceActionMappingMap =
136                                    new LinkedHashMap<>();
137    
138                            if (jsonWebServiceActionMapping.isDeprecated()) {
139                                    jsonWebServiceActionMappingMap.put("deprecated", Boolean.TRUE);
140                            }
141    
142                            JavadocMethod javadocMethod =
143                                    JavadocManagerUtil.lookupJavadocMethod(
144                                            jsonWebServiceActionMapping.getRealActionMethod());
145    
146                            if (javadocMethod != null) {
147                                    String methodComment = javadocMethod.getComment();
148    
149                                    if (methodComment != null) {
150                                            jsonWebServiceActionMappingMap.put(
151                                                    "description", javadocMethod.getComment());
152                                    }
153                            }
154    
155                            jsonWebServiceActionMappingMap.put(
156                                    "method", jsonWebServiceActionMapping.getMethod());
157    
158                            jsonWebServiceActionMappingMap.put(
159                                    "name", _getName(jsonWebServiceActionMapping));
160    
161                            MethodParameter[] methodParameters =
162                                    jsonWebServiceActionMapping.getMethodParameters();
163    
164                            List<Map<String, String>> parametersList = new ArrayList<>(
165                                    methodParameters.length);
166    
167                            for (int i = 0; i < methodParameters.length; i++) {
168                                    MethodParameter methodParameter = methodParameters[i];
169    
170                                    Class<?>[] genericTypes = null;
171    
172                                    try {
173                                            genericTypes = methodParameter.getGenericTypes();
174                                    }
175                                    catch (ClassNotFoundException cnfe) {
176                                            throw new PortalException(cnfe);
177                                    }
178    
179                                    Map<String, String> parameterMap = new HashMap<>();
180    
181                                    if (javadocMethod != null) {
182                                            String parameterComment = javadocMethod.getParameterComment(
183                                                    i);
184    
185                                            if (!Validator.isBlank(parameterComment)) {
186                                                    parameterMap.put("description", parameterComment);
187                                            }
188                                    }
189    
190                                    parameterMap.put("name", methodParameter.getName());
191                                    parameterMap.put(
192                                            "type",
193                                            _formatType(
194                                                    methodParameter.getType(), genericTypes, false));
195    
196                                    parametersList.add(parameterMap);
197                            }
198    
199                            jsonWebServiceActionMappingMap.put("parameters", parametersList);
200    
201                            jsonWebServiceActionMappingMap.put("path", path);
202    
203                            Map<String, String> returnsMap = new LinkedHashMap<>();
204    
205                            if (javadocMethod != null) {
206                                    String returnComment = javadocMethod.getReturnComment();
207    
208                                    if (!Validator.isBlank(returnComment)) {
209                                            returnsMap.put("description", returnComment);
210                                    }
211                            }
212    
213                            Method actionMethod = jsonWebServiceActionMapping.getActionMethod();
214    
215                            returnsMap.put(
216                                    "type",
217                                    _formatType(
218                                            actionMethod.getReturnType(),
219                                            _getGenericReturnTypes(jsonWebServiceActionMapping), true));
220    
221                            jsonWebServiceActionMappingMap.put("returns", returnsMap);
222    
223                            jsonWebServiceActionMappingMaps.add(jsonWebServiceActionMappingMap);
224                    }
225    
226                    return jsonWebServiceActionMappingMaps;
227            }
228    
229            private List<Map<String, String>> _buildPropertiesList(Class<?> type) {
230                    try {
231                            BeanAnalyzerTransformer beanAnalyzerTransformer =
232                                    new BeanAnalyzerTransformer(type) {
233    
234                                            @Override
235                                            protected String getTypeName(Class<?> type) {
236                                                    return _formatType(type, null, false);
237                                            }
238    
239                                    };
240    
241                            return beanAnalyzerTransformer.collect();
242                    }
243                    catch (Exception e) {
244                            return null;
245                    }
246            }
247    
248            private List<Map<String, Object>> _buildTypes() {
249                    List<Map<String, Object>> types = new ArrayList<>();
250    
251                    for (int i = 0; i < _types.size(); i++) {
252                            Class<?> type = _types.get(i);
253    
254                            Map<String, Object> map = new LinkedHashMap<>();
255    
256                            types.add(map);
257    
258                            Class<?> modelType = type;
259    
260                            if (type.isInterface()) {
261                                    try {
262                                            Class<?> clazz = getClass();
263    
264                                            ClassLoader classLoader = clazz.getClassLoader();
265    
266                                            String modelImplClassName =
267                                                    _jsonWebServiceNaming.convertModelClassToImplClassName(
268                                                            type);
269    
270                                            modelType = classLoader.loadClass(modelImplClassName);
271                                    }
272                                    catch (ClassNotFoundException cnfe) {
273                                    }
274                            }
275    
276                            if (modelType.isInterface() ||
277                                    Modifier.isAbstract(modelType.getModifiers())) {
278    
279                                    map.put("interface", Boolean.TRUE);
280                            }
281    
282                            List<Map<String, String>> propertiesList = _buildPropertiesList(
283                                    modelType);
284    
285                            if (propertiesList != null) {
286                                    map.put("properties", propertiesList);
287                            }
288    
289                            map.put("type", type.getName());
290                    }
291    
292                    return types;
293            }
294    
295            private String _formatType(
296                    Class<?> type, Class<?>[] genericTypes, boolean returnType) {
297    
298                    if (type.isArray()) {
299                            Class<?> componentType = type.getComponentType();
300    
301                            return _formatType(componentType, genericTypes, returnType) + "[]";
302                    }
303    
304                    if (type.isPrimitive()) {
305                            return type.getSimpleName();
306                    }
307    
308                    if (type.equals(Boolean.class)) {
309                            return "boolean";
310                    }
311                    else if (type.equals(Class.class)) {
312                            if (!returnType) {
313                                    return "string";
314                            }
315                    }
316                    else if (type.equals(Date.class)) {
317                            return "long";
318                    }
319                    else if (type.equals(File.class)) {
320                            if (!returnType) {
321                                    return "file";
322                            }
323                            else {
324                                    type = FileData.class;
325                            }
326                    }
327                    else if (type.equals(Locale.class) || type.equals(String.class) ||
328                                     type.equals(TimeZone.class)) {
329    
330                            return "string";
331                    }
332                    else if (type.equals(Object.class) || type.equals(Serializable.class)) {
333                            return "map";
334                    }
335                    else if (ReflectUtil.isTypeOf(type, Number.class)) {
336                            String typeName = null;
337    
338                            if (type == Character.class) {
339                                    typeName = "char";
340                            }
341                            else if (type == Integer.class) {
342                                    typeName = "int";
343                            }
344                            else {
345                                    typeName = StringUtil.toLowerCase(type.getSimpleName());
346                            }
347    
348                            return typeName;
349                    }
350    
351                    String typeName = type.getName();
352    
353                    if ((type == Collection.class) ||
354                            ReflectUtil.isTypeOf(type, List.class)) {
355    
356                            typeName = "list";
357                    }
358                    else if (ReflectUtil.isTypeOf(type, Map.class)) {
359                            typeName = "map";
360                    }
361                    else {
362                            if (!_types.contains(type)) {
363                                    _types.add(type);
364                            }
365                    }
366    
367                    if (genericTypes == null) {
368                            return typeName;
369                    }
370    
371                    StringBundler sb = new StringBundler(genericTypes.length * 2 + 1);
372    
373                    sb.append(StringPool.LESS_THAN);
374    
375                    for (int i = 0; i < genericTypes.length; i++) {
376                            Class<?> genericType = genericTypes[i];
377    
378                            if (i != 0) {
379                                    sb.append(StringPool.COMMA);
380                            }
381    
382                            if (genericType == null) {
383                                    sb.append(StringPool.STAR);
384                            }
385                            else {
386                                    sb.append(_formatType(genericType, null, returnType));
387                            }
388                    }
389    
390                    sb.append(StringPool.GREATER_THAN);
391    
392                    return typeName + sb.toString();
393            }
394    
395            private Class<?>[] _getGenericReturnTypes(
396                    JSONWebServiceActionMapping jsonWebServiceActionMapping) {
397    
398                    Method realActionMethod =
399                            jsonWebServiceActionMapping.getRealActionMethod();
400    
401                    Type genericReturnType = realActionMethod.getGenericReturnType();
402    
403                    if (!(genericReturnType instanceof ParameterizedType)) {
404                            return null;
405                    }
406    
407                    ParameterizedType parameterizedType =
408                            (ParameterizedType)genericReturnType;
409    
410                    Type[] genericTypes = parameterizedType.getActualTypeArguments();
411    
412                    Class<?>[] genericReturnTypes = new Class[genericTypes.length];
413    
414                    for (int i = 0; i < genericTypes.length; i++) {
415                            Type genericType = genericTypes[i];
416    
417                            genericReturnTypes[i] = ReflectUtil.getRawType(
418                                    genericType, jsonWebServiceActionMapping.getActionClass());
419                    }
420    
421                    return genericReturnTypes;
422            }
423    
424            private String _getName(
425                    JSONWebServiceActionMapping jsonWebServiceActionMapping) {
426    
427                    Class<?> clazz = jsonWebServiceActionMapping.getActionClass();
428    
429                    String className =
430                            _jsonWebServiceNaming.convertServiceClassToSimpleName(clazz);
431    
432                    Method method = jsonWebServiceActionMapping.getRealActionMethod();
433    
434                    return className.concat(StringPool.POUND).concat(method.getName());
435            }
436    
437            private final String _basePath;
438            private final String _baseURL;
439            private final String _contextName;
440            private final JSONWebServiceNaming _jsonWebServiceNaming;
441            private final List<Class<?>> _types = new ArrayList<>();
442    
443    }