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