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

org.bdware.doip.cluster.client.DoipClusterClient Maven / Gradle / Ivy

There is a newer version: 1.5.4
Show newest version
package org.bdware.doip.cluster.client;

import com.google.gson.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bdware.doip.audit.EndpointConfig;
import org.bdware.doip.audit.client.AuditDoipClient;
import org.bdware.doip.audit.client.AuditIrpClient;
import org.bdware.doip.audit.writer.AuditConfig;
import org.bdware.doip.audit.writer.AuditType;
import org.bdware.doip.cluster.util.ResultCollector;
import org.bdware.doip.cluster.util.RouterTool;
import org.bdware.doip.codec.JsonDoipMessage;
import org.bdware.doip.codec.doipMessage.DoipMessage;
import org.bdware.doip.codec.doipMessage.DoipMessageFactory;
import org.bdware.doip.codec.doipMessage.DoipMessageSigner;
import org.bdware.doip.codec.doipMessage.DoipResponseCode;
import org.bdware.doip.codec.operations.BasicOperations;
import org.bdware.doip.encrypt.SM2Signer;
import org.bdware.doip.endpoint.client.DoipClientImpl;
import org.bdware.doip.endpoint.client.DoipMessageCallback;
import org.bdware.irp.exception.IrpClientException;
import org.bdware.irp.irplib.exception.IrpConnectException;
import org.bdware.sc.bean.JoinInfo;
import org.bdware.sc.bean.RouteInfo;
import org.bdware.sc.util.JsonUtil;
import org.zz.gmhelper.SM2KeyPair;
import org.zz.gmhelper.SM2Util;
import wrp.jdk.nashorn.api.scripting.NashornScriptEngineUtil;

import javax.script.ScriptException;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;


//Client for single bdo running in cluster model.
//Leverage resolved result as input.
//connections between different bdo is reused if possible.
public class DoipClusterClient extends DoipClientImpl {
    private final DoipMessageSigner doipMessageSigner;
    private AuditIrpClient irsClient;
    private EndpointConfig routerConfig;
    // all the doipOperation and routeInfo binding relationships
    private Map doipOperationToRouteInfo;
    // all the doipOperation and joinInfo binding relationships
    private Map doipOperationToJoinInfo;
    // 维护了地址 与 doipPort之间的映射关系
    static Map addressToClient;
    // BDO和DoipClusterClient是一一对应的关系,一个BDO对应一组BDRepo,一个BDRepo对应了一个DoipServer端口,因此这里维护Repo -> 端口的映射
    private Map bdRepoToBDOClient;
    Logger Logger = LogManager.getLogger(DoipClusterClient.class);
    private final RouterTool routerTool;
    String curDOID;
    boolean enableAutoDelegate;
    JsonObject appendixes;

    private NashornScriptEngineUtil engineUtil;

    public DoipClusterClient(EndpointConfig routerConfig) throws IrpClientException, ScriptException {
        this.routerConfig = routerConfig;
        irsClient = new AuditIrpClient(routerConfig);
        try {
            irsClient.reconnect();
        } catch (IrpConnectException e) {
            e.printStackTrace();
        }
        Logger.info("The repoIrpClient initialization has done");
        // initiate doipMessageSigner
        if (routerConfig.publicKey != null && routerConfig.privateKey != null) {
            doipMessageSigner = new SM2Signer(SM2KeyPair.fromJson(new Gson().toJson(routerConfig)));
        } else
            doipMessageSigner = new SM2Signer(SM2Util.generateSM2KeyPair());

        engineUtil = new NashornScriptEngineUtil();
        doipOperationToRouteInfo = new HashMap<>();
        doipOperationToJoinInfo = new HashMap<>();
        addressToClient = new ConcurrentHashMap<>();
        bdRepoToBDOClient = new ConcurrentHashMap<>();
        routerTool = new RouterTool(irsClient, engineUtil);
        Logger.info("The DoipClusterClient has been initialized");
    }


    public void parseDOID(String doid) {
        for (int retry = 0; retry < 5; retry++)
            try {
                // 如果已经解析过了,就不需要再次进行解析了
                synchronized (this) {
                    if (!doid.equals(curDOID)) {
                        JsonObject BDOInfo = routerTool.verifyCluster(doid);
                        // 读取appendixes并放入变量
                        appendixes = BDOInfo.get("appendixes").getAsJsonObject();

                        // 传递的是BDO.clusterInfo -> BDRepoIDs
                        // BDRepoID -> BDRepo.address
                        routerTool.parseClusterInfo(BDOInfo, engineUtil);

                        // 传递的是BDO.clusterInfo -> BCOID
                        // 传递的是BCOID -> BCO.accessRules
                        routerTool.parseAccessRules(doipOperationToRouteInfo, doipOperationToJoinInfo, BDOInfo);
                        curDOID = doid;
                    }
                }
                return;
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    Thread.sleep(retry + retry * (10 << retry));
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
            }
    }


    @Override
    public void sendMessage(DoipMessage doipMessage, DoipMessageCallback cb) {
        try {
            BasicOperations op = BasicOperations.getDoOp(doipMessage.header.parameters.operation);
            JsonDoipMessage message = JsonDoipMessage.fromDoipMessage(doipMessage);
            String[] BDRepoIDs = route(message);
            // joinParams都来自于callback,joinParams就是所有节点callback到达之后,merge之后的结果
            JoinInfo joinInfo = getJoinInfo(op);
            sendMsgAndJoin(doipMessage, joinInfo, BDRepoIDs, cb);
        } catch (Exception e) {
            e.printStackTrace();
            DoipMessageFactory.DoipMessageBuilder builder = new DoipMessageFactory.DoipMessageBuilder();
            builder.createResponse(DoipResponseCode.UnKnownError, doipMessage);
            cb.onResult(builder.create());
        }

    }

    public AuditDoipClient getFastClientByUrl(String previousAddress, String address, String version) {
        AuditDoipClient conn2 = getClientByUrl(address, version);
        synchronized (bdRepoToBDOClient) {
            AuditDoipClient conn = bdRepoToBDOClient.get(previousAddress);
            if (conn == null && conn2 != null) {
                bdRepoToBDOClient.put(previousAddress, conn2);
            } else conn2 = conn;
        }
        return conn2;
    }

    static ExecutorService pool = Executors.newCachedThreadPool(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        }
    });

    public AuditDoipClient getClientByUrl(String address, String version) {
        AuditDoipClient doipClientImpl = null;
        try {
            if (address == null && version == null) return null;
            synchronized (addressToClient) {
                doipClientImpl = addressToClient.getOrDefault(address, null);
                if (doipClientImpl == null) {
                    doipClientImpl = createWithoutConnect(address, version);
                    if (doipClientImpl != null)
                        addressToClient.put(address, doipClientImpl);
                }
            }
            if (doipClientImpl != null)
                tryReconnectSync(doipClientImpl);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return doipClientImpl;
    }

    static class Wrapper {
        T t;
    }

    private AuditDoipClient tryReconnectSync(AuditDoipClient doipClientImpl) {
        Wrapper wrapper = new Wrapper<>();
        Future task = pool.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (doipClientImpl) {
                        if (!doipClientImpl.isConnected()) {
                            LOGGER.info("try reconnect to:" + doipClientImpl.getRepoUrl());
                            doipClientImpl.reconnect();
                        }
                    }
                    wrapper.t = doipClientImpl;
                } catch (Exception e) {
                    ByteArrayOutputStream bo = new ByteArrayOutputStream();
                    e.printStackTrace(new PrintStream(bo));
                    LOGGER.info("failed to connect to:" + doipClientImpl.getRepoUrl() + " " + bo.toString());
                } finally {
                    synchronized (wrapper) {
                        wrapper.notify();
                    }
                }
            }
        });
        synchronized (wrapper) {
            try {
                wrapper.wait(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (wrapper.t == null) {
            LOGGER.error("!!!!!!!!!!!!!!!!!!!Reconnect Exception " + doipClientImpl.getRepoUrl() + "!!!!!!!!!!!!!!!!!!!!!!!");
            task.cancel(true);
        }
        return wrapper.t;
    }

    private AuditDoipClient createWithoutConnect(String address, String version) {
        AuditDoipClient doipClientImpl = new AuditDoipClient(AuditConfig.newInstance(null, AuditType.None, null), null);
        LOGGER.info("connect to:" + address);
        doipClientImpl.setRepoUrl(address);
        return doipClientImpl;
    }

    static Logger LOGGER = LogManager.getLogger(DoipClusterClient.class);

    // 根据请求参数,执行RouteInfo,寻找DoipServers
    public String[] route(JsonDoipMessage doipParam) {
        try {
            parseDOID(doipParam.header.identifier);
            BasicOperations operations = BasicOperations.getDoOp(doipParam.header.operation);

            // If the operation truly has routeInfo
            RouteInfo routeInfo = doipOperationToRouteInfo.getOrDefault(operations.getName(), null);
            String[] BDRepoIDs = engineUtil.doipClusterUtil.doipServers;
            if (routeInfo != null) {
                if (routeInfo.useDefault == null) {
                    JsonElement requester = routerConfig.publicKey == null ? JsonNull.INSTANCE : new JsonPrimitive(routerConfig.publicKey);
                    return engineUtil.invokeFunction(routeInfo.funcName, String[].class, JsonUtil.parseObject(doipParam), requester);
                }

                switch (routeInfo.useDefault) {
                    case byRequester:
                        int val = new BigInteger(routerConfig.publicKey, 16).mod(new BigInteger("" + BDRepoIDs.length)).intValue();
                        while (val < 0 && BDRepoIDs.length > 0) {
                            val = val + BDRepoIDs.length;
                        }
                        return new String[]{BDRepoIDs[val]};
                    case byArgHash:
                        val = JsonUtil.toJson(doipParam).hashCode();
                        val = val % BDRepoIDs.length;
                        while (val < 0 && BDRepoIDs.length > 0) {
                            val += BDRepoIDs.length;
                        }
                        return new String[]{BDRepoIDs[val]};
                    case byJsonPropHash:
                        JsonElement jo = tryLoadJsonProp(JsonUtil.parseObject(doipParam).getAsJsonObject(), routeInfo.param);
                        val = jo.toString().hashCode() % BDRepoIDs.length;
                        while (val < 0 && BDRepoIDs.length > 0) {
                            val += BDRepoIDs.length;
                        }
                        return new String[]{BDRepoIDs[val]};
                    default:
                        return BDRepoIDs;
                }

                // 如果没有RouteInfo,默认随机选择一台机器发送
            } else {
                Logger.warn("Not RouteInfo in the function");
                return BDRepoIDs.length != 0 ? new String[]{BDRepoIDs[(int) (Math.random() * BDRepoIDs.length)]} : BDRepoIDs;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    private JsonElement tryLoadJsonProp(JsonObject routeInfoArg, String param) {
        try {
            if (routeInfoArg == null)
                return JsonNull.INSTANCE;
            JsonObject arg;
            if (routeInfoArg.isJsonPrimitive()) {
                arg = JsonUtil.parseString(routeInfoArg.getAsString()).getAsJsonObject();
            } else arg = routeInfoArg.getAsJsonObject();
            if (!param.contains(".")) {
                return arg.get(param);
            } else {
                String[] props = param.split("\\.");
                JsonElement result = arg;
                for (String str : props)
                    result = result.getAsJsonObject().get(str);
                return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return JsonNull.INSTANCE;
        }
    }

    protected int getJoinCount(JoinInfo joinInfo, int serversNum, JsonObject joinParams) {
        try {
            if (joinInfo == null) return serversNum;
            if (joinInfo.joinCountFuncName != null) {
                JsonElement requester = routerConfig.publicKey == null ? JsonNull.INSTANCE : new JsonPrimitive(routerConfig.publicKey);
                return engineUtil.invokeFunction(joinInfo.joinCountFuncName, Integer.class, joinParams, requester);
            }
            if (joinInfo.joinCount != 0) return joinInfo.joinCount;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return serversNum;
    }

    public JoinInfo getJoinInfo(BasicOperations operation) {
        return doipOperationToJoinInfo.getOrDefault(operation.getName(), null);
    }

    public void sendMsgAndJoin(DoipMessage msg, JoinInfo joinInfo, String[] BDRepoIDs, DoipMessageCallback cb) {
        int serversNum = BDRepoIDs.length;
        int count = getJoinCount(joinInfo, serversNum, new JsonObject());
        // 如果joinCountFunc执行算出的joinCount,比所有的servers还要多,就更新为servers的数量
        if (serversNum < count) {
            count = serversNum;
        }

        if (count > 0) {
            ResultCollector resultCollector = new ResultCollector(msg, cb, count, engineUtil, joinInfo);
            for (String BDRepoId : BDRepoIDs) {
                // 如果已经建立过BDRepo -> BDO的映射,可以通过BDRepo的地址,直接找到BDO的Client,对其进行操作(本质BDRepo和BDO是一一对应的关系)
                String address = appendixes.get(BDRepoId).getAsJsonObject().get("address").getAsString();
                String version = appendixes.get(BDRepoId).getAsJsonObject().get("version").getAsString();
                AuditDoipClient doipClient = getFastClientByUrl(address, null, null);
                if (doipClient == null)
                    doipClient = getClientByUrl(address, version);
                if (doipMessageSigner != null) {
                    doipMessageSigner.signMessage(msg);
                }
                // 这里的doipClient为BDRepoClient或者BDOClient,无所谓,如果是BDRepoClient,DelegateDoipMessageCallback会出手转为BDOClient
                DelegateDoipMessageCallback delegateDoipMessageCallback = new DelegateDoipMessageCallback(doipClient, this, resultCollector, msg);
                doipClient.sendMessage(msg, delegateDoipMessageCallback);
            }
        } else {
            Logger.error("joinCount is not a positive num");
        }
    }

    @Override
    public void close() {
        for (AuditDoipClient client : bdRepoToBDOClient.values())
            try {
                client.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

        bdRepoToBDOClient.clear();

    }

    public void closeAll() {
        close();
        for (AuditDoipClient client : addressToClient.values()) {
            try {
                if (client.isConnected()) client.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        addressToClient.clear();
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy