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