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