/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.action;
import com.liferay.portal.kernel.json.JSONArray;
import com.liferay.portal.kernel.json.JSONException;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.json.JSONSerializable;
import com.liferay.portal.kernel.json.JSONSerializer;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.security.access.control.AccessControlThreadLocal;
import com.liferay.portal.kernel.service.ServiceContext;
import com.liferay.portal.kernel.service.ServiceContextUtil;
import com.liferay.portal.kernel.util.ArrayUtil;
import com.liferay.portal.kernel.util.ClassLoaderUtil;
import com.liferay.portal.kernel.util.ClassUtil;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.LocaleUtil;
import com.liferay.portal.kernel.util.LocalizationUtil;
import com.liferay.portal.kernel.util.ParamUtil;
import com.liferay.portal.kernel.util.SetUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.struts.JSONAction;
import com.liferay.portal.util.PropsValues;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
/**
* @author Brian Wing Shun Chan
* @author Karthik Sudarshan
* @author Julio Camarero
* @author Eduardo Lundgren
*/
public class JSONServiceAction extends JSONAction {
public JSONServiceAction() {
_invalidClassNames = SetUtil.fromArray(
PropsValues.JSON_SERVICE_INVALID_CLASS_NAMES);
_invalidMethodNames = SetUtil.fromArray(
PropsValues.JSON_SERVICE_INVALID_METHOD_NAMES);
if (_log.isDebugEnabled()) {
for (String invalidClassName : _invalidClassNames) {
_log.debug("Invalid class name " + invalidClassName);
}
for (String invalidMethodName : _invalidMethodNames) {
_log.debug("Invalid method name " + invalidMethodName);
}
}
}
@Override
public String getJSON(
ActionMapping actionMapping, ActionForm actionForm,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
String className = ParamUtil.getString(request, "serviceClassName");
String methodName = ParamUtil.getString(request, "serviceMethodName");
String[] serviceParameters = getStringArrayFromJSON(
request, "serviceParameters");
String[] serviceParameterTypes = getStringArrayFromJSON(
request, "serviceParameterTypes");
if (!isValidRequest(request)) {
return null;
}
ClassLoader contextClassLoader =
ClassLoaderUtil.getContextClassLoader();
Class<?> clazz = contextClassLoader.loadClass(className);
Object[] methodAndParameterTypes = getMethodAndParameterTypes(
clazz, methodName, serviceParameters, serviceParameterTypes);
if (methodAndParameterTypes == null) {
return null;
}
Method method = (Method)methodAndParameterTypes[0];
Type[] parameterTypes = (Type[])methodAndParameterTypes[1];
Object[] args = new Object[serviceParameters.length];
for (int i = 0; i < serviceParameters.length; i++) {
args[i] = getArgValue(
request, clazz, methodName, serviceParameters[i],
parameterTypes[i]);
}
try {
if (_log.isDebugEnabled()) {
_log.debug(
"Invoking " + clazz + " on method " + method.getName() +
" with args " + Arrays.toString(args));
}
Object returnObj = null;
boolean remoteAccess = AccessControlThreadLocal.isRemoteAccess();
try {
AccessControlThreadLocal.setRemoteAccess(true);
returnObj = method.invoke(clazz, args);
}
finally {
AccessControlThreadLocal.setRemoteAccess(remoteAccess);
}
if (returnObj != null) {
return getReturnValue(returnObj);
}
else {
return JSONFactoryUtil.getNullJSON();
}
}
catch (Exception e) {
if (_log.isDebugEnabled()) {
_log.debug(
"Invoked " + clazz + " on method " + method.getName() +
" with args " + Arrays.toString(args),
e);
}
return JSONFactoryUtil.serializeThrowable(e);
}
}
protected Object getArgValue(
HttpServletRequest request, Class<?> clazz, String methodName,
String parameter, Type parameterType)
throws Exception {
String typeNameOrClassDescriptor = getTypeNameOrClassDescriptor(
parameterType);
String value = ParamUtil.getString(request, parameter);
if (Validator.isNull(value) &&
!typeNameOrClassDescriptor.equals("[Ljava.lang.String;")) {
return null;
}
else if (typeNameOrClassDescriptor.equals("boolean") ||
typeNameOrClassDescriptor.equals(Boolean.class.getName())) {
return Boolean.valueOf(ParamUtil.getBoolean(request, parameter));
}
else if (typeNameOrClassDescriptor.equals("double") ||
typeNameOrClassDescriptor.equals(Double.class.getName())) {
return Double.valueOf(ParamUtil.getDouble(request, parameter));
}
else if (typeNameOrClassDescriptor.equals("int") ||
typeNameOrClassDescriptor.equals(Integer.class.getName())) {
return Integer.valueOf(ParamUtil.getInteger(request, parameter));
}
else if (typeNameOrClassDescriptor.equals("long") ||
typeNameOrClassDescriptor.equals(Long.class.getName())) {
return Long.valueOf(ParamUtil.getLong(request, parameter));
}
else if (typeNameOrClassDescriptor.equals("short") ||
typeNameOrClassDescriptor.equals(Short.class.getName())) {
return Short.valueOf(ParamUtil.getShort(request, parameter));
}
else if (typeNameOrClassDescriptor.equals(Calendar.class.getName())) {
Calendar cal = Calendar.getInstance(LocaleUtil.getDefault());
cal.setTimeInMillis(ParamUtil.getLong(request, parameter));
return cal;
}
else if (typeNameOrClassDescriptor.equals(Date.class.getName())) {
return new Date(ParamUtil.getLong(request, parameter));
}
else if (typeNameOrClassDescriptor.equals(
ServiceContext.class.getName())) {
JSONObject jsonObject = JSONFactoryUtil.createJSONObject(value);
jsonObject.put("javaClass", ServiceContext.class.getName());
return ServiceContextUtil.deserialize(jsonObject);
}
else if (typeNameOrClassDescriptor.equals(String.class.getName())) {
return value;
}
else if (typeNameOrClassDescriptor.equals("[Z")) {
return ParamUtil.getBooleanValues(request, parameter);
}
else if (typeNameOrClassDescriptor.equals("[D")) {
return ParamUtil.getDoubleValues(request, parameter);
}
else if (typeNameOrClassDescriptor.equals("[F")) {
return ParamUtil.getFloatValues(request, parameter);
}
else if (typeNameOrClassDescriptor.equals("[I")) {
return ParamUtil.getIntegerValues(request, parameter);
}
else if (typeNameOrClassDescriptor.equals("[J")) {
return ParamUtil.getLongValues(request, parameter);
}
else if (typeNameOrClassDescriptor.equals("[S")) {
return ParamUtil.getShortValues(request, parameter);
}
else if (typeNameOrClassDescriptor.equals("[Ljava.lang.String;")) {
return ParamUtil.getParameterValues(request, parameter);
}
else if (typeNameOrClassDescriptor.equals("[[Z")) {
String[] values = request.getParameterValues(parameter);
if (ArrayUtil.isNotEmpty(values)) {
String[] values0 = StringUtil.split(values[0]);
boolean[][] doubleArray =
new boolean[values.length][values0.length];
for (int i = 0; i < values.length; i++) {
String[] curValues = StringUtil.split(values[i]);
for (int j = 0; j < curValues.length; j++) {
doubleArray[i][j] = GetterUtil.getBoolean(curValues[j]);
}
}
return doubleArray;
}
else {
return new boolean[0][0];
}
}
else if (typeNameOrClassDescriptor.equals("[[D")) {
String[] values = request.getParameterValues(parameter);
if (ArrayUtil.isNotEmpty(values)) {
String[] values0 = StringUtil.split(values[0]);
double[][] doubleArray =
new double[values.length][values0.length];
for (int i = 0; i < values.length; i++) {
String[] curValues = StringUtil.split(values[i]);
for (int j = 0; j < curValues.length; j++) {
doubleArray[i][j] = GetterUtil.getDouble(curValues[j]);
}
}
return doubleArray;
}
else {
return new double[0][0];
}
}
else if (typeNameOrClassDescriptor.equals("[[F")) {
String[] values = request.getParameterValues(parameter);
if (ArrayUtil.isNotEmpty(values)) {
String[] values0 = StringUtil.split(values[0]);
float[][] doubleArray =
new float[values.length][values0.length];
for (int i = 0; i < values.length; i++) {
String[] curValues = StringUtil.split(values[i]);
for (int j = 0; j < curValues.length; j++) {
doubleArray[i][j] = GetterUtil.getFloat(curValues[j]);
}
}
return doubleArray;
}
else {
return new float[0][0];
}
}
else if (typeNameOrClassDescriptor.equals("[[I")) {
String[] values = request.getParameterValues(parameter);
if (ArrayUtil.isNotEmpty(values)) {
String[] values0 = StringUtil.split(values[0]);
int[][] doubleArray = new int[values.length][values0.length];
for (int i = 0; i < values.length; i++) {
String[] curValues = StringUtil.split(values[i]);
for (int j = 0; j < curValues.length; j++) {
doubleArray[i][j] = GetterUtil.getInteger(curValues[j]);
}
}
return doubleArray;
}
else {
return new int[0][0];
}
}
else if (typeNameOrClassDescriptor.equals("[[J")) {
String[] values = request.getParameterValues(parameter);
if (ArrayUtil.isNotEmpty(values)) {
String[] values0 = StringUtil.split(values[0]);
long[][] doubleArray = new long[values.length][values0.length];
for (int i = 0; i < values.length; i++) {
String[] curValues = StringUtil.split(values[i]);
for (int j = 0; j < curValues.length; j++) {
doubleArray[i][j] = GetterUtil.getLong(curValues[j]);
}
}
return doubleArray;
}
else {
return new long[0][0];
}
}
else if (typeNameOrClassDescriptor.equals("[[S")) {
String[] values = request.getParameterValues(parameter);
if (ArrayUtil.isNotEmpty(values)) {
String[] values0 = StringUtil.split(values[0]);
short[][] doubleArray =
new short[values.length][values0.length];
for (int i = 0; i < values.length; i++) {
String[] curValues = StringUtil.split(values[i]);
for (int j = 0; j < curValues.length; j++) {
doubleArray[i][j] = GetterUtil.getShort(curValues[j]);
}
}
return doubleArray;
}
else {
return new short[0][0];
}
}
else if (typeNameOrClassDescriptor.equals("[[Ljava.lang.String")) {
String[] values = request.getParameterValues(parameter);
if (ArrayUtil.isNotEmpty(values)) {
String[] values0 = StringUtil.split(values[0]);
String[][] doubleArray =
new String[values.length][values0.length];
for (int i = 0; i < values.length; i++) {
doubleArray[i] = StringUtil.split(values[i]);
}
return doubleArray;
}
else {
return new String[0][0];
}
}
else if (typeNameOrClassDescriptor.equals(
"java.util.Map<java.util.Locale, java.lang.String>")) {
JSONObject jsonObject = JSONFactoryUtil.createJSONObject(value);
return LocalizationUtil.deserialize(jsonObject);
}
else {
try {
return JSONFactoryUtil.looseDeserialize(value);
}
catch (Exception e) {
_log.error(
"Unsupported parameter type for class " + clazz +
", method " + methodName + ", parameter " + parameter +
", and type " + typeNameOrClassDescriptor);
return null;
}
}
}
/**
* @see com.liferay.portal.jsonwebservice.JSONWebServiceServiceAction#getCSRFOrigin(
* HttpServletRequest)
*/
@Override
protected String getCSRFOrigin(HttpServletRequest request) {
StringBundler sb = new StringBundler(6);
sb.append(ClassUtil.getClassName(this));
sb.append(StringPool.COLON);
sb.append(StringPool.SLASH);
String serviceClassName = ParamUtil.getString(
request, "serviceClassName");
sb.append(serviceClassName);
sb.append(StringPool.POUND);
String serviceMethodName = ParamUtil.getString(
request, "serviceMethodName");
sb.append(serviceMethodName);
return sb.toString();
}
protected Object[] getMethodAndParameterTypes(
Class<?> clazz, String methodName, String[] parameters,
String[] parameterTypes)
throws Exception {
StringBundler sb = new StringBundler(5);
sb.append(clazz.getName());
sb.append("_METHOD_NAME_");
sb.append(methodName);
sb.append("_PARAMETERS_");
String parameterTypesNames = StringUtil.merge(parameterTypes);
if (Validator.isNull(parameterTypesNames)) {
sb.append(parameters.length);
}
else {
sb.append(parameterTypesNames);
}
String key = sb.toString();
Object[] methodAndParameterTypes = _methodCache.get(key);
if (methodAndParameterTypes != null) {
return methodAndParameterTypes;
}
Method method = null;
Type[] methodParameterTypes = null;
Method[] methods = clazz.getMethods();
for (Method curMethod : methods) {
if (curMethod.getName().equals(methodName)) {
Type[] curParameterTypes = curMethod.getGenericParameterTypes();
if (curParameterTypes.length == parameters.length) {
if ((parameterTypes.length > 0) &&
(parameterTypes.length == curParameterTypes.length)) {
boolean match = true;
for (int j = 0; j < parameterTypes.length; j++) {
String t1 = parameterTypes[j];
String t2 = getTypeNameOrClassDescriptor(
curParameterTypes[j]);
if (!t1.equals(t2)) {
match = false;
}
}
if (match) {
method = curMethod;
methodParameterTypes = curParameterTypes;
break;
}
}
else if (method != null) {
String parametersString = StringUtil.merge(parameters);
_log.error(
"Obscure method name for class " + clazz +
", method " + methodName + ", and parameters " +
parametersString);
return null;
}
else {
method = curMethod;
methodParameterTypes = curParameterTypes;
}
}
}
}
if (method != null) {
methodAndParameterTypes =
new Object[] {method, methodParameterTypes};
_methodCache.put(key, methodAndParameterTypes);
return methodAndParameterTypes;
}
String parametersString = StringUtil.merge(parameters);
_log.error(
"No method found for class " + clazz + ", method " + methodName +
", and parameters " + parametersString);
return null;
}
@Override
protected String getReroutePath() {
return _REROUTE_PATH;
}
protected String getReturnValue(Object returnObj) throws Exception {
if (returnObj instanceof JSONSerializable) {
JSONSerializable jsonSerializable = (JSONSerializable)returnObj;
return jsonSerializable.toJSONString();
}
JSONSerializer jsonSerializer = JSONFactoryUtil.createJSONSerializer();
jsonSerializer.exclude("*.class");
return jsonSerializer.serializeDeep(returnObj);
}
protected String[] getStringArrayFromJSON(
HttpServletRequest request, String param)
throws JSONException {
String json = ParamUtil.getString(request, param, "[]");
JSONArray jsonArray = JSONFactoryUtil.createJSONArray(json);
return ArrayUtil.toStringArray(jsonArray);
}
protected String getTypeNameOrClassDescriptor(Type type) {
String typeName = type.toString();
if (typeName.contains("class ")) {
return typeName.substring(6);
}
Matcher matcher = _fieldDescriptorPattern.matcher(typeName);
while (matcher.find()) {
String dimensions = matcher.group(2);
String fieldDescriptor = matcher.group(1);
if (Validator.isNull(dimensions)) {
return fieldDescriptor;
}
dimensions = dimensions.replace(
StringPool.CLOSE_BRACKET, StringPool.BLANK);
if (fieldDescriptor.equals("boolean")) {
fieldDescriptor = "Z";
}
else if (fieldDescriptor.equals("byte")) {
fieldDescriptor = "B";
}
else if (fieldDescriptor.equals("char")) {
fieldDescriptor = "C";
}
else if (fieldDescriptor.equals("double")) {
fieldDescriptor = "D";
}
else if (fieldDescriptor.equals("float")) {
fieldDescriptor = "F";
}
else if (fieldDescriptor.equals("int")) {
fieldDescriptor = "I";
}
else if (fieldDescriptor.equals("long")) {
fieldDescriptor = "J";
}
else if (fieldDescriptor.equals("short")) {
fieldDescriptor = "S";
}
else {
fieldDescriptor = "L".concat(fieldDescriptor).concat(
StringPool.SEMICOLON);
}
return dimensions.concat(fieldDescriptor);
}
throw new IllegalArgumentException(type.toString() + " is invalid");
}
protected boolean isValidRequest(HttpServletRequest request) {
String className = ParamUtil.getString(request, "serviceClassName");
String methodName = ParamUtil.getString(request, "serviceMethodName");
if (className.contains(".service.") &&
className.endsWith("ServiceUtil") &&
!className.endsWith("LocalServiceUtil") &&
!_invalidClassNames.contains(className) &&
!_invalidMethodNames.contains(methodName)) {
return true;
}
else {
return false;
}
}
private static final String _REROUTE_PATH = "/api/json";
private static final Log _log = LogFactoryUtil.getLog(
JSONServiceAction.class);
private static final Pattern _fieldDescriptorPattern = Pattern.compile(
"^(.*?)((\\[\\])*)$", Pattern.DOTALL);
private final Set<String> _invalidClassNames;
private final Set<String> _invalidMethodNames;
private final Map<String, Object[]> _methodCache = new HashMap<>();
}