org.xenmaster.web.Hook.java Source code

Java tutorial

Introduction

Here is the source code for org.xenmaster.web.Hook.java

Source

/*
 * Hook.java
 * Copyright (C) 2011,2012 Wannes De Smet
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.xenmaster.web;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import net.wgr.core.ReflectionUtils;
import net.wgr.server.web.handling.WebCommandHandler;
import net.wgr.utility.GlobalExecutorService;
import net.wgr.wcp.command.Command;
import net.wgr.wcp.command.CommandException;
import net.wgr.wcp.command.Result;
import net.wgr.wcp.connectivity.Connection;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.xenmaster.api.util.APIHook;
import org.xenmaster.api.util.APIUtil;
import org.xenmaster.api.util.CachingFacility;
import org.xenmaster.controller.BadAPICallException;

/**
 *
 * @created Oct 1, 2011
 * @author double-u
 */
public class Hook extends WebCommandHandler {

    protected ConcurrentHashMap<Integer, StoredValue> store;
    protected Class clazz = null;
    protected Object current = null;
    protected String className = "", commandName;
    protected Connection connection;

    public Hook() {
        super("xen");

        store = new ConcurrentHashMap<>();
        GlobalExecutorService.get().scheduleAtFixedRate(new Housekeeper(), 1, 5, TimeUnit.MINUTES);
    }

    @Override
    public Object execute(Command cmd) {
        // Cleanup
        clazz = null;
        current = null;
        className = commandName = "";
        connection = cmd.getConnection();

        GsonBuilder builder = new GsonBuilder();
        builder.registerTypeAdapter(APICall.class, new APICallDecoder());
        Gson gson = builder.create();
        APICall apic = gson.fromJson(cmd.getData(), APICall.class);

        return executeInstruction(cmd.getName(), apic.ref, apic.args);
    }

    protected <T> String createLocalObject(Class<T> clazz, Object[] args) throws Exception {
        T obj = clazz.newInstance();

        if (args == null || args.length < 1 || args[0] == null || !(args[0] instanceof Map)) {
            throw new IllegalArgumentException("Illegal arguments map was given");
        }
        for (Map.Entry<String, Object> entry : ((Map<String, Object>) args[0]).entrySet()) {
            String methodName = "set" + entry.getKey().toLowerCase();
            boolean set = false;
            for (Method m : clazz.getMethods()) {
                if (!m.getName().toLowerCase().equals(methodName) || m.getParameterTypes().length != 1) {
                    continue;
                }
                m.invoke(obj, APIUtil.deserializeToTargetType(entry.getValue(), m.getParameterTypes()[0]));
                set = true;
            }

            if (!set) {
                Logger.getLogger(getClass()).debug("Given field was not able to be set: " + entry.getKey());
            }
        }

        return storeLocalObject(obj);
    }

    protected void determineClass(String ref, int index, String[] values) throws Exception {
        String s = values[index];
        int refOpen = s.indexOf('[');
        if (refOpen != -1) {
            className = s.substring(0, refOpen);
            clazz = Class.forName("org.xenmaster.api." + className);
        } else if (index == values.length - 2) {
            className += s;
            clazz = Class.forName("org.xenmaster.api." + className);
        } else {
            className += s + '.';
        }

        if (refOpen != -1) {
            ref = s.substring(refOpen + 1, s.indexOf(']'));
        }

        initClassInstance(ref, clazz);
    }

    protected void initClassInstance(String ref, Class clazz) {
        if (clazz != null && ref == null && APIHook.class.isAssignableFrom(clazz)) {
            try {
                Constructor ctor = clazz.getConstructor(Connection.class);
                current = ctor.newInstance(connection);
                return;
            } catch (Exception ex) {
                Logger.getLogger(getClass()).error("Failed to init APIHook", ex);
            }
        }

        // The reference may be an empty string, just not null
        if (ref != null && clazz != null) {
            if (ref.startsWith("LocalRef:")) {
                Integer localRef = Integer.parseInt(ref.substring(ref.indexOf(":") + 1));
                if (store.get(localRef) == null) {
                    current = new IllegalStateException("LocalRef has expired : " + localRef);
                    return;
                }
                current = store.get(localRef).value;
            } else {
                current = CachingFacility.get(ref, clazz);
            }
        }
    }

    protected Object findAndCallMethod(String ref, String s, Object[] args) throws Exception {
        int open = s.indexOf('(');
        String methodName = (open != -1 ? s.substring(0, open) : s);

        // Caller requested new instance of object
        if (methodName.equals("new")) {
            return current;
        }

        if (open != -1) {
            String argstr = s.substring(s.indexOf('(') + 1, s.indexOf(')'));
            argstr = argstr.replace(", ", ",");
            args = StringUtils.split(argstr, ',');
        }

        if (current != null) {
            clazz = current.getClass();
        }

        ArrayList<Method> matches = new ArrayList<>();

        // First find name matches
        for (Method m : ReflectionUtils.getAllMethods(clazz)) {
            if (m.getName().equals(methodName) && Modifier.isPublic(m.getModifiers())) {
                matches.add(m);
            }
        }

        // Then param count matches
        for (ListIterator<Method> it = matches.listIterator(); it.hasNext();) {
            Method m = it.next();

            if ((m.getParameterTypes().length > 0 && args == null)
                    || (args != null && m.getParameterTypes().length != args.length)) {
                it.remove();
            }
        }

        if (matches.size() > 0 && matches.size() < 2) {
            parseAndExecuteMethod(matches.get(0), args);
        } else if (matches.isEmpty()) {
            if (methodName.equals("build")) {
                return createLocalObject(clazz, args);
            } else {
                Logger.getLogger(getClass()).warn("Method not found " + s + " in " + commandName);
                return new CommandException("Method " + methodName + " was not found", commandName);
            }
        } else {
            // We cannot match based on type information as we use the type information to cast the parameters
            return new CommandException(
                    "The function call was ambiguous with " + matches.size() + " matched methods", commandName);
        }

        return null;
    }

    protected void parseAndExecuteMethod(Method m, Object[] args) throws Exception {
        Class<?>[] types = m.getParameterTypes();

        // Check method signature
        if ((types != null && types.length != 0)
                && ((types.length > 0 && args == null) || (types.length != args.length))) {
            Logger.getLogger(getClass()).info("Hook call made with incorrect number of arguments: " + commandName);
            current = new CommandException("Illegal number of arguments in " + m.getName() + " call", commandName);
        } else if (args != null) {
            for (int j = 0; j < types.length; j++) {
                Class<?> type = types[j];
                Object value = args[j];

                if (value == null) {
                    throw new IllegalArgumentException(
                            "An argument for  " + clazz.getSimpleName() + '.' + m.getName() + " was null");
                } else if (value instanceof String) {
                    String str = value.toString();

                    if (str.startsWith("LocalRef:")) {
                        Integer localRef = Integer.parseInt(str.substring(str.indexOf(":") + 1));
                        if (!store.containsKey(localRef)) {
                            current = new CommandException("Local object reference does not exist", commandName);
                        }
                        args[j] = store.get(localRef).value;
                    } else {
                        args[j] = APIUtil.deserializeToTargetType(value, type);
                    }
                } else {
                    args[j] = APIUtil.deserializeToTargetType(value, type);
                }
            }
        }

        try {
            if (Modifier.isStatic(m.getModifiers())) {
                current = m.invoke(null, args);
            } else {
                if (current == null) {
                    throw new IllegalArgumentException("Instance method called as a static method.");
                }
                m.setAccessible(true);
                current = m.invoke(current, args);
            }
        } catch (InvocationTargetException ex) {
            // If it has a cause, it will be parsed by the next handler
            if (ex.getCause() == null) {
                Logger.getLogger(getClass()).info("Failed to invoke method", ex);
                current = new CommandException(ex, commandName);
            } else {
                Logger.getLogger(getClass()).info("Hook call threw Exception", ex.getCause());
                if (ex.getCause() instanceof BadAPICallException) {
                    current = new DetailedCommandException(commandName, ((BadAPICallException) ex.getCause()));
                } else {
                    current = new CommandException(ex.getCause(), commandName);
                }
            }
        }

    }

    protected Object executeInstruction(String command, String ref, Object[] args) {
        String[] split = StringUtils.split(command, '.');
        commandName = command;

        for (int i = 0; i < split.length; i++) {
            String s = split[i];
            try {
                if (clazz == null) {
                    determineClass(ref, i, split);
                } else if (APIHook.class.isAssignableFrom(clazz) && ref == null) {
                    // API hooks are responsable for their own handling
                    return ((APIHook) current).handle(command, args, this);
                } else {
                    Object result = findAndCallMethod(ref, s, args);
                    if (result != null) {
                        return result;
                    }
                }
            } catch (Exception ex) {
                Logger.getLogger(getClass()).error("Instruction failed " + s, ex);
                return new CommandException(ex, commandName);
            }
        }
        if (current == null) {
            current = new Result(null, null, "EMPTY");
        }
        return current;
    }

    public String storeLocalObject(Object obj) {
        int ref = store.size();
        store.put(ref, new StoredValue(obj));
        return "LocalRef:" + ref;
    }

    public String getReferenceForLocalObject(Object obj) {
        for (Map.Entry<Integer, StoredValue> entry : store.entrySet()) {
            if (entry.getValue().value.equals(obj)) {
                return "LocalRef:" + entry.getKey();
            }
        }

        return null;
    }

    public static class APICall {

        public String ref;
        public Object[] args;
    }

    public static class StoredValue {

        public long lastAccess = System.currentTimeMillis();
        public Object value;

        public StoredValue(Object value) {
            this.value = value;
        }
    }

    protected class Housekeeper implements Runnable {

        @Override
        public void run() {
            for (Iterator<Map.Entry<Integer, StoredValue>> it = store.entrySet().iterator(); it.hasNext();) {
                Map.Entry<Integer, StoredValue> entry = it.next();
                Logger.getLogger(getClass()).debug("Deleting stale object with index LocalRef:" + entry.getKey());
                if (System.currentTimeMillis() - entry.getValue().lastAccess > 60000) {
                    it.remove();
                }
            }
        }
    }
}