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.security.pacl.PACLClassLoaderUtil;
036 import com.liferay.portal.service.ServiceContext;
037 import com.liferay.portal.service.ServiceContextUtil;
038 import com.liferay.portal.struts.JSONAction;
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 PACLClassLoaderUtil.getContextClassLoader();
102
103 Class<?> clazz = contextClassLoader.loadClass(className);
104
105 Object[] methodAndParameterTypes = getMethodAndParameterTypes(
106 clazz, methodName, serviceParameters, serviceParameterTypes);
107
108 if (methodAndParameterTypes != null) {
109 Method method = (Method)methodAndParameterTypes[0];
110 Type[] parameterTypes = (Type[])methodAndParameterTypes[1];
111 Object[] args = new Object[serviceParameters.length];
112
113 for (int i = 0; i < serviceParameters.length; i++) {
114 args[i] = getArgValue(
115 request, clazz, methodName, serviceParameters[i],
116 parameterTypes[i]);
117 }
118
119 try {
120 if (_log.isDebugEnabled()) {
121 _log.debug(
122 "Invoking " + clazz + " on method " + method.getName() +
123 " with args " + Arrays.toString(args));
124 }
125
126 Object returnObj = method.invoke(clazz, args);
127
128 if (returnObj != null) {
129 return getReturnValue(returnObj);
130 }
131 else {
132 return JSONFactoryUtil.getNullJSON();
133 }
134 }
135 catch (Exception e) {
136 if (_log.isDebugEnabled()) {
137 _log.debug(
138 "Invoked " + clazz + " on method " + method.getName() +
139 " with args " + Arrays.toString(args),
140 e);
141 }
142
143 return JSONFactoryUtil.serializeException(e);
144 }
145 }
146
147 return null;
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 if (typeNameOrClassDescriptor.startsWith("java.util.Map")) {
408 return JSONFactoryUtil.looseDeserializeSafe(value);
409 }
410 else {
411 _log.error(
412 "Unsupported parameter type for class " + clazz + ", method " +
413 methodName + ", parameter " + parameter + ", and type " +
414 typeNameOrClassDescriptor);
415
416 return null;
417 }
418 }
419
420 protected Object[] getMethodAndParameterTypes(
421 Class<?> clazz, String methodName, String[] parameters,
422 String[] parameterTypes)
423 throws Exception {
424
425 StringBundler sb = new StringBundler(5);
426
427 sb.append(clazz.getName());
428 sb.append("_METHOD_NAME_");
429 sb.append(methodName);
430 sb.append("_PARAMETERS_");
431
432 String parameterTypesNames = StringUtil.merge(parameterTypes);
433
434 if (Validator.isNull(parameterTypesNames)) {
435 sb.append(parameters.length);
436 }
437 else {
438 sb.append(parameterTypesNames);
439 }
440
441 String key = sb.toString();
442
443 Object[] methodAndParameterTypes = _methodCache.get(key);
444
445 if (methodAndParameterTypes != null) {
446 return methodAndParameterTypes;
447 }
448
449 Method method = null;
450 Type[] methodParameterTypes = null;
451
452 Method[] methods = clazz.getMethods();
453
454 for (Method curMethod : methods) {
455 if (curMethod.getName().equals(methodName)) {
456 Type[] curParameterTypes = curMethod.getGenericParameterTypes();
457
458 if (curParameterTypes.length == parameters.length) {
459 if ((parameterTypes.length > 0) &&
460 (parameterTypes.length == curParameterTypes.length)) {
461
462 boolean match = true;
463
464 for (int j = 0; j < parameterTypes.length; j++) {
465 String t1 = parameterTypes[j];
466 String t2 = getTypeNameOrClassDescriptor(
467 curParameterTypes[j]);
468
469 if (!t1.equals(t2)) {
470 match = false;
471 }
472 }
473
474 if (match) {
475 method = curMethod;
476 methodParameterTypes = curParameterTypes;
477
478 break;
479 }
480 }
481 else if (method != null) {
482 String parametersString = StringUtil.merge(parameters);
483
484 _log.error(
485 "Obscure method name for class " + clazz +
486 ", method " + methodName + ", and parameters " +
487 parametersString);
488
489 return null;
490 }
491 else {
492 method = curMethod;
493 methodParameterTypes = curParameterTypes;
494 }
495 }
496 }
497 }
498
499 if (method != null) {
500 methodAndParameterTypes = new Object[] {
501 method, methodParameterTypes
502 };
503
504 _methodCache.put(key, methodAndParameterTypes);
505
506 return methodAndParameterTypes;
507 }
508 else {
509 String parametersString = StringUtil.merge(parameters);
510
511 _log.error(
512 "No method found for class " + clazz + ", method " +
513 methodName + ", and parameters " + parametersString);
514
515 return null;
516 }
517 }
518
519 @Override
520 protected String getReroutePath() {
521 return _REROUTE_PATH;
522 }
523
524 protected String getReturnValue(Object returnObj) throws Exception {
525 if (returnObj instanceof JSONSerializable) {
526 JSONSerializable jsonSerializable = (JSONSerializable)returnObj;
527
528 return jsonSerializable.toJSONString();
529 }
530
531 JSONSerializer jsonSerializer = JSONFactoryUtil.createJSONSerializer();
532
533 jsonSerializer.exclude("*.class");
534
535 return jsonSerializer.serialize(returnObj);
536 }
537
538 protected String[] getStringArrayFromJSON(
539 HttpServletRequest request, String param)
540 throws JSONException {
541
542 String json = ParamUtil.getString(request, param, "[]");
543
544 JSONArray jsonArray = JSONFactoryUtil.createJSONArray(json);
545
546 return ArrayUtil.toStringArray(jsonArray);
547 }
548
549 protected String getTypeNameOrClassDescriptor(Type type) {
550 String typeName = type.toString();
551
552 if (typeName.contains("class ")) {
553 return typeName.substring(6);
554 }
555
556 Matcher matcher = _fieldDescriptorPattern.matcher(typeName);
557
558 while (matcher.find()) {
559 String dimensions = matcher.group(2);
560 String fieldDescriptor = matcher.group(1);
561
562 if (Validator.isNull(dimensions)) {
563 return fieldDescriptor;
564 }
565
566 dimensions = dimensions.replace(
567 StringPool.CLOSE_BRACKET, StringPool.BLANK);
568
569 if (fieldDescriptor.equals("boolean")) {
570 fieldDescriptor = "Z";
571 }
572 else if (fieldDescriptor.equals("byte")) {
573 fieldDescriptor = "B";
574 }
575 else if (fieldDescriptor.equals("char")) {
576 fieldDescriptor = "C";
577 }
578 else if (fieldDescriptor.equals("double")) {
579 fieldDescriptor = "D";
580 }
581 else if (fieldDescriptor.equals("float")) {
582 fieldDescriptor = "F";
583 }
584 else if (fieldDescriptor.equals("int")) {
585 fieldDescriptor = "I";
586 }
587 else if (fieldDescriptor.equals("long")) {
588 fieldDescriptor = "J";
589 }
590 else if (fieldDescriptor.equals("short")) {
591 fieldDescriptor = "S";
592 }
593 else {
594 fieldDescriptor = "L".concat(fieldDescriptor).concat(
595 StringPool.SEMICOLON);
596 }
597
598 return dimensions.concat(fieldDescriptor);
599 }
600
601 throw new IllegalArgumentException(type.toString() + " is invalid");
602 }
603
604 protected boolean isValidRequest(HttpServletRequest request) {
605 String className = ParamUtil.getString(request, "serviceClassName");
606
607 if (className.contains(".service.") &&
608 className.endsWith("ServiceUtil") &&
609 !className.endsWith("LocalServiceUtil") &&
610 !_invalidClassNames.contains(className)) {
611
612 return true;
613 }
614 else {
615 return false;
616 }
617 }
618
619 private static final String _REROUTE_PATH = "/api/json";
620
621 private static Log _log = LogFactoryUtil.getLog(JSONServiceAction.class);
622
623 private static Pattern _fieldDescriptorPattern = Pattern.compile(
624 "^(.*?)((\\[\\])*)$", Pattern.DOTALL);
625
626 private Set<String> _invalidClassNames;
627 private Map<String, Object[]> _methodCache =
628 new HashMap<String, Object[]>();
629
630 }