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