/*******************************************************************************
* Copyright (c) 2011 GigaSpaces Technologies Ltd. All rights reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.cloudifysource.rest.out;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.cloudifysource.rest.command.CommandManager;
import org.cloudifysource.rest.util.AdminTypeBlacklist;
import org.cloudifysource.rest.util.PrimitiveWrapper;
/**
* a util class for inserting various type objects into a predetermined map that
* is received by reference.
*
* @author adaml
*
*/
public class OutputUtils {
private static String hostAddress;
private static String hostContext;
private static HashSet<String> getBlackList() {
final HashSet<String> blackList = new HashSet<String>();
blackList
.add("getReplicationStatus com.j_spaces.core.admin.JSpaceAdminProxy");
blackList
.add("getClusterConfigFile com.j_spaces.core.admin.JSpaceAdminProxy");
blackList
.add("getTargetSpaces com.gigaspaces.internal.client.spaceproxy.SpaceProxyImpl");
blackList
.add("getAppDomainId com.gigaspaces.internal.client.spaceproxy.SpaceProxyImpl");
blackList
.add("getDotnetProxyHandleId com.gigaspaces.internal.client.spaceproxy.SpaceProxyImpl");
blackList
.add("getReplicationStatus com.gigaspaces.internal.lrmi.stubs.LRMISpaceImpl");
blackList
.add("getReplicationTarget com.gigaspaces.internal.lrmi.stubs.LRMISpaceImpl");
blackList
.add("getClusterConfigFile com.gigaspaces.internal.lrmi.stubs.LRMISpaceImpl");
blackList
.add("getSpacePump com.gigaspaces.internal.lrmi.stubs.LRMISpaceImpl");
blackList
.add("getLocalConfig com.j_spaces.core.admin.JSpaceAdminProxy");
blackList.add("getReplicationStatus com.gigaspaces.reflect.$GSProxy10");
blackList.add("getReplicationStatus com.gigaspaces.reflect.$GSProxy12");
blackList.add("getClusterConfigFile com.gigaspaces.reflect.$GSProxy12");
blackList.add("getReplicationTarget com.gigaspaces.reflect.$GSProxy10");
blackList.add("getSpacePump com.gigaspaces.reflect.$GSProxy10");
blackList.add("getReplicationStatus com.gigaspaces.reflect.$GSProxy9");
blackList.add("getClusterConfigFile com.gigaspaces.reflect.$GSProxy10");
blackList
.add("getThreadSecurityContext com.gigaspaces.internal.client.spaceproxy.SpaceProxyImpl");
return blackList;
}
public static final String NULL_OBJECT_DENOTER = "<null>";
private OutputUtils() {
}
/**
* gets an array object and a reference to the output map. inserts the
* parameters of the array into the output map.
*
* @param arrayObject
* @param outputMap
* @param completeURL
*/
public static void outputArrayToMap(Object arrayObject,
final Map<String, Object> outputMap, final String completeURL) {
if (isNull(arrayObject)) {
return;
}
arrayObject = getArray(arrayObject);
final int arrayLength = ((Object[]) arrayObject).length;
final String[] uriPathArray = new String[arrayLength];
for (int i = 0; i < arrayLength; i++) {
uriPathArray[i] = completeURL.concat("/" + i);
}
final String[] commands = completeURL.split("/");
outputMap
.put(commands[commands.length - 1] + "-Elements", uriPathArray);
outputMap.put(commands[commands.length - 1] + "-Size", arrayLength);
}
private static String getRelativePathURLS(final String uriPathArray) {
final int contextIndex = uriPathArray.indexOf(getHostContext()
+ "/admin");
final String relativePath = uriPathArray.substring(contextIndex);
return getHostAddress() + relativePath;
}
// Helps in dealing with primitive type arrays. returns an Object Array.
public static Object[] getArray(final Object val) {
final int arrlength = Array.getLength(val);
final Object[] outputArray = new Object[arrlength];
for (int i = 0; i < arrlength; ++i) {
outputArray[i] = Array.get(val, i);
}
return outputArray;
}
public static void outputListToMap(final Object listObject,
final Map<String, Object> outputMap, final String completeURL) {
if (isNull(listObject)) {
return;
}
final int listSize = ((List<?>) listObject).size();
final String[] uriPathList = new String[listSize];
for (int i = 0; i < listSize; i++) {
uriPathList[i] = completeURL.concat("/" + i);
}
final String[] commands = completeURL.split("/");
outputMap.put(commands[commands.length - 1].concat("-Size"),
uriPathList);
}
public static void outputMapToMap(final Object mapObject,
final Map<String, Object> outputMap, final String completeURL) {
if (isNull(mapObject)) {
return;
}
final Map<?, ?> map = (Map<?, ?>) mapObject;
final int mapSize = map.size();
final String[] uriPathArray = new String[mapSize];
int i = 0;
for (final Object key : map.keySet()) {
uriPathArray[i] = completeURL.concat("/"
+ key.toString().replace(" ", "%20"));
i++;
}
final String[] commands = completeURL.split("/");
outputMap.put(commands[commands.length - 1].concat("-Elements"),
uriPathArray);
}
public static void outputObjectToMap(final CommandManager manager,
final Map<String, Object> outputMap) {
final Object object = manager.getFinalCommand().getCommandObject();
final String commandURL = getRelativePathURLS(manager.getCommandURL());
final String commandName = manager.getFinalCommandName();
simpleOutputObjectToMap(object, commandURL, commandName, outputMap);
}
private static void simpleOutputObjectToMap(final Object object,
final String commandURL, final String rawCommandName,
final Map<String, Object> outputMap) {
final Class<?> aClass = object.getClass();
if (PrimitiveWrapper.is(aClass)) {
outputMap.put(rawCommandName, object.toString());
return;
}
final List<Method> validGetterMethods = getValidGetters(aClass);
Object resultObject = null;
String commandName;
for (final Method method : validGetterMethods) {
final Class<?> methodReturnType = method.getReturnType();
commandName = getGetterCommandName(method.getName());
String nextCommandURL = null;
if (isDetailsGetter(method)) {
resultObject = safeInvoke(method, object);
if (!isNull(resultObject)) {
final HashMap<String, Object> detailsMap = new HashMap<String, Object>();
// Recurse to get details result in a new map.
simpleOutputObjectToMap(resultObject, commandURL + "/"
+ commandName, commandName, detailsMap);
outputMap.put(commandName, detailsMap);
}
} else if (methodReturnType.isArray()) {
resultObject = safeInvoke(method, object);
nextCommandURL = getNextCommandUrl(commandURL, commandName,
false);
OutputUtils.outputArrayToMap(resultObject, outputMap,
nextCommandURL);
} else if (Map.class.isAssignableFrom(methodReturnType)) {
resultObject = safeInvoke(method, object);
nextCommandURL = getNextCommandUrl(commandURL, commandName,
false);
OutputUtils.outputMapToMap(resultObject, outputMap,
nextCommandURL);
} else if (List.class.isAssignableFrom(methodReturnType)) {
resultObject = safeInvoke(method, object);
nextCommandURL = getNextCommandUrl(commandURL, commandName,
false);
OutputUtils.outputListToMap(resultObject, outputMap,
nextCommandURL);
} else if (PrimitiveWrapper.is(methodReturnType)) {
resultObject = safeInvoke(method, object);
if (!isNull(resultObject)) {
outputMap.put(commandName, resultObject.toString());
}
} else {
nextCommandURL = getNextCommandUrl(commandURL, commandName,
false);
outputMap.put(commandName, nextCommandURL);
// Special treatment for enum objects.
resultObject = safeInvoke(method, object);
if (!isNull(resultObject)) {
if (resultObject.getClass().isEnum()) {
outputMap.put(commandName + "-Enumerator",
resultObject.toString());
}
}
if (object.getClass().isEnum()) {
outputMap.put(commandName + "-Enumerator",
object.toString());
}
}
}
}
/**
* returns the next command's url with the correct relative path. if the
* last(top most) object is of type Map/List/Array than we should NOT
* include a duplication of the command name in the url.
*
* @param commandURI .
* @param commandName .
* @param isLastObjectAndCollection .
* @return next command url.
*/
public static String getNextCommandUrl(final String commandURI,
final String commandName, final boolean isLastObjectAndCollection) {
String outputUrl = getRelativePathURLS(commandURI);
if (!isLastObjectAndCollection) {
outputUrl = outputUrl + "/" + commandName;
}
return outputUrl;
}
// Trunk is/get
private static String getGetterCommandName(final String getterName) {
String commandName = null;
if (getterName.startsWith("is")) {
commandName = getterName.substring(2);
} else if (getterName.startsWith("get")) {
commandName = getterName.substring(3);
}
return commandName;
}
// return a list of valid getters.
private static List<Method> getValidGetters(final Class<?> aClass) {
final Method[] allMethods = aClass.getMethods();
final List<Method> validGetterMethods = new ArrayList<Method>();
for (final Method method : allMethods) {
if (isValidObjectGetter(method)) {
validGetterMethods.add(method);
}
}
return validGetterMethods;
}
// e.g. getMemcachedDetails()
private static boolean isDetailsGetter(final Method getter) {
final String name = getter.getName();
return name.startsWith("get") && name.endsWith("Details");
}
public static boolean isValidObjectGetter(final Method method) {
final String methodName = method.getName();
final Class<?> retType = method.getReturnType();
// black listed methods.
if (methodName.equals("getGigaSpace")
|| methodName.equals("getIJSpace")) {
return false;
}
if (methodName.equals("getRegistrar")) {
return false;
}
// private object getters will not be invoked.
if (method.getModifiers() == Modifier.PRIVATE) {
return false;
}
if (methodName.equals("getClass")) { // irrelevant
return false;
}
// special case: avoid event-related getters by name
if (methodName.endsWith("Changed") || methodName.endsWith("Removed")
|| methodName.endsWith("Added")) {
return false;
}
// special case: avoid event-related getters by return value
if (retType.getCanonicalName().contains(".events")) {
return false;
}
if (method.getParameterTypes().length > 0) {
return false;
}
if (AdminTypeBlacklist.in(retType)) {
return false;
}
// special case: boolean getter
if (methodName.startsWith("is")
&& (retType.equals(boolean.class) || retType
.equals(Boolean.class))) {
return true;
} else if (!methodName.startsWith("get")) {
return false;
}
return true;
}
public static boolean isNull(final Object obj) {
return obj == null || obj.equals(NULL_OBJECT_DENOTER);
}
public static Object safeInvoke(final Method method, final Object obj) {
Object retval = null;
final String className = obj.getClass().getName();
final String methodName = method.getName();
try {
// if the method is blacklisted, we ignore.
if (getBlackList().contains(methodName + " " + className)) {
return null;
}
if (!Map.class.isAssignableFrom(obj.getClass())
&& !obj.getClass().isArray()
&& !List.class.isAssignableFrom(obj.getClass())) {
// This is a workaround for a known bug in the JVM
// where method.invoke throws IllegalAccessException on inner
// class public method.
// link:
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4819108
// p.s: no private method should arrive here. private methods
// are filtered in getValidGetters.
if (!method.isAccessible()) {
method.setAccessible(true);
}
retval = method.invoke(obj, (Object[]) null);
} else {
return "DataSet " + obj.getClass().getTypeParameters()[0];
}
}
catch (final InvocationTargetException e) {
// TODO: Create exception class that will be handled in a different
// manner in the AdminAPIController
throw new RuntimeException(
"Invocation error: Failed to execute getter function "
+ method.getName() + ". Reason: " + e.getMessage(),
e);
} catch (final Exception e) {
throw new RuntimeException("Failed to execute getter function "
+ method.getName() + ". Reason: " + e.getMessage(), e);
}
return retval == null ? NULL_OBJECT_DENOTER : retval;
}
public static void setHostAddress(final String hostAddress) {
OutputUtils.hostAddress = hostAddress;
}
public static String getHostAddress() {
return hostAddress;
}
public static void setHostContext(final String hostContext) {
OutputUtils.hostContext = hostContext;
}
public static String getHostContext() {
return hostContext;
}
}