reflex.node.KernelExecutor.java Source code

Java tutorial

Introduction

Here is the source code for reflex.node.KernelExecutor.java

Source

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2011-2016 Incapture Technologies LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package reflex.node;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import rapture.common.RaptureTransferObject;
import rapture.common.exception.ExceptionToString;
import rapture.common.exception.RaptureException;
import rapture.common.exception.RaptureExceptionFactory;
import rapture.common.impl.jackson.JacksonUtil;
import reflex.ReflexException;
import reflex.value.ReflexFileValue;
import reflex.value.ReflexValue;
import reflex.value.internal.ReflexNullValue;

public final class KernelExecutor {

    protected KernelExecutor() {

    }

    private static final Logger log = Logger.getLogger(KernelExecutor.class);

    public static ReflexValue executeFunction(int lineNumber, Object outerApi, String areaName, String fnName,
            List<ReflexValue> params) {
        String apiName;
        if (!StringUtils.isEmpty(areaName) && areaName.length() > 1) {
            apiName = areaName.substring(0, 1).toUpperCase() + areaName.substring(1);
        } else {
            apiName = "<api name missing>";
        }
        int numPassedParams = params.size();
        try {
            // Find the method get[AreaName], which will return the area
            Method[] methods = outerApi.getClass().getMethods();
            String getApiMethodName = "get" + apiName;

            for (Method m : methods) {
                if (m.getName().equals(getApiMethodName)) {
                    // Call that method to get the api requested
                    Object api = m.invoke(outerApi);
                    // Now find the method with the fnName
                    Method[] innerMethods = api.getClass().getMethods();
                    for (Method im : innerMethods) {
                        if (im.getName().equals(fnName)) {
                            // the api should just have one entry
                            Type[] types = im.getGenericParameterTypes();
                            int numExpectedParams = types.length;
                            if (numExpectedParams == numPassedParams) {
                                List<Object> callParams = new ArrayList<Object>(types.length);
                                // Now coerce the types...
                                for (int i = 0; i < types.length; i++) {
                                    ReflexValue v = params.get(i);
                                    Object x = convertValueToType(v, types[i]);
                                    callParams.add(x);
                                }

                                // Now invoke
                                Object ret;
                                try {
                                    ret = im.invoke(api, callParams.toArray());
                                } catch (InvocationTargetException e) {
                                    // TODO Auto-generated catch block
                                    throw new ReflexException(lineNumber, String.format(
                                            "Error in Reflex script at line %d. Call to %s.%s failed: %s",
                                            lineNumber, apiName, fnName, e.getTargetException().getMessage()), e);
                                }
                                ReflexValue retVal = new ReflexNullValue(lineNumber);
                                if (ret != null) {
                                    retVal = new ReflexValue(convertObject(ret));
                                }
                                return retVal;
                            }
                        }
                    }
                    throw new ReflexException(lineNumber, String.format(
                            "API call not found: %s.%s (taking %s parameters)", apiName, fnName, numPassedParams));
                }
            }
            throw new ReflexException(lineNumber, "API '" + apiName + "' not found!");
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            if (cause != null) {
                if (cause instanceof OutOfMemoryError) {
                    log.error(ExceptionToString.format(e));
                    throw (OutOfMemoryError) cause;
                } else if (cause instanceof RaptureException) {
                    log.warn(ExceptionToString.format(e));
                    String message = ((RaptureException) cause).getFormattedMessage();
                    throw new ReflexException(lineNumber, message, e);
                } else {
                    String details = null;
                    if (cause.getMessage() != null) {
                        details = ": " + cause.getMessage();
                    }
                    RaptureException re = RaptureExceptionFactory
                            .create(String.format("Error executing api call: %s.%s (takes %s parameters)%s",
                                    apiName, fnName, numPassedParams, details), e);

                    String message = re.getFormattedMessage();
                    throw new ReflexException(lineNumber, message, re);
                }
            }
            throw new ReflexException(lineNumber, e.getTargetException().getMessage(), e);
        } catch (ReflexException e) {
            throw e;
        } catch (Exception e) {
            log.error(ExceptionToString.format(e));
            throw new ReflexException(lineNumber, e.getMessage(), e);
        }
    }

    public static Object convert(List<ReflexValue> asList) {
        List<Object> ret = new ArrayList<Object>(asList.size());
        for (Object o : asList) {
            if (o instanceof ReflexValue) {
                ReflexValue e = (ReflexValue) o;
                if (e.isMap()) {
                    ret.add(convert(e.asMap()));
                } else if (e.isList()) {
                    ret.add(convert(e.asList()));
                } else if (e.isComplex()) {
                    ret.add(convertObject(e.asObject()));
                } else {
                    ret.add(e.asObject());
                }
            } else {
                ret.add(o);
            }
        }
        return ret;
    }

    public static Object convertSimpleList(List<Object> asList) {
        List<Object> ret = new ArrayList<Object>(asList.size());
        for (Object o : asList) {
            if (o instanceof ReflexValue) {
                ReflexValue e = (ReflexValue) o;
                if (e.isMap()) {
                    ret.add(convert(e.asMap()));
                } else if (e.isList()) {
                    ret.add(convert(e.asList()));
                } else {
                    ret.add(e.asObject());
                }
            } else {
                ret.add(o);
            }
        }
        return ret;
    }

    @SuppressWarnings("unchecked")
    public static Map<String, Object> convert(Map<String, Object> asMap) {
        if (asMap == null)
            return null;
        Map<String, Object> converted = new LinkedHashMap<String, Object>();
        for (Map.Entry<String, Object> e : asMap.entrySet()) {
            if (e.getValue() instanceof ReflexValue) {
                ReflexValue val = (ReflexValue) e.getValue();
                if (val.isMap()) {
                    converted.put(e.getKey(), convert(val.asMap()));
                } else if (val.isList()) {
                    converted.put(e.getKey(), convert(val.asList()));
                } else if (val.isString()) {
                    converted.put(e.getKey(), val.asString());
                } else {
                    converted.put(e.getKey(), val.asObject());
                }
            } else if (e.getValue() instanceof List) {
                List<Object> vals = (List<Object>) e.getValue();
                converted.put(e.getKey(), convertSimpleList(vals));
            } else if (e.getValue() instanceof Map) {
                converted.put(e.getKey(), convert((Map<String, Object>) e.getValue()));
            } else {
                converted.put(e.getKey(), e.getValue());
            }
        }
        return converted;
    }

    public static String convertValueToJson(ReflexValue value, boolean prettyFy) {
        Object toJson = value.asObject();
        if (value.isMap()) {
            toJson = convert(value.asMap());
        } else if (value.isList()) {
            toJson = convert(value.asList());
        } else if (value.isStruct()) {
            toJson = convert(value.asStruct().asMap());
        }
        String ret = JacksonUtil.jsonFromObject(toJson);
        if (prettyFy) {
            ret = JacksonUtil.prettyfy(ret);
        }
        return ret;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static Object convertValueToType(ReflexValue v, Type type) {

        if (type.equals(byte[].class)) {
            if (v.isFile()) {
                // Load file to bytes and return that
                return getContentFromFile(v.asFile());
            } else if (v.isByteArray()) {
                return v.asByteArray();
            } else if (v.isNull()) {
                return "".getBytes();
            } else {
                return v.toString().getBytes();
            }
        } else if (type.equals(String.class)) {
            if (v.isFile()) {
                return new String(getContentFromFile(v.asFile()));
            } else if (v.isNull()) {
                return null;
            } else {
                return v.toString();
            }
        } else if (type.equals(Double.class) || type.equals(double.class)) {
            return v.asDouble();
        } else if (type.equals(Integer.class) || type.equals(int.class)) {
            return v.asInt();
        } else if (type.equals(Long.class) || type.equals(long.class)) {
            return v.asLong();
        } else if (type.equals(Boolean.class) || type.equals(boolean.class)) {
            return v.asBoolean();
        } else if (type instanceof ParameterizedType) {
            return handleParameterizedType(v, type);
        } else if (type.equals(Map.class) || type.equals(List.class)) {
            return handleParameterizedType(v, type);
        } else if (type instanceof Class && ((Class<?>) type).isEnum()) {
            return Enum.valueOf((Class<Enum>) type, v.asString());
        } else if (v.isMap()) {
            // If it's a map then JacksonUtil may be able to recreate the type
            return handleMap(v, type);
        }
        return v.asObject();
    }

    private static byte[] getContentFromFile(ReflexFileValue asFile) {
        try {
            return IOUtils.toByteArray(asFile.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static ReflexValue reconstructFromObject(Object x) throws ClassNotFoundException {
        if (x instanceof ReflexValue) {
            return (ReflexValue) x;
        }
        if (x instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> theMap = convert((Map<String, Object>) x);
            if (theMap.containsKey("CLASS")) {
                String typeName = theMap.get("CLASS").toString();
                theMap.remove("CLASS");
                String json = JacksonUtil.jsonFromObject(theMap);
                Object realObject = JacksonUtil.objectFromJson(json, Class.forName(typeName));
                return new ReflexValue(realObject);
            } else {
                return new ReflexValue(theMap);
            }
        } else if (x instanceof List) {
            List<?> r = (List<?>) x;
            List<ReflexValue> ret2 = new ArrayList<ReflexValue>(r.size());
            for (Object inner : r) {
                ret2.add(inner == null ? new ReflexNullValue() : reconstructFromObject(inner));
            }
            return new ReflexValue(ret2);
        } else {
            return new ReflexValue(x);
        }
    }

    private static Object handleParameterizedType(ReflexValue v, Type type) {
        if (!(type instanceof ParameterizedType)) {
            if (type.equals(String.class))
                return v.asString();
            if (type.equals(Double.class))
                return v.asDouble();
            if (type.equals(Long.class))
                return v.asLong();
            if (type.equals(BigDecimal.class))
                return v.asBigDecimal();
            return v.asObject();
        }

        ParameterizedType pType = (ParameterizedType) type;
        if (pType.getRawType().equals(Map.class)) {
            if (!v.isMap()) {
                log.error(v.toString() + " is not a map");
                return null;
            }
            Map<String, Object> convertedMap = new LinkedHashMap<>();

            for (Entry<String, Object> entry : v.asMap().entrySet()) {
                Type[] innerType = pType.getActualTypeArguments();
                String key = null;
                if (!innerType[0].equals(String.class)) {
                    // This could get tricky
                    log.warn("Keys for maps should always be Strings");
                }
                key = entry.getKey().toString();
                Object value = entry.getValue();
                if (value instanceof ReflexValue)
                    convertedMap.put(key, handleParameterizedType((ReflexValue) value, innerType[1]));
                else
                    convertedMap.put(key, value);
            }
            return convertedMap;
        } else if (pType.getRawType().equals(List.class)) {
            if (!v.isList()) {
                log.error(v.toString() + " is not a list");
                return null;
            }
            List<ReflexValue> inner = v.asList();
            Type innerType = pType.getActualTypeArguments()[0];
            if (innerType.equals(String.class)) {
                List<String> ret = new ArrayList<String>(inner.size());
                for (ReflexValue vi : inner) {
                    ret.add(vi.asString());
                }
                return ret;
            } else if (innerType.equals(Double.class)) {
                List<Double> ret = new ArrayList<>(inner.size());
                for (ReflexValue vi : inner) {
                    ret.add(vi.asDouble());
                }
                return ret;
            } else if (innerType.equals(Long.class)) {
                List<Long> ret = new ArrayList<>(inner.size());
                for (ReflexValue vi : inner) {
                    ret.add(vi.asLong());
                }
                return ret;
            } else if (innerType.equals(BigDecimal.class)) {
                List<BigDecimal> ret = new ArrayList<>(inner.size());
                for (ReflexValue vi : inner) {
                    ret.add(vi.asBigDecimal());
                }
                return ret;
            } else if (innerType instanceof ParameterizedType || inner instanceof List) {
                List<Object> ret = new ArrayList<>();
                for (ReflexValue vi : inner) {
                    ret.add(handleParameterizedType(vi, innerType));
                }
                return ret;
            } else {
                log.warn("Cannot convert " + v.toString() + " to " + type.toString());
            }
        } else if (v.isMap()) {
            // If it's a map then JacksonUtil may be able to recreate the type
            return handleMap(v, type);
        }
        return v.asObject();
    }

    static Object convertObject(Object ret) {
        if (ret instanceof Object[]) {
            Object[] r = (Object[]) ret;
            List<ReflexValue> ret2 = new ArrayList<ReflexValue>(r.length);
            for (Object x : r) {
                ret2.add(x == null ? new ReflexNullValue() : new ReflexValue(x));
            }
            return ret2;
        } else if (ret instanceof List) {
            List<?> r = (List<?>) ret;
            List<ReflexValue> ret2 = new ArrayList<ReflexValue>(r.size());
            for (Object x : r) {
                ret2.add(x == null ? new ReflexNullValue() : new ReflexValue(convertObject(x)));
            }
            return ret2;
        } else if (ret instanceof RaptureTransferObject) {
            // RaptureTransferObject is a hint to convert this object from an
            // object
            // to a json doc to a map
            String json = JacksonUtil.jsonFromObject(ret);
            Object retMap = JacksonUtil.getMapFromJson(json);
            @SuppressWarnings("unchecked")
            Map<String, Object> mapAsRet = (Map<String, Object>) retMap;
            mapAsRet.put("CLASS", ret.getClass().getName());
            return retMap;
        }
        return ret;
    }

    private static Object handleMap(ReflexValue v, Type type) {
        // If we have a map, we may be able to convert this into the Type by
        // using a JacksonUtil conversion
        // But first we need to walk the map to convert the values to native
        // types
        Map<String, Object> convertedMap = convert(v.asMap());
        if (convertedMap.containsKey("CLASS")) {
            convertedMap.remove("CLASS");
        }
        String json = JacksonUtil.jsonFromObject(convertedMap);
        Object x = JacksonUtil.objectFromJson(json, (Class<?>) type);
        return x;
    }
}