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