001    /**
002     * Copyright (c) 2000-2013 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.action;
016    
017    import com.liferay.portal.kernel.json.JSONArray;
018    import com.liferay.portal.kernel.json.JSONException;
019    import com.liferay.portal.kernel.json.JSONFactoryUtil;
020    import com.liferay.portal.kernel.json.JSONObject;
021    import com.liferay.portal.kernel.json.JSONSerializable;
022    import com.liferay.portal.kernel.json.JSONSerializer;
023    import com.liferay.portal.kernel.log.Log;
024    import com.liferay.portal.kernel.log.LogFactoryUtil;
025    import com.liferay.portal.kernel.util.ArrayUtil;
026    import com.liferay.portal.kernel.util.GetterUtil;
027    import com.liferay.portal.kernel.util.LocalizationUtil;
028    import com.liferay.portal.kernel.util.ParamUtil;
029    import com.liferay.portal.kernel.util.SetUtil;
030    import com.liferay.portal.kernel.util.StringBundler;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.StringUtil;
033    import com.liferay.portal.kernel.util.Validator;
034    import com.liferay.portal.service.ServiceContext;
035    import com.liferay.portal.service.ServiceContextUtil;
036    import com.liferay.portal.struts.JSONAction;
037    import com.liferay.portal.util.ClassLoaderUtil;
038    import com.liferay.portal.util.PropsValues;
039    
040    import java.lang.reflect.Method;
041    import java.lang.reflect.Type;
042    
043    import java.util.Arrays;
044    import java.util.Date;
045    import java.util.HashMap;
046    import java.util.Map;
047    import java.util.Set;
048    import java.util.regex.Matcher;
049    import java.util.regex.Pattern;
050    
051    import javax.servlet.http.HttpServletRequest;
052    import javax.servlet.http.HttpServletResponse;
053    
054    import org.apache.struts.action.ActionForm;
055    import org.apache.struts.action.ActionMapping;
056    
057    /**
058     * @author Brian Wing Shun Chan
059     * @author Karthik Sudarshan
060     * @author Julio Camarero
061     * @author Eduardo Lundgren
062     */
063    public class JSONServiceAction extends JSONAction {
064    
065            public JSONServiceAction() {
066                    _invalidClassNames = SetUtil.fromArray(
067                            PropsValues.JSON_SERVICE_INVALID_CLASS_NAMES);
068    
069                    if (_log.isDebugEnabled()) {
070                            for (String invalidClassName : _invalidClassNames) {
071                                    _log.debug("Invalid class name " + invalidClassName);
072                            }
073                    }
074            }
075    
076            @Override
077            public String getJSON(
078                            ActionMapping actionMapping, ActionForm actionForm,
079                            HttpServletRequest request, HttpServletResponse response)
080                    throws Exception {
081    
082                    String className = ParamUtil.getString(request, "serviceClassName");
083                    String methodName = ParamUtil.getString(request, "serviceMethodName");
084    
085                    String[] serviceParameters = getStringArrayFromJSON(
086                            request, "serviceParameters");
087                    String[] serviceParameterTypes = getStringArrayFromJSON(
088                            request, "serviceParameterTypes");
089    
090                    if (!isValidRequest(request)) {
091                            return null;
092                    }
093    
094                    ClassLoader contextClassLoader =
095                            ClassLoaderUtil.getContextClassLoader();
096    
097                    Class<?> clazz = contextClassLoader.loadClass(className);
098    
099                    Object[] methodAndParameterTypes = getMethodAndParameterTypes(
100                            clazz, methodName, serviceParameters, serviceParameterTypes);
101    
102                    if (methodAndParameterTypes == null) {
103                            return null;
104                    }
105    
106                    Method method = (Method)methodAndParameterTypes[0];
107                    Type[] parameterTypes = (Type[])methodAndParameterTypes[1];
108                    Object[] args = new Object[serviceParameters.length];
109    
110                    for (int i = 0; i < serviceParameters.length; i++) {
111                            args[i] = getArgValue(
112                                    request, clazz, methodName, serviceParameters[i],
113                                    parameterTypes[i]);
114                    }
115    
116                    try {
117                            if (_log.isDebugEnabled()) {
118                                    _log.debug(
119                                            "Invoking " + clazz + " on method " + method.getName() +
120                                                    " with args " + Arrays.toString(args));
121                            }
122    
123                            Object returnObj = method.invoke(clazz, args);
124    
125                            if (returnObj != null) {
126                                    return getReturnValue(returnObj);
127                            }
128                            else {
129                                    return JSONFactoryUtil.getNullJSON();
130                            }
131                    }
132                    catch (Exception e) {
133                            if (_log.isDebugEnabled()) {
134                                    _log.debug(
135                                            "Invoked " + clazz + " on method " + method.getName() +
136                                                    " with args " + Arrays.toString(args),
137                                            e);
138                            }
139    
140                            return JSONFactoryUtil.serializeException(e);
141                    }
142            }
143    
144            protected Object getArgValue(
145                            HttpServletRequest request, Class<?> clazz, String methodName,
146                            String parameter, Type parameterType)
147                    throws Exception {
148    
149                    String typeNameOrClassDescriptor = getTypeNameOrClassDescriptor(
150                            parameterType);
151    
152                    String value = ParamUtil.getString(request, parameter);
153    
154                    if (Validator.isNull(value) &&
155                            !typeNameOrClassDescriptor.equals("[Ljava.lang.String;")) {
156    
157                            return null;
158                    }
159                    else if (typeNameOrClassDescriptor.equals("boolean") ||
160                                     typeNameOrClassDescriptor.equals(Boolean.class.getName())) {
161    
162                            return Boolean.valueOf(ParamUtil.getBoolean(request, parameter));
163                    }
164                    else if (typeNameOrClassDescriptor.equals("double") ||
165                                     typeNameOrClassDescriptor.equals(Double.class.getName())) {
166    
167                            return new Double(ParamUtil.getDouble(request, parameter));
168                    }
169                    else if (typeNameOrClassDescriptor.equals("int") ||
170                                     typeNameOrClassDescriptor.equals(Integer.class.getName())) {
171    
172                            return new Integer(ParamUtil.getInteger(request, parameter));
173                    }
174                    else if (typeNameOrClassDescriptor.equals("long") ||
175                                     typeNameOrClassDescriptor.equals(Long.class.getName())) {
176    
177                            return new Long(ParamUtil.getLong(request, parameter));
178                    }
179                    else if (typeNameOrClassDescriptor.equals("short") ||
180                                     typeNameOrClassDescriptor.equals(Short.class.getName())) {
181    
182                            return new Short(ParamUtil.getShort(request, parameter));
183                    }
184                    else if (typeNameOrClassDescriptor.equals(Date.class.getName())) {
185                            return new Date(ParamUtil.getLong(request, parameter));
186                    }
187                    else if (typeNameOrClassDescriptor.equals(
188                                            ServiceContext.class.getName())) {
189    
190                            JSONObject jsonObject = JSONFactoryUtil.createJSONObject(value);
191    
192                            jsonObject.put("javaClass", ServiceContext.class.getName());
193    
194                            return ServiceContextUtil.deserialize(jsonObject);
195                    }
196                    else if (typeNameOrClassDescriptor.equals(String.class.getName())) {
197                            return value;
198                    }
199                    else if (typeNameOrClassDescriptor.equals("[Z")) {
200                            return ParamUtil.getBooleanValues(request, parameter);
201                    }
202                    else if (typeNameOrClassDescriptor.equals("[D")) {
203                            return ParamUtil.getDoubleValues(request, parameter);
204                    }
205                    else if (typeNameOrClassDescriptor.equals("[F")) {
206                            return ParamUtil.getFloatValues(request, parameter);
207                    }
208                    else if (typeNameOrClassDescriptor.equals("[I")) {
209                            return ParamUtil.getIntegerValues(request, parameter);
210                    }
211                    else if (typeNameOrClassDescriptor.equals("[J")) {
212                            return ParamUtil.getLongValues(request, parameter);
213                    }
214                    else if (typeNameOrClassDescriptor.equals("[S")) {
215                            return ParamUtil.getShortValues(request, parameter);
216                    }
217                    else if (typeNameOrClassDescriptor.equals("[Ljava.lang.String;")) {
218                            return ParamUtil.getParameterValues(request, parameter);
219                    }
220                    else if (typeNameOrClassDescriptor.equals("[[Z")) {
221                            String[] values = request.getParameterValues(parameter);
222    
223                            if (ArrayUtil.isNotEmpty(values)) {
224                                    String[] values0 = StringUtil.split(values[0]);
225    
226                                    boolean[][] doubleArray =
227                                            new boolean[values.length][values0.length];
228    
229                                    for (int i = 0; i < values.length; i++) {
230                                            String[] curValues = StringUtil.split(values[i]);
231    
232                                            for (int j = 0; j < curValues.length; j++) {
233                                                    doubleArray[i][j] = GetterUtil.getBoolean(curValues[j]);
234                                            }
235                                    }
236    
237                                    return doubleArray;
238                            }
239                            else {
240                                    return new boolean[0][0];
241                            }
242                    }
243                    else if (typeNameOrClassDescriptor.equals("[[D")) {
244                            String[] values = request.getParameterValues(parameter);
245    
246                            if (ArrayUtil.isNotEmpty(values)) {
247                                    String[] values0 = StringUtil.split(values[0]);
248    
249                                    double[][] doubleArray =
250                                            new double[values.length][values0.length];
251    
252                                    for (int i = 0; i < values.length; i++) {
253                                            String[] curValues = StringUtil.split(values[i]);
254    
255                                            for (int j = 0; j < curValues.length; j++) {
256                                                    doubleArray[i][j] = GetterUtil.getDouble(curValues[j]);
257                                            }
258                                    }
259    
260                                    return doubleArray;
261                            }
262                            else {
263                                    return new double[0][0];
264                            }
265                    }
266                    else if (typeNameOrClassDescriptor.equals("[[F")) {
267                            String[] values = request.getParameterValues(parameter);
268    
269                            if (ArrayUtil.isNotEmpty(values)) {
270                                    String[] values0 = StringUtil.split(values[0]);
271    
272                                    float[][] doubleArray =
273                                            new float[values.length][values0.length];
274    
275                                    for (int i = 0; i < values.length; i++) {
276                                            String[] curValues = StringUtil.split(values[i]);
277    
278                                            for (int j = 0; j < curValues.length; j++) {
279                                                    doubleArray[i][j] = GetterUtil.getFloat(curValues[j]);
280                                            }
281                                    }
282    
283                                    return doubleArray;
284                            }
285                            else {
286                                    return new float[0][0];
287                            }
288                    }
289                    else if (typeNameOrClassDescriptor.equals("[[I")) {
290                            String[] values = request.getParameterValues(parameter);
291    
292                            if (ArrayUtil.isNotEmpty(values)) {
293                                    String[] values0 = StringUtil.split(values[0]);
294    
295                                    int[][] doubleArray = new int[values.length][values0.length];
296    
297                                    for (int i = 0; i < values.length; i++) {
298                                            String[] curValues = StringUtil.split(values[i]);
299    
300                                            for (int j = 0; j < curValues.length; j++) {
301                                                    doubleArray[i][j] = GetterUtil.getInteger(curValues[j]);
302                                            }
303                                    }
304    
305                                    return doubleArray;
306                            }
307                            else {
308                                    return new int[0][0];
309                            }
310                    }
311                    else if (typeNameOrClassDescriptor.equals("[[J")) {
312                            String[] values = request.getParameterValues(parameter);
313    
314                            if (ArrayUtil.isNotEmpty(values)) {
315                                    String[] values0 = StringUtil.split(values[0]);
316    
317                                    long[][] doubleArray = new long[values.length][values0.length];
318    
319                                    for (int i = 0; i < values.length; i++) {
320                                            String[] curValues = StringUtil.split(values[i]);
321    
322                                            for (int j = 0; j < curValues.length; j++) {
323                                                    doubleArray[i][j] = GetterUtil.getLong(curValues[j]);
324                                            }
325                                    }
326    
327                                    return doubleArray;
328                            }
329                            else {
330                                    return new long[0][0];
331                            }
332                    }
333                    else if (typeNameOrClassDescriptor.equals("[[S")) {
334                            String[] values = request.getParameterValues(parameter);
335    
336                            if (ArrayUtil.isNotEmpty(values)) {
337                                    String[] values0 = StringUtil.split(values[0]);
338    
339                                    short[][] doubleArray =
340                                            new short[values.length][values0.length];
341    
342                                    for (int i = 0; i < values.length; i++) {
343                                            String[] curValues = StringUtil.split(values[i]);
344    
345                                            for (int j = 0; j < curValues.length; j++) {
346                                                    doubleArray[i][j] = GetterUtil.getShort(curValues[j]);
347                                            }
348                                    }
349    
350                                    return doubleArray;
351                            }
352                            else {
353                                    return new short[0][0];
354                            }
355                    }
356                    else if (typeNameOrClassDescriptor.equals("[[Ljava.lang.String")) {
357                            String[] values = request.getParameterValues(parameter);
358    
359                            if (ArrayUtil.isNotEmpty(values)) {
360                                    String[] values0 = StringUtil.split(values[0]);
361    
362                                    String[][] doubleArray =
363                                            new String[values.length][values0.length];
364    
365                                    for (int i = 0; i < values.length; i++) {
366                                            doubleArray[i] = StringUtil.split(values[i]);
367                                    }
368    
369                                    return doubleArray;
370                            }
371                            else {
372                                    return new String[0][0];
373                            }
374                    }
375                    else if (typeNameOrClassDescriptor.equals(
376                                            "java.util.Map<java.util.Locale, java.lang.String>")) {
377    
378                            JSONObject jsonObject = JSONFactoryUtil.createJSONObject(value);
379    
380                            return LocalizationUtil.deserialize(jsonObject);
381                    }
382                    else {
383                            try {
384                                    return JSONFactoryUtil.looseDeserializeSafe(value);
385                            }
386                            catch (Exception e) {
387                                    _log.error(
388                                            "Unsupported parameter type for class " + clazz +
389                                                    ", method " + methodName + ", parameter " + parameter +
390                                                            ", and type " + typeNameOrClassDescriptor);
391    
392                                    return null;
393                            }
394                    }
395            }
396    
397            protected Object[] getMethodAndParameterTypes(
398                            Class<?> clazz, String methodName, String[] parameters,
399                            String[] parameterTypes)
400                    throws Exception {
401    
402                    StringBundler sb = new StringBundler(5);
403    
404                    sb.append(clazz.getName());
405                    sb.append("_METHOD_NAME_");
406                    sb.append(methodName);
407                    sb.append("_PARAMETERS_");
408    
409                    String parameterTypesNames = StringUtil.merge(parameterTypes);
410    
411                    if (Validator.isNull(parameterTypesNames)) {
412                            sb.append(parameters.length);
413                    }
414                    else {
415                            sb.append(parameterTypesNames);
416                    }
417    
418                    String key = sb.toString();
419    
420                    Object[] methodAndParameterTypes = _methodCache.get(key);
421    
422                    if (methodAndParameterTypes != null) {
423                            return methodAndParameterTypes;
424                    }
425    
426                    Method method = null;
427                    Type[] methodParameterTypes = null;
428    
429                    Method[] methods = clazz.getMethods();
430    
431                    for (Method curMethod : methods) {
432                            if (curMethod.getName().equals(methodName)) {
433                                    Type[] curParameterTypes = curMethod.getGenericParameterTypes();
434    
435                                    if (curParameterTypes.length == parameters.length) {
436                                            if ((parameterTypes.length > 0) &&
437                                                    (parameterTypes.length == curParameterTypes.length)) {
438    
439                                                    boolean match = true;
440    
441                                                    for (int j = 0; j < parameterTypes.length; j++) {
442                                                            String t1 = parameterTypes[j];
443                                                            String t2 = getTypeNameOrClassDescriptor(
444                                                                    curParameterTypes[j]);
445    
446                                                            if (!t1.equals(t2)) {
447                                                                    match = false;
448                                                            }
449                                                    }
450    
451                                                    if (match) {
452                                                            method = curMethod;
453                                                            methodParameterTypes = curParameterTypes;
454    
455                                                            break;
456                                                    }
457                                            }
458                                            else if (method != null) {
459                                                    String parametersString = StringUtil.merge(parameters);
460    
461                                                    _log.error(
462                                                            "Obscure method name for class " + clazz +
463                                                                    ", method " + methodName + ", and parameters " +
464                                                                            parametersString);
465    
466                                                    return null;
467                                            }
468                                            else {
469                                                    method = curMethod;
470                                                    methodParameterTypes = curParameterTypes;
471                                            }
472                                    }
473                            }
474                    }
475    
476                    if (method != null) {
477                            methodAndParameterTypes = new Object[] {
478                                    method, methodParameterTypes
479                            };
480    
481                            _methodCache.put(key, methodAndParameterTypes);
482    
483                            return methodAndParameterTypes;
484                    }
485    
486                    String parametersString = StringUtil.merge(parameters);
487    
488                    _log.error(
489                            "No method found for class " + clazz + ", method " + methodName +
490                                    ", and parameters " + parametersString);
491    
492                    return null;
493            }
494    
495            @Override
496            protected String getReroutePath() {
497                    return _REROUTE_PATH;
498            }
499    
500            protected String getReturnValue(Object returnObj) throws Exception {
501                    if (returnObj instanceof JSONSerializable) {
502                            JSONSerializable jsonSerializable = (JSONSerializable)returnObj;
503    
504                            return jsonSerializable.toJSONString();
505                    }
506    
507                    JSONSerializer jsonSerializer = JSONFactoryUtil.createJSONSerializer();
508    
509                    jsonSerializer.exclude("*.class");
510    
511                    return jsonSerializer.serializeDeep(returnObj);
512            }
513    
514            protected String[] getStringArrayFromJSON(
515                            HttpServletRequest request, String param)
516                    throws JSONException {
517    
518                    String json = ParamUtil.getString(request, param, "[]");
519    
520                    JSONArray jsonArray = JSONFactoryUtil.createJSONArray(json);
521    
522                    return ArrayUtil.toStringArray(jsonArray);
523            }
524    
525            protected String getTypeNameOrClassDescriptor(Type type) {
526                    String typeName = type.toString();
527    
528                    if (typeName.contains("class ")) {
529                            return typeName.substring(6);
530                    }
531    
532                    Matcher matcher = _fieldDescriptorPattern.matcher(typeName);
533    
534                    while (matcher.find()) {
535                            String dimensions = matcher.group(2);
536                            String fieldDescriptor = matcher.group(1);
537    
538                            if (Validator.isNull(dimensions)) {
539                                    return fieldDescriptor;
540                            }
541    
542                            dimensions = dimensions.replace(
543                                    StringPool.CLOSE_BRACKET, StringPool.BLANK);
544    
545                            if (fieldDescriptor.equals("boolean")) {
546                                    fieldDescriptor = "Z";
547                            }
548                            else if (fieldDescriptor.equals("byte")) {
549                                    fieldDescriptor = "B";
550                            }
551                            else if (fieldDescriptor.equals("char")) {
552                                    fieldDescriptor = "C";
553                            }
554                            else if (fieldDescriptor.equals("double")) {
555                                    fieldDescriptor = "D";
556                            }
557                            else if (fieldDescriptor.equals("float")) {
558                                    fieldDescriptor = "F";
559                            }
560                            else if (fieldDescriptor.equals("int")) {
561                                    fieldDescriptor = "I";
562                            }
563                            else if (fieldDescriptor.equals("long")) {
564                                    fieldDescriptor = "J";
565                            }
566                            else if (fieldDescriptor.equals("short")) {
567                                    fieldDescriptor = "S";
568                            }
569                            else {
570                                    fieldDescriptor = "L".concat(fieldDescriptor).concat(
571                                            StringPool.SEMICOLON);
572                            }
573    
574                            return dimensions.concat(fieldDescriptor);
575                    }
576    
577                    throw new IllegalArgumentException(type.toString() + " is invalid");
578            }
579    
580            protected boolean isValidRequest(HttpServletRequest request) {
581                    String className = ParamUtil.getString(request, "serviceClassName");
582    
583                    if (className.contains(".service.") &&
584                            className.endsWith("ServiceUtil") &&
585                            !className.endsWith("LocalServiceUtil") &&
586                            !_invalidClassNames.contains(className)) {
587    
588                            return true;
589                    }
590                    else {
591                            return false;
592                    }
593            }
594    
595            private static final String _REROUTE_PATH = "/api/json";
596    
597            private static Log _log = LogFactoryUtil.getLog(JSONServiceAction.class);
598    
599            private static Pattern _fieldDescriptorPattern = Pattern.compile(
600                    "^(.*?)((\\[\\])*)$", Pattern.DOTALL);
601    
602            private Set<String> _invalidClassNames;
603            private Map<String, Object[]> _methodCache =
604                    new HashMap<String, Object[]>();
605    
606    }