org.bdware.doip.cluster.client.DoipClusterClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of doip-audit-tool Show documentation
Show all versions of doip-audit-tool Show documentation
doip audit tool developed by bdware
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();
}
}