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