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.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
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
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 }