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