/**
* 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.jsonwebservice.action;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONSerializable;
import com.liferay.portal.kernel.json.JSONSerializer;
import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceAction;
import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceActionMapping;
import com.liferay.portal.kernel.jsonwebservice.JSONWebServiceActionsManagerUtil;
import com.liferay.portal.kernel.util.CamelCaseUtil;
import com.liferay.portal.kernel.util.CharPool;
import com.liferay.portal.kernel.util.Constants;
import com.liferay.portal.kernel.util.ListUtil;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import jodd.bean.BeanUtil;
import jodd.json.BeanSerializer;
import jodd.json.JsonContext;
import jodd.json.JsonSerializer;
import jodd.servlet.ServletUtil;
import jodd.util.NameValue;
/**
* @author Igor Spasic
* @author Eduardo Lundgren
*/
public class JSONWebServiceInvokerAction implements JSONWebServiceAction {
public JSONWebServiceInvokerAction(HttpServletRequest request) {
_request = request;
String command = request.getParameter(Constants.CMD);
if (command == null) {
try {
command = ServletUtil.readRequestBody(request);
}
catch (IOException ioe) {
throw new IllegalArgumentException(ioe);
}
}
_command = command;
}
@Override
public JSONWebServiceActionMapping getJSONWebServiceActionMapping() {
return null;
}
@Override
public Object invoke() throws Exception {
Object command = JSONFactoryUtil.looseDeserialize(_command);
List<Object> list = null;
boolean batchMode = false;
if (command instanceof List) {
list = (List<Object>)command;
batchMode = true;
}
else if (command instanceof Map) {
list = new ArrayList<>(1);
list.add(command);
batchMode = false;
}
else {
throw new IllegalArgumentException();
}
for (int i = 0; i < list.size(); i++) {
Map<String, Map<String, Object>> map =
(Map<String, Map<String, Object>>)list.get(i);
if (map.isEmpty()) {
throw new IllegalArgumentException();
}
Set<Map.Entry<String, Map<String, Object>>> entrySet =
map.entrySet();
Iterator<Map.Entry<String, Map<String, Object>>> iterator =
entrySet.iterator();
Map.Entry<String, Map<String, Object>> entry = iterator.next();
Statement statement = _parseStatement(
null, entry.getKey(), entry.getValue());
Object result = _executeStatement(statement);
list.set(i, result);
}
Object result = null;
if (batchMode == false) {
result = list.get(0);
}
else {
result = list;
}
return new InvokerResult(result);
}
public class InvokerResult implements JSONSerializable {
public InvokerResult(Object result) {
_result = result;
}
public JSONWebServiceInvokerAction getJSONWebServiceInvokerAction() {
return JSONWebServiceInvokerAction.this;
}
public Object getResult() {
return _result;
}
@Override
public String toJSONString() {
if (_result == null) {
return JSONFactoryUtil.getNullJSON();
}
JSONSerializer jsonSerializer = createJSONSerializer();
if (_includes != null) {
for (String include : _includes) {
jsonSerializer.include(include);
}
}
return jsonSerializer.serialize(_result);
}
protected JSONSerializer createJSONSerializer() {
JSONSerializer jsonSerializer =
JSONFactoryUtil.createJSONSerializer();
return jsonSerializer;
}
private Object _result;
}
private void _addInclude(Statement statement, String name) {
if (_includes == null) {
_includes = new ArrayList<>();
}
StringBuilder sb = new StringBuilder();
while (statement._parentStatement != null) {
String statementName = statement.getName().substring(1);
sb.insert(0, statementName + StringPool.PERIOD);
statement = statement._parentStatement;
}
sb.append(name);
String includeName = sb.toString();
if (!_includes.contains(includeName)) {
_includes.add(includeName);
}
}
private Object _addVariableStatement(
Statement variableStatement, Object result)
throws Exception {
Statement statement = variableStatement.getParentStatement();
result = _populateFlags(statement, result);
String name = variableStatement.getName();
Object variableResult = _executeStatement(variableStatement);
Map<String, Object> map = _convertObjectToMap(statement, result, null);
if (!variableStatement.isInner()) {
map.put(name.substring(1), variableResult);
return map;
}
int index = name.indexOf(".$");
String innerObjectName = name.substring(0, index);
if (innerObjectName.contains(StringPool.PERIOD)) {
throw new IllegalArgumentException(
"Inner properties with more than 1 level are not supported");
}
Object innerObject = map.get(innerObjectName);
String innerPropertyName = name.substring(index + 2);
if (innerObject instanceof List) {
List<Object> innerList = (List<Object>)innerObject;
List<Object> newInnerList = new ArrayList<>(innerList.size());
for (Object innerListElement : innerList) {
Map<String, Object> newInnerListElement = _convertObjectToMap(
statement, innerListElement, innerObjectName);
newInnerListElement.put(innerPropertyName, variableResult);
newInnerList.add(newInnerListElement);
}
map.put(innerObjectName, newInnerList);
}
else {
Map<String, Object> innerMap = _convertObjectToMap(
statement, innerObject, innerObjectName);
innerMap.put(innerPropertyName, variableResult);
map.put(innerObjectName, innerMap);
}
return map;
}
private Object _addVariableStatementList(
Statement variableStatement, List<Object> resultList,
List<Object> results)
throws Exception {
for (Object object : resultList) {
List<Object> listObject = _convertObjectToList(object);
if (listObject != null) {
Object value = _addVariableStatementList(
variableStatement, listObject, results);
results.add(value);
}
else {
Object value = _addVariableStatement(variableStatement, object);
results.add(value);
}
}
return results;
}
private List<Object> _convertObjectToList(Object object) {
if (object == null) {
return null;
}
if (object instanceof List) {
return (List<Object>)object;
}
if (object instanceof Iterable) {
List<Object> list = new ArrayList<>();
Iterable<?> iterable = (Iterable<?>)object;
Iterator<?> iterator = iterable.iterator();
while (iterator.hasNext()) {
list.add(iterator.next());
}
return list;
}
Class<?> clazz = object.getClass();
if (!clazz.isArray()) {
return null;
}
Class<?> componentType = clazz.getComponentType();
if (!componentType.isPrimitive()) {
return ListUtil.toList((Object[])object);
}
List<Object> list = new ArrayList<>();
for (int i = 0; i < Array.getLength(object); i++) {
list.add(Array.get(object, i));
}
return list;
}
private Map<String, Object> _convertObjectToMap(
final Statement statement, Object object, final String prefix) {
if (object instanceof Map) {
return (Map<String, Object>)object;
}
JsonContext jsonContext = _jsonSerializer.createJsonContext(null);
final Map<String, Object> map = new LinkedHashMap<>();
BeanSerializer beanSerializer = new BeanSerializer(
jsonContext, object) {
@Override
protected void onSerializableProperty(
String propertyName,
@SuppressWarnings("rawtypes") Class propertyClass,
Object value) {
map.put(propertyName, value);
String include = propertyName;
if (prefix != null) {
include = prefix + "." + include;
}
_addInclude(statement, include);
}
};
beanSerializer.serialize();
return map;
}
private Object _executeStatement(Statement statement) throws Exception {
JSONWebServiceAction jsonWebServiceAction =
JSONWebServiceActionsManagerUtil.getJSONWebServiceAction(
_request, statement.getMethod(), null,
statement.getParameterMap());
Object result = jsonWebServiceAction.invoke();
result = _filterResult(statement, result);
List<Statement> variableStatements = statement.getVariableStatements();
if (variableStatements == null) {
return result;
}
for (Statement variableStatement : variableStatements) {
boolean innerStatement = variableStatement.isInner();
if (innerStatement) {
result = variableStatement.push(result);
}
List<Object> resultList = _convertObjectToList(result);
if (resultList != null) {
result = _addVariableStatementList(
variableStatement, resultList, new ArrayList<Object>());
variableStatement.setExecuted(true);
if (innerStatement) {
result = variableStatement.pop(result);
}
}
else {
if (innerStatement) {
result = variableStatement.pop(result);
}
result = _addVariableStatement(variableStatement, result);
variableStatement.setExecuted(true);
}
}
return result;
}
private Object _filterResult(Statement statement, Object result) {
List<Object> resultList = _convertObjectToList(result);
if (resultList != null) {
result = _filterResultList(
statement, resultList, new ArrayList<Object>());
}
else {
result = _filterResultObject(statement, result);
}
return result;
}
private Object _filterResultList(
Statement statement, List<Object> resultList, List<Object> results) {
for (Object object : resultList) {
Object value = _filterResultObject(statement, object);
results.add(value);
}
return results;
}
private Object _filterResultObject(Statement statement, Object result) {
if (result == null) {
return result;
}
String[] whitelist = statement.getWhitelist();
if (whitelist == null) {
return result;
}
Map<String, Object> map = _convertObjectToMap(statement, result, null);
Map<String, Object> whitelistMap = new HashMap<>(whitelist.length);
for (String key : whitelist) {
Object value = map.get(key);
whitelistMap.put(key, value);
}
return whitelistMap;
}
private Statement _parseStatement(
Statement parentStatement, String assignment,
Map<String, Object> statementBody) {
Statement statement = new Statement(parentStatement);
_statements.add(statement);
int x = assignment.indexOf(StringPool.EQUAL);
if (x == -1) {
statement.setMethod(assignment.trim());
}
else {
String name = assignment.substring(0, x).trim();
int y = name.indexOf(StringPool.OPEN_BRACKET);
if (y != -1) {
String whitelistString = name.substring(
y + 1, name.length() - 1);
String[] whiteList = StringUtil.split(whitelistString);
for (int i = 0; i < whiteList.length; i++) {
whiteList[i] = whiteList[i].trim();
}
statement.setWhitelist(whiteList);
name = name.substring(0, y);
}
statement.setName(name);
statement.setMethod(assignment.substring(x + 1).trim());
}
HashMap<String, Object> parameterMap = new HashMap<>(
statementBody.size());
statement.setParameterMap(parameterMap);
for (Map.Entry<String, Object> entry : statementBody.entrySet()) {
String key = entry.getKey();
if (key.startsWith(StringPool.AT)) {
String value = (String)entry.getValue();
List<Flag> flags = statement.getFlags();
if (flags == null) {
flags = new ArrayList<>();
statement.setFlags(flags);
}
Flag flag = new Flag();
flag.setName(key.substring(1));
flag.setValue(value);
flags.add(flag);
}
else if (key.startsWith(StringPool.DOLLAR) || key.contains(".$")) {
Map<String, Object> map = (Map<String, Object>)entry.getValue();
List<Statement> variableStatements =
statement.getVariableStatements();
if (variableStatements == null) {
variableStatements = new ArrayList<>();
statement.setVariableStatements(variableStatements);
}
Statement variableStatement = _parseStatement(
statement, key, map);
variableStatements.add(variableStatement);
}
else {
Object value = entry.getValue();
parameterMap.put(CamelCaseUtil.normalizeCamelCase(key), value);
}
}
return statement;
}
private Object _populateFlags(Statement statement, Object result) {
List<Object> listResult = _convertObjectToList(result);
if (listResult != null) {
result = _populateFlagsList(
statement.getName(), listResult, new ArrayList<Object>());
}
else {
_populateFlagsObject(statement.getName(), result);
}
return result;
}
private List<Object> _populateFlagsList(
String name, List<Object> list, List<Object> results) {
for (Object object : list) {
List<Object> listObject = _convertObjectToList(object);
if (listObject != null) {
Object value = _populateFlagsList(name, listObject, results);
results.add(value);
}
else {
_populateFlagsObject(name, object);
results.add(object);
}
}
return results;
}
private void _populateFlagsObject(String name, Object object) {
if (name == null) {
return;
}
String pushedName = null;
int index = name.indexOf(CharPool.PERIOD);
if (index != -1) {
pushedName = name.substring(0, index + 1);
}
name = name.concat(StringPool.PERIOD);
for (Statement statement : _statements) {
if (statement.isExecuted()) {
continue;
}
List<Flag> flags = statement.getFlags();
if (flags == null) {
continue;
}
for (Flag flag : flags) {
String value = flag.getValue();
if (value == null) {
continue;
}
if (value.startsWith(name) &&
(value.indexOf(CharPool.PERIOD, name.length()) == -1)) {
Map<String, Object> parameterMap =
statement.getParameterMap();
Object propertyValue = BeanUtil.getDeclaredProperty(
object, value.substring(name.length()));
parameterMap.put(flag.getName(), propertyValue);
}
else if (statement.isPushed() && value.startsWith(pushedName)) {
Map<String, Object> parameterMap =
statement.getParameterMap();
Object propertyValue = BeanUtil.getDeclaredProperty(
statement._pushTarget,
value.substring(pushedName.length()));
parameterMap.put(flag.getName(), propertyValue);
}
}
}
}
private static final JsonSerializer _jsonSerializer = new JsonSerializer();
private final String _command;
private List<String> _includes;
private final HttpServletRequest _request;
private final List<Statement> _statements = new ArrayList<>();
private static class Flag extends NameValue<String, String> {
}
private static class Statement {
public List<Flag> getFlags() {
return _flags;
}
public String getMethod() {
return _method;
}
public String getName() {
return _name;
}
public Map<String, Object> getParameterMap() {
return _parameterMap;
}
public Statement getParentStatement() {
return _parentStatement;
}
public List<Statement> getVariableStatements() {
return _variableStatements;
}
public String[] getWhitelist() {
return _whitelist;
}
public boolean isExecuted() {
return _executed;
}
public boolean isInner() {
return _inner;
}
public boolean isPushed() {
if (_pushTarget != null) {
return true;
}
return false;
}
public Object pop(Object result) {
if (_pushTarget == null) {
return null;
}
Statement statement = getParentStatement();
String statementName = statement.getName();
int index = statementName.lastIndexOf('.');
String beanName = statementName.substring(index + 1);
statementName = statementName.substring(0, index);
statement.setName(statementName);
setName(beanName + StringPool.PERIOD + getName());
BeanUtil.setDeclaredProperty(_pushTarget, beanName, result);
result = _pushTarget;
_pushTarget = null;
return result;
}
public Object push(Object result) {
if (_parentStatement == null) {
return null;
}
_pushTarget = result;
Statement statement = getParentStatement();
String variableName = getName();
int index = variableName.indexOf(".$");
String beanName = variableName.substring(0, index);
result = BeanUtil.getDeclaredProperty(result, beanName);
statement.setName(
statement.getName() + StringPool.PERIOD + beanName);
variableName = variableName.substring(index + 1);
setName(variableName);
return result;
}
public void setExecuted(boolean executed) {
_executed = executed;
}
public void setFlags(List<Flag> flags) {
_flags = flags;
}
public void setMethod(String method) {
_method = method;
}
public void setName(String name) {
if (name.contains(".$")) {
_inner = true;
}
else {
_inner = false;
}
_name = name;
}
public void setParameterMap(Map<String, Object> parameterMap) {
_parameterMap = parameterMap;
}
public void setVariableStatements(List<Statement> variableStatements) {
_variableStatements = variableStatements;
}
public void setWhitelist(String[] whitelist) {
_whitelist = whitelist;
}
private Statement(Statement parentStatement) {
_parentStatement = parentStatement;
}
private boolean _executed;
private List<Flag> _flags;
private boolean _inner;
private String _method;
private String _name;
private Map<String, Object> _parameterMap;
private Statement _parentStatement;
private Object _pushTarget;
private List<Statement> _variableStatements;
private String[] _whitelist;
}
}