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