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