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