All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.lab515.qray.runtime.Remoto Maven / Gradle / Ivy

There is a newer version: 1.0.12
Show newest version
package io.github.lab515.qray.runtime;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

// as base, we didn't specify the remotetype, only provide a annoation tag
public abstract class Remoto implements Remotee {
    public static final int              C_RESULTS = 3;
    // solve the ctor issue, and make sure getVar can be used as early as we can
    private static ThreadLocal                _q_cxt =  new ThreadLocal(); // me,. bind, varMethod, that
    // chekc if it's in remote mode, the lifecycle started at Remotee.execute method
    public static boolean isRemoteMode(){ return _q_cxt.get() != null;}
    // for any thread, getRemoto will give the current remoting object
    public static Remotee getRemotee(){
        Object[] cxt = _q_cxt.get();
        return cxt != null ? (Remotee)cxt[0] : null;
    }
    public static Remoto getRemoto() {
        Remotee remotee = getRemotee();
        return remotee != null ? remotee.getStub() : null;
    }
    // get properties as object
    public static  T getObject(String vName) throws Exception {
        Remotee rm = getRemoto();
        return rm != null ? (T)rm.getStub().getBindObject(vName) : null;
    }
    // get property as string
    public static String getVar(String vName) throws Exception {
        Remotee rm = getRemoto();
        return rm != null ? rm.getStub().getBindVar(vName) : null;
    }

    // main entry for qray
    public static String execute(Object that, Object bind, String data) {
        Remotee target = null;
        ArrayList remotees = null;
        try {
            // me,. bind, var, that, as step 1.
            _q_cxt.set(
                    new Object[]{
                            null,
                            bind,
                            bind.getClass().getMethod("getVariable", String.class),
                            that}); // no getDeclaredMethod
            String[] segs = unpackData(data); // pd 0: methods, 1: files, 2:
            // object fields info!!!
            String[] methods = segs[0].split("\n"); // awesome
            LinkedHashMap map = new LinkedHashMap();
            Class clz = null;
            try{
                clz = Thread.currentThread().getContextClassLoader().loadClass(methods[0].trim());
            }catch(ClassNotFoundException e){
                clz = that.getClass().getClassLoader().loadClass(methods[0].trim()); // this is the class
            }
            if (!Remotee.class.isAssignableFrom(clz)) return "Error: remote object must always implement Remoto interface!";
            remotees = new ArrayList();
            target = unpackBase(segs.length > 2 ? segs[2] : null,clz, null, null, remotees);
            return target.getStub().executeInternal(that, bind, segs, methods, remotees);
        } catch (Exception e) {
            e.printStackTrace();
            return "Exception during initiate remote object from remote: " + e.getClass().getCanonicalName() + "/" +  e.getMessage();
        }finally{
            if(target != null)target.getStub().uninitContext();
            if(remotees != null) remotees.clear();
        }
    }

    // NOTE: all Remotee's fields are not remotable!!!!!, starts with _q_!!
    private String[]                     _q_data             = null; // remote
    private String[]                     _q_addFiles         = null; // local, thread safe (owned by user)
    private String[]                     _q_addClses         = null; // local, owned by user
    private ArrayList             _q_fields           = null; // local. only in packeMe unPackMe (synchronized)
    //private ArrayList            _q_runners          = null; // remote is safe, local need sync

    private String                       _q_provider         = null; // should be safe, inited in early stage
    private String                       _q_nonProviders     = null; // same

    private Object                       _q_binds            = null;
    private Method                       _q_getVar           = null;
    private Object                       _q_that             = null;
    private ArrayList            _q_threads          = null; // this will mark all threads who owns this Remotee
    private Remotee                      _q_target           = null;
    private boolean                      _q_rollback         = false; // added: but never used, it's more application related
    //public abstract void onTestDone(Method m); // callback before each test case run
    @Override
    public Remoto getStub(){return this;}

    //public abstract String onError(Throwable e) throws Exception;
    public abstract Object onResult(Method m, Object ret, Exception e) throws Exception;
    public abstract void onTestRun(Method m) throws Exception;
    public abstract void onRemoting(Method[] ms) throws Exception;
    public abstract void onRemoted(Method[] ms) throws Exception;

    public abstract void onReturning(Method[] ms) throws Exception;
    public abstract void onArrived(Method[] ms) throws Exception;
    public abstract Object getDryReturn(Method method); // this is for the dryrun return results

    public abstract String getConfig(String name);
    // object de-serialization/serialization
    public abstract  T unpack(String packedData, Class optionalClazz) throws Exception;
    public abstract String pack(Object t)throws Exception;
    // dryrun return object if necessary
    public abstract String getOutput() throws Exception;
    public abstract String zip(String data) throws Exception;
    public abstract String zip2(byte[] data) throws Exception;
    public abstract String unzip(String data) throws Exception;
    public abstract void output(String info);

    public String getProvider(){
        initContext();
        return _q_provider;
    }

    public boolean isRollback(){
        initContext();
        return _q_rollback;
    }

    public synchronized void initContext(){
        Object[] cxt = _q_cxt.get();
        if((_q_getVar != null) == (cxt != null))return;
        Remotee rmt = null;
        if(_q_getVar == null) {
            _q_binds = cxt[1];
            _q_getVar = (Method) cxt[2];
            if(cxt[0] == null)cxt[0] = _q_target;
            else rmt = (Remotee)cxt[0];
            String p = null;
            try{
                p = (String)_q_getVar.invoke(_q_binds,"_provider");
            }catch(Exception e){e.printStackTrace();} // normally it won't be a issue
            if (p != null) p = p.trim();
            if (p == null || p.length() < 1) p = null; // hard set
            _q_provider = p;
            try {
                p = (String) _q_getVar.invoke(_q_binds, "_nonProviders"); // hard set
            }catch (Exception e){e.printStackTrace();}
            if (p != null && (p = p.trim()).length() < 1) p = null;
            else p = "," + p + ",";
            _q_nonProviders = p;
            try{
                p = (String)_q_getVar.invoke(_q_binds,"_rollback");
            }catch(Exception e){e.printStackTrace();} // normally it won't be a issue
            if (p != null && p.trim().equalsIgnoreCase("true"))_q_rollback = true;
            _q_that = cxt[3];
        }else{
            _q_cxt.set(
                    new Object[]{
                            _q_target,
                            _q_binds,
                            _q_getVar,
                            _q_that}); // no getDeclaredMethod
        }
        if(rmt == null)rmt = _q_target;
        if(rmt.getStub()._q_threads == null)rmt.getStub()._q_threads = new ArrayList();
        if(rmt.getStub()._q_threads.indexOf(Thread.currentThread()) < 0)rmt.getStub()._q_threads.add(Thread.currentThread());
    }
    private void uninitContext(){
        // we need to get the current threads, for each threads
        Object[] cxt = _q_cxt.get();
        if(cxt != null) {
            for(int i  =0; i < cxt.length;i++)cxt[0] = null;
        }
        _q_cxt.remove(); // remove the refer
        // loop through all the threads
        if(_q_threads == null || _q_threads.size() < 1)return;
        try {
            Field f = Thread.class.getDeclaredField("threadLocals");
            f.setAccessible(true);
            Method m = null;
            for (Thread t : _q_threads) {
                if (t == null) continue;
                Object map = f.get(t);
                if (map == null) continue;
                if (m == null){
                    m = map.getClass().getDeclaredMethod("remove", ThreadLocal.class);
                    m.setAccessible(true);
                }
                cxt = (Object[]) m.invoke(map, _q_cxt);
                if (cxt != null) {
                    for (int i = 0; i < cxt.length; i++) cxt[0] = null;
                }
                cxt = null;
            }
        }catch  (Exception e){
            e.printStackTrace();
        }
        _q_threads.clear();
        _q_threads = null;
    }

    boolean canPack(Class cz, Remotable fieldAnno, boolean paraMode){
        if(fieldAnno != null)return fieldAnno.type() == Remotype.UNDEFINED || fieldAnno.type() == Remotype.REMOTE; // SKIP, LOCAL
        if(cz.isArray()){
            return paraMode ? canPack(cz.getComponentType(),null,true) : false;
        }
        if(cz.isPrimitive() || Remotee.class.isAssignableFrom(cz))return true;
        String c = cz.getCanonicalName();
        if(c.startsWith("java.lang.") && ".Integer.Double.Long.Float.String.Boolean.Short.Character.Byte.".indexOf(c.substring(9) + ".") >= 0)return true;
        fieldAnno = (Remotable)cz.getAnnotation(Remotable.class);
        if(fieldAnno == null)return false;
        return fieldAnno.type() == Remotype.REMOTE || fieldAnno.type() == Remotype.UNDEFINED;
    }
    int hasPacked(Remotee that, ArrayList remotees) throws Exception{
        int ret = remotees.indexOf(that);
        if(remotees.size() < 1 && that != _q_target) throw new Exception("packing object first one must be Remotee object itself!");
        if(ret < 0) remotees.add(that);
        return ret;
    }
    synchronized String packMe(boolean staticOnly, boolean pack, ArrayList remotees) throws Exception{
        int has = hasPacked(_q_target, remotees);
        if(!pack)return null;
        if(has >= 0)return "#" + has; // a ref no
        if(_q_fields == null) {
            _q_fields = new ArrayList();
            Class cc = _q_target.getClass();
            HashSet checker = new HashSet();
            while (cc != null && cc != Remotee.class && cc != Remoto.class) {
                for (Field fld : cc.getDeclaredFields()) {
                    if (Modifier.isFinal(fld.getModifiers()))
                        continue; // final is not easy to set, and we don't support
                    // no package
                    if (!canPack(fld.getType(), fld.getAnnotation(Remotable.class), false)) continue;
                    if (checker.contains(fld.getName())) continue; // child > parent
                    _q_fields.add(fld);
                }
                cc = cc.getSuperclass();
            }
        }
        if(_q_fields.size() < 1)return null;
        ArrayList ret = new ArrayList();
        //String[] ret = new String[_q_fields.size() * 2];
        for(int i  =0; i < _q_fields.size();i++){
            Field f = _q_fields.get(i);
            if(!Modifier.isStatic(f.getModifiers()) && staticOnly)continue;
            ret.add(f.getDeclaringClass().getName() + "." + f.getName());
            // find the object
            f.setAccessible(true);
            Object t = f.get(Modifier.isStatic(f.getModifiers()) ? null : _q_target);
            if(t == null)ret.add(null);
            else if(t instanceof Remotee)ret.add(((Remotee)t).getStub().packMe(false, true, remotees));
            else ret.add(pack(t));
        }
        if(ret.size() < 1)return null;
        String[] rr = new String[ret.size()];
        ret.toArray(rr);
        ret.clear();
        return "#" + _q_target.getClass().getName() + "#" + packData(rr);
    }
    // remote only, thread safe
    static Remotee unpackBase(String packedMe, Class clz, Remoto packer, Remotee target, ArrayList remotees) throws Exception{
        String rc = null;
        if(packedMe != null && packedMe.length() > 0){
            if(!packedMe.startsWith("#"))throw new Exception("invalid RunBase package, should starts with #clazz#data, or #index");
            int p = packedMe.indexOf('#',1);
            if(p < 0){
                return remotees.get(Integer.parseInt(packedMe.substring(1)));
            }
            rc = packedMe.substring(1,p);
            packedMe = packedMe.substring(p+1);
        }
        if(target == null){
            if(rc != null && !clz.getName().equals(rc)){
                clz = clz.getClassLoader().loadClass(rc);
            }

            if(Remotee.class.isAssignableFrom(clz)){
                Constructor ctor = clz.getConstructor();
                ctor.setAccessible(true);
                target = (Remotee) ctor.newInstance();
                target.getStub()._q_target = target;
            }
            if(target == null)throw new Exception("failed initiate Remotee Object: " + clz.getCanonicalName());
        }
        if(packer == null)packer = target.getStub();
        packer.hasPacked(target, remotees);
        // first of all, let's pack it
        target.getStub().unpackMe(packedMe, remotees);
        return target;
    }

    synchronized void unpackMe(String packedData,ArrayList remotees) throws Exception{
        if(_q_fields == null) _q_fields = new ArrayList();
        if (packedData == null || packedData.length() < 1)return;
        String[] arr = unpackData(packedData);
        if(arr.length > 1 && _q_fields.size() == 0){
            // initiate the stuff
            Class m = _q_target.getClass();
            for(int i = 0; i  remotees) {
        initContext(); // just in case, actually it's already called in ctor
        // // pd 0: methods, 1: files, 2: object fields info!!!
        // set the transferred files
        _q_data = unpackData(segs[1]);
        LinkedHashMap map = new LinkedHashMap();
        Class cclz = _q_target.getClass();
        LinkedList clses = new LinkedList();
        //Method errorHandler = null;
        while (cclz != null && cclz != Remotee.class && cclz != Remoto.class && cclz != Object.class) {
            // FIX for ADF, this is a hard coded stuff
            // QInvoker regression, add a special logic to check Remotable annoation
            //if(cclz.getName().indexOf(".saf.") < 0) {
            try{
                for (Method m : cclz.getDeclaredMethods()) {
                    map.put(m.toGenericString(), m);
                    //if (m.getName().equals("onError") && Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers()) && m.getReturnType() != null && m.getReturnType().equals(String.class)) {
                    //    Class[] ss = m.getParameterTypes();
                    //    if (ss != null && ss.length == 1 && ss[0].equals(Throwable.class)) {
                    //        errorHandler = m;
                    //    }
                    //}
                }
            }catch(Throwable t){}
            cclz = cclz.getSuperclass();
        }


        // set object attrs
        try{
            //unpackMe(segs.length > 2 ? segs[2] : null);
            // put order
            ArrayList finalList = new ArrayList();
            ArrayList finalPs = new ArrayList();
            int testMethod = -1;
            for (int i = 1; i < methods.length; i++) {
                String m = methods[i].trim();
                if (m.length() < 1)
                    continue;
                if (m.startsWith("#")) {
                    testMethod = finalList.size();
                    m = m.substring(1);
                }
                // add: final change: add parameters
                int p = m.indexOf('@');
                String ps = null;
                if(p > 0){
                    ps = m.substring(p+1).trim();
                    m = m.substring(0,p).trim(); // the real method
                }
                if (map.containsKey(m)){
                    finalList.add(map.get(m));
                    finalPs.add(ps);
                }else {
                    return "method: " + m + " is not defined in class: " + methods[0];
                }
            }
            if (finalList.size() < 1 || testMethod < 0)
                return "there is no test method defined in method list";
            // ok, we need to execute method based on the order, and catch exception
            // as needed
            boolean stop = false;
            Method[] mtds = new Method[finalList.size()];
            finalList.toArray(mtds);
            finalList.clear();
            String[] result = new String[mtds.length * C_RESULTS + 1]; // exception,
            // log, added:
            // return object
            // the last one
            // will be the
            // object
            // attributes
            Object[] rets = new Object[mtds.length];
            try{
                this.onArrived(mtds);
            }catch(Exception e){
                // we do nothing, but just print
                e.printStackTrace();
            }
            for (int i = 0; i < mtds.length; i++) {
                if (i <= testMethod && stop) {
                    continue;
                }
                Method m = mtds[i];
                try {
                    m.setAccessible(true);
                    // do the parameters
                    String ps = finalPs.get(i);
                    Object [] ops = null;
                    Class[] tps = m.getParameterTypes();
                    if(ps != null && ps.length() > 0){
                        String[] pss = unpackData(unzip(finalPs.get(i)));
                        ops = new Object[pss.length];
                        if(ops.length == tps.length){
                            for(int p  =0; p < ops.length; p++){
                                if(Remotee.class.isAssignableFrom(tps[p]) ||
                                        (tps[p] == Object.class && pss[p] != null && pss[p].startsWith("#")))ops[p] = unpackBase(pss[p],tps[p],this, null, remotees);
                                else if(pss[p] != null)ops[p] = unpack(pss[p], tps[p]);
                            }
                        }
                    }

                    if((tps != null ? tps.length : 0) == (ops != null ? ops.length : 0)){
                        Object ret = m.invoke(Modifier.isStatic(m.getModifiers()) ? null : _q_target,ops);
                        if(m.getReturnType() != Void.class && ret != null){
                            // serailzie it
                            rets[i] = ret;
                            //result[i * C_RESULTS + 2] = pack(ret);
                        }
                    }else
                        result[i * C_RESULTS] = "invalid parameters number to call!";
                } catch (Throwable e) {
                    e.printStackTrace();
                    if (e.getCause() != null)
                        e = e.getCause();
                    ByteArrayOutputStream baos = null;
                    PrintStream ps = null;
                    try {
                        //if (errorHandler != null)
                        result[i * C_RESULTS] = pack(e);
                                    //+ errorHandler.invoke(Modifier.isStatic(errorHandler.getModifiers()) ? null : _q_target, e);
                    } catch (Exception e2) {
                        baos = new ByteArrayOutputStream();
                        ps = new PrintStream(baos);
                        e.printStackTrace(ps);
                        result[i * C_RESULTS] = baos.toString("utf-8");
                        //result[i * C_RESULTS] = "handling exception: " + e.getClass().getSimpleName() + ":" + e.getMessage() + " failed: " + e2.getClass().getSimpleName() + ":" + e2.getMessage();
                    } finally {
                        try {
                            if (ps != null)ps.close();
                        } catch (Exception ee) {}
                        try {
                            if (baos != null)baos.close();
                        } catch (Exception ee) {}
                    }
                    if (result[i * C_RESULTS] == null || result[i * C_RESULTS].length() < 1)
                        result[i * C_RESULTS] = "unknown exception";
                    stop = true;
                }
                // add: also we handle the log info
                String log = getOutput();
                if (log == null)log = "";
                result[i * C_RESULTS + 1] = log;
            }
            try{
                this.onReturning(mtds);
            }catch(Exception e){
                // we do nothing, but just print
                e.printStackTrace();
            }
            //clean up!!
            remotees.clear();
            // step1, pack me
            result[result.length - 1] = packMe(false,true, remotees);
            // pack all rest stuff
            for(int i  =0; i < rets.length;i++){
                if(rets[i] != null && result[i * C_RESULTS] == null){ // no exception
                    result[i * C_RESULTS + 2] = (rets[i] instanceof Remotee) ? ((Remotee)rets[i]).getStub().packMe(false,  true, remotees) : pack(rets[i]);
                }
            }
            return "#" + zip(packData(result));
        }catch(Exception e){
            e.printStackTrace();
            return "unexpected runtime error: " + e.getMessage() + (e.getCause() != null ? e.getCause().getMessage() : "");
        }

    }

    /**
     * Simple format of data packing
     *
     * @param arr
     *            array of string
     * @return string
     */
    static String packData(String[] arr) {
        return packData(arr, -1);
    }

    /**
     * Simple format of data packing
     *
     * @param arr
     *            array of string
     * @param max
     *            the max items to pack
     * @return string
     */
    static String packData(String[] arr, int max) {
        if (arr == null || arr.length < 1) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < arr.length && (max < 1 || i < max); i++) {
            if (arr[i] == null) {
                sb.append("-1:");
            } else {
                sb.append(arr[i].length());
                sb.append(":");
                sb.append(arr[i]);
            }
        }
        String s = sb.toString();
        sb.setLength(0);
        return s;
    }

    static String[] unpackData(String value) {
        if (value == null || value.length() < 1) {
            return null;
        }

        int len = value.length();
        if (len < 2) {
            return null;
        }
        int pos = 0;
        List ret = new ArrayList();
        int segLen = 0;
        int startPos = 0;
        while (pos < len - 1) {
            // try to get the next index
            segLen = value.indexOf(":", pos);
            if (segLen <= pos) {
                break;
            }
            startPos = segLen + 1;
            segLen = Integer.parseInt(value.substring(pos, segLen));
            if (segLen >= 0 && startPos + segLen <= len) {
                ret.add(value.substring(startPos, startPos + segLen));
                pos = startPos + segLen;
            } else if (segLen == -1) {
                ret.add(null);
                pos = startPos;
            } else {
                break;
            }
        }

        if (pos != len || ret.size() < 1) {
            return null;
        }
        String[] rets = new String[ret.size()];
        ret.toArray(rets);
        ret.clear();
        return rets;
    }



    protected  T getBindObject(String vName) throws Exception {
        if(vName == null || vName.length() < 1)return (T)null;
        initContext();
        if (_q_getVar == null)
            throw new Exception("bind properties not initiated, contact amdin!");
        T ret = null;
        if(_q_provider != null &&
                !(vName.equals("_provider") || vName.equals("_nonProviders"))){
            if(_q_nonProviders == null || _q_nonProviders.indexOf( "," + vName + ",") < 0){
                ret = (T)_q_getVar.invoke(_q_binds, _q_provider + "." + vName);
                if(ret != null)return ret;
            }
        }
        return (T) _q_getVar.invoke(_q_binds, vName);
    }

    protected String getBindVar(String vName) throws Exception {
        // there are certain keys
        Object t = getBindObject(vName);
        if (t instanceof String)
            return (String) t;
        else
            return t != null ? t.toString() : null;
    }

    public String readFileEncoded(String name) throws Exception {
        if (name == null || name.length() < 1)
            return null;
        if (_q_data == null)
            throw new Exception("no files transfered to server, please use anno Remotable(resources=)!");
        String hit = null;
        String hitName = null;
        for (int i = 0; i < _q_data.length - 1; i += 2) {
            if(_q_data[i] == null || _q_data[i].length() < 1)continue;
            if (hit == null && _q_data[i].equals(name)) {
                hit = _q_data[i + 1];
                hitName = _q_data[i];
                break;
            } else if (hit == null && _q_data[i].indexOf(name) >= 0) {
                hit = _q_data[i + 1];
                hitName = _q_data[i];
            }
        }
        if(hitName == null)throw new Exception("file " + name + " is not present, please include it in Remotable resources");
        else if(hit == null){
            throw new Exception(
                    (hitName.equals(name) ?
                            "file " + name : "file " + name +"'s matcher " + hitName) + " is not transfered, please check your local data folder for existence!");
        }
        // return hit.length() > 0 ?
        // ExUtils.inflateDataBlock(QRUtils.decodeBase64(hit)) : new byte[0];
        return hit;
    }

    public void setExtraDataRetention(String[] fileNames){
        _q_addFiles = fileNames;
    }

    public void setExtraDependentClasses(String[] clses){
        _q_addClses = clses;
    }

    public String[] getExtraDataRetention(){
        return _q_addFiles;
    }

    public String[] getExtraDependentClasses(){
        return _q_addClses;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy