/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso 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 3 of the License, or
* (at your option) any later version.
*
* Jspresso 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.qooxdoo.rpc;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
/**
* Refines the way Qooxdoo rpc handles JSON types.
*
* @author Vincent Vandenschrick
*/
public class RemoteCallUtils extends net.sf.qooxdoo.rpc.RemoteCallUtils {
private final static ThreadLocal<BidiMap<String, String>> CODEC = new ThreadLocal<>();
/**
* Handles Lists.
* <p/>
* {@inheritDoc}
*/
@Override
public Object fromJava(Object obj)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, JSONException {
BidiMap<String, String> codec = CODEC.get();
boolean initialCall = false;
if (codec == null) {
initialCall = true;
codec = new DualHashBidiMap<>();
CODEC.set(codec);
}
try {
Object returnValue;
if (obj instanceof List<?>) {
List<?> list = (List<?>) obj;
JSONObject jsonObject = new JSONObject();
jsonObject.put(encode("class"), encode("qx.data.Array"));
jsonObject.put(encode("array"), fromJava(list.toArray()));
returnValue = jsonObject;
} else if (obj instanceof BigDecimal) {
returnValue = super.fromJava(((BigDecimal) obj).doubleValue());
} else if (obj instanceof BigInteger) {
returnValue = super.fromJava(((BigInteger) obj).longValue());
} else {
returnValue = super.fromJava(obj);
}
if (initialCall) {
JSONObject wrapper = new JSONObject();
wrapper.put("codec", new JSONObject(codec));
wrapper.put("payload", returnValue);
returnValue = wrapper;
}
return returnValue;
} finally {
if (initialCall) {
CODEC.remove();
}
}
}
/**
* Handles qx.data.Array <-> lists. Handles polymorphism in arrays.
* <p/>
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public Object toJava(Object obj, @SuppressWarnings("rawtypes") Class targetType) {
if (obj instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) obj;
String requestedTypeName = jsonObject.optString("class", null);
if (requestedTypeName != null) {
if ("qx.data.Array".equals(requestedTypeName)) {
JSONArray jsonArray = jsonObject.optJSONArray("array");
if (jsonArray != null) {
Object array = toJava(jsonArray, Object[].class);
if (array instanceof Object[]) {
List<Object> returnedList = new ArrayList<>();
Collections.addAll(returnedList, ((Object[]) array));
return returnedList;
}
}
} else {
Class<?> clazz = null;
try {
clazz = resolveClassHint(requestedTypeName, targetType);
} catch (Throwable t) {
// NO-OP
}
if (clazz != null && targetType.isAssignableFrom(clazz)) {
return super.toJava(obj, clazz);
}
}
}
}
return super.toJava(obj, targetType);
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
protected Map filter(Object obj, Map map) {
Map<String, Object> filteredMap = super.filter(obj, map);
filteredMap.put("class", encode(obj.getClass().getName()));
// Prevents recursion on JSON serialization
filteredMap.remove("parent");
Map<String, Object> encodedMap = new HashMap();
for (Map.Entry<String, Object> entry : filteredMap.entrySet()) {
encodedMap.put(encode(entry.getKey()), entry.getValue());
}
return encodedMap;
}
/**
* {@inheritDoc}
*/
@Override
protected Class<?> resolveClassHint(String requestedTypeName, @SuppressWarnings("rawtypes") Class targetType)
throws Exception {
Class<?> clazz = super.resolveClassHint(requestedTypeName, targetType);
if (clazz == null) {
return Class.forName(requestedTypeName);
}
return clazz;
}
/**
* Make the Rpc java lib more permissive, i.e. do not impose any signature
* constraint on public method.
* <p/>
* {@inheritDoc}
*/
@Override
protected boolean throwsExpectedException(Method method) {
// Removes the exception constraints on method signature.
return true;
}
/**
* Encode string.
*
* @param original
* the original
* @return the string
*/
protected String encode(String original) {
BidiMap<String, String> codec = CODEC.get();
String key = codec.getKey(original);
if (key == null) {
key = Integer.toHexString(codec.size());
codec.put(key, original);
}
return key;
}
}