Files
redkale/src/org/redkale/boot/Application.java
Redkale a1e37643d0
2020-05-16 22:12:45 +08:00

1015 lines
49 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package org.redkale.boot;
import org.redkale.util.RedkaleClassLoader;
import org.redkale.net.TransportGroupInfo;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;
import javax.annotation.Resource;
import javax.net.ssl.SSLContext;
import javax.xml.parsers.*;
import org.redkale.boot.ClassFilter.FilterEntry;
import org.redkale.convert.Convert;
import org.redkale.convert.bson.BsonFactory;
import org.redkale.convert.json.JsonFactory;
import org.redkale.net.*;
import org.redkale.net.http.MimeType;
import org.redkale.net.sncp.*;
import org.redkale.service.Service;
import org.redkale.source.*;
import org.redkale.util.AnyValue.DefaultAnyValue;
import org.redkale.util.*;
import org.redkale.watch.*;
import org.w3c.dom.*;
/**
*
* 进程启动类,全局对象。 <br>
* <pre>
* 程序启动执行步骤:
* 1、读取application.xml
* 2、进行classpath扫描动态加载Service、WebSocket与Servlet
* 3、优先加载所有SNCP协议的服务再加载其他协议服务 最后加载WATCH协议的服务
* 4、最后进行Service、Servlet与其他资源之间的依赖注入
* </pre>
* <p>
* 详情见: https://redkale.org
*
* @author zhangjx
*/
public final class Application {
/**
* 当前进程启动的时间, 类型: long
*/
public static final String RESNAME_APP_TIME = "APP_TIME";
/**
* 当前进程的根目录, 类型String、File、Path、URI
*/
public static final String RESNAME_APP_HOME = "APP_HOME";
/**
* 当前进程的配置目录如果不是绝对路径则视为HOME目录下的相对路径 类型String、File、Path、URI
*/
public static final String RESNAME_APP_CONF = "APP_CONF";
/**
* application.xml 文件中resources节点的内容 类型: AnyValue
*/
public static final String RESNAME_APP_GRES = "APP_GRES";
/**
* 当前进程节点的nodeid 类型int
*/
public static final String RESNAME_APP_NODEID = "APP_NODEID";
/**
* 当前进程节点的IP地址 类型InetAddress、String
*/
public static final String RESNAME_APP_ADDR = "APP_ADDR";
/**
* 当前Service所属的SNCP Server的地址 类型: SocketAddress、InetSocketAddress、String <br>
*/
public static final String RESNAME_SNCP_ADDR = "SNCP_ADDR";
/**
* 当前Service所属的SNCP Server所属的组 类型: String<br>
*/
public static final String RESNAME_SNCP_GROUP = "SNCP_GROUP";
/**
* "SERVER_ROOT" 当前Server的ROOT目录类型String、File、Path
*/
public static final String RESNAME_SERVER_ROOT = Server.RESNAME_SERVER_ROOT;
/**
* 当前Server的线程池
*/
public static final String RESNAME_SERVER_EXECUTOR = Server.RESNAME_SERVER_EXECUTOR;
/**
* 当前Server的ResourceFactory
*/
public static final String RESNAME_SERVER_RESFACTORY = Server.RESNAME_SERVER_RESFACTORY;
//本进程节点ID
final int nodeid;
//本地IP地址
final InetAddress localAddress;
//CacheSource 资源
final List<CacheSource> cacheSources = new CopyOnWriteArrayList<>();
//DataSource 资源
final List<DataSource> dataSources = new CopyOnWriteArrayList<>();
//NodeServer 资源, 顺序必须是sncps, others, watchs
final List<NodeServer> servers = new CopyOnWriteArrayList<>();
//SNCP传输端的TransportFactory, 注意: 只给SNCP使用
final TransportFactory sncpTransportFactory;
//第三方服务发现管理接口
final ClusterAgent[] clusterAgents;
//全局根ResourceFactory
final ResourceFactory resourceFactory = ResourceFactory.root();
//服务配置项
final AnyValue config;
//临时计数器
CountDownLatch servicecdl; //会出现两次赋值
//是否启动了WATCH协议服务
boolean watching;
//--------------------------------------------------------------------------------------------
//是否用于main方法运行
final boolean singletonrun;
//根WatchFactory
//private final WatchFactory watchFactory = WatchFactory.root();
//进程根目录
private final File home;
//配置文件目录
private final URI confPath;
//日志
private final Logger logger;
//监听事件
private final List<ApplicationListener> listeners = new CopyOnWriteArrayList<>();
//服务启动时间
private final long startTime = System.currentTimeMillis();
//Server启动的计数器用于确保所有Server都启动完后再进行下一步处理
private final CountDownLatch serversLatch;
//根ClassLoader
private final RedkaleClassLoader classLoader;
//Server根ClassLoader
private final RedkaleClassLoader serverClassLoader;
private Application(final AnyValue config) {
this(false, config);
}
private Application(final boolean singletonrun, final AnyValue config) {
this.singletonrun = singletonrun;
this.config = config;
System.setProperty("redkale.version", Redkale.getDotedVersion());
final File root = new File(System.getProperty(RESNAME_APP_HOME));
this.resourceFactory.register(RESNAME_APP_TIME, long.class, this.startTime);
this.resourceFactory.register(RESNAME_APP_HOME, Path.class, root.toPath());
this.resourceFactory.register(RESNAME_APP_HOME, File.class, root);
this.resourceFactory.register(RESNAME_APP_HOME, URI.class, root.toURI());
try {
this.resourceFactory.register(RESNAME_APP_HOME, root.getCanonicalPath());
this.home = root.getCanonicalFile();
String confsubpath = System.getProperty(RESNAME_APP_CONF, "conf");
if (confsubpath.contains("://")) {
this.confPath = new URI(confsubpath);
} else if (confsubpath.charAt(0) == '/' || confsubpath.indexOf(':') > 0) {
this.confPath = new File(confsubpath).getCanonicalFile().toURI();
} else {
this.confPath = new File(this.home, confsubpath).getCanonicalFile().toURI();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
String localaddr = config.getValue("address", "").trim();
this.localAddress = localaddr.isEmpty() ? Utility.localInetAddress() : new InetSocketAddress(localaddr, config.getIntValue("port")).getAddress();
this.resourceFactory.register(RESNAME_APP_ADDR, this.localAddress.getHostAddress());
this.resourceFactory.register(RESNAME_APP_ADDR, InetAddress.class, this.localAddress);
{
int nid = config.getIntValue("nodeid", 0);
this.nodeid = nid;
this.resourceFactory.register(RESNAME_APP_NODEID, nid);
System.setProperty(RESNAME_APP_NODEID, "" + nid);
}
//以下是初始化日志配置
final URI logConfURI = "file".equals(confPath.getScheme()) ? new File(new File(confPath), "logging.properties").toURI()
: URI.create(confPath.toString() + (confPath.toString().endsWith("/") ? "" : "/") + "logging.properties");
if (!"file".equals(confPath.getScheme()) || (new File(logConfURI).isFile() && new File(logConfURI).canRead())) {
try {
final String rootpath = root.getCanonicalPath().replace('\\', '/');
InputStream fin = logConfURI.toURL().openStream();
Properties properties = new Properties();
properties.load(fin);
fin.close();
properties.entrySet().stream().forEach(x -> {
x.setValue(x.getValue().toString().replace("${APP_HOME}", rootpath));
});
if (properties.getProperty("java.util.logging.FileHandler.formatter") == null) {
properties.setProperty("java.util.logging.FileHandler.formatter", LogFileHandler.LoggingFormater.class.getName());
}
if (properties.getProperty("java.util.logging.ConsoleHandler.formatter") == null) {
properties.setProperty("java.util.logging.ConsoleHandler.formatter", LogFileHandler.LoggingFormater.class.getName());
}
String fileHandlerPattern = properties.getProperty("java.util.logging.FileHandler.pattern");
if (fileHandlerPattern != null && fileHandlerPattern.contains("%d")) {
final String fileHandlerClass = LogFileHandler.class.getName();
Properties prop = new Properties();
final String handlers = properties.getProperty("handlers");
if (handlers != null && handlers.contains("java.util.logging.FileHandler")) {
//singletonrun模式下不输出文件日志
prop.setProperty("handlers", handlers.replace("java.util.logging.FileHandler", singletonrun ? "" : fileHandlerClass));
}
if (!prop.isEmpty()) {
String prefix = fileHandlerClass + ".";
properties.entrySet().stream().forEach(x -> {
if (x.getKey().toString().startsWith("java.util.logging.FileHandler.")) {
prop.put(x.getKey().toString().replace("java.util.logging.FileHandler.", prefix), x.getValue());
}
});
prop.entrySet().stream().forEach(x -> {
properties.put(x.getKey(), x.getValue());
});
}
properties.put(SncpClient.class.getSimpleName() + ".handlers", LogFileHandler.SncpLogFileHandler.class.getName());
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
final PrintStream ps = new PrintStream(out);
properties.forEach((x, y) -> ps.println(x + "=" + y));
LogManager.getLogManager().readConfiguration(new ByteArrayInputStream(out.toByteArray()));
} catch (Exception e) {
Logger.getLogger(this.getClass().getSimpleName()).log(Level.WARNING, "init logger configuration error", e);
}
}
this.logger = Logger.getLogger(this.getClass().getSimpleName());
this.serversLatch = new CountDownLatch(config.getAnyValues("server").length + 1);
this.classLoader = new RedkaleClassLoader(Thread.currentThread().getContextClassLoader());
logger.log(Level.INFO, "------------------------- Redkale " + Redkale.getDotedVersion() + " -------------------------");
//------------------配置 <transport> 节点 ------------------
ObjectPool<ByteBuffer> transportPool = null;
ExecutorService transportExec = null;
AsynchronousChannelGroup transportGroup = null;
final AnyValue resources = config.getAnyValue("resources");
TransportStrategy strategy = null;
List<ClusterAgent> clusters = new ArrayList<>();
int bufferCapacity = 32 * 1024;
int bufferPoolSize = Runtime.getRuntime().availableProcessors() * 8;
int readTimeoutSeconds = TransportFactory.DEFAULT_READTIMEOUTSECONDS;
int writeTimeoutSeconds = TransportFactory.DEFAULT_WRITETIMEOUTSECONDS;
AtomicLong createBufferCounter = new AtomicLong();
AtomicLong cycleBufferCounter = new AtomicLong();
if (resources != null) {
AnyValue transportConf = resources.getAnyValue("transport");
int groupsize = resources.getAnyValues("group").length;
if (groupsize > 0 && transportConf == null) transportConf = new DefaultAnyValue();
if (transportConf != null) {
//--------------transportBufferPool-----------
bufferCapacity = Math.max(parseLenth(transportConf.getValue("bufferCapacity"), bufferCapacity), 8 * 1024);
readTimeoutSeconds = transportConf.getIntValue("readTimeoutSeconds", readTimeoutSeconds);
writeTimeoutSeconds = transportConf.getIntValue("writeTimeoutSeconds", writeTimeoutSeconds);
final int threads = parseLenth(transportConf.getValue("threads"), groupsize * Runtime.getRuntime().availableProcessors() * 2);
bufferPoolSize = parseLenth(transportConf.getValue("bufferPoolSize"), threads * 4);
final int capacity = bufferCapacity;
transportPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, bufferPoolSize,
(Object... params) -> ByteBuffer.allocateDirect(capacity), null, (e) -> {
if (e == null || e.isReadOnly() || e.capacity() != capacity) return false;
e.clear();
return true;
});
//-----------transportChannelGroup--------------
try {
final String strategyClass = transportConf.getValue("strategy");
if (strategyClass != null && !strategyClass.isEmpty()) {
strategy = (TransportStrategy) classLoader.loadClass(strategyClass).getDeclaredConstructor().newInstance();
}
final AtomicInteger counter = new AtomicInteger();
transportExec = Executors.newFixedThreadPool(threads, (Runnable r) -> {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("Redkale-Transport-Thread-" + counter.incrementAndGet());
return t;
});
transportGroup = AsynchronousChannelGroup.withCachedThreadPool(transportExec, 1);
} catch (Exception e) {
throw new RuntimeException(e);
}
logger.log(Level.INFO, Transport.class.getSimpleName() + " configure bufferCapacity = " + bufferCapacity / 1024 + "K; bufferPoolSize = " + bufferPoolSize + "; threads = " + threads + ";");
}
AnyValue[] clusterConfs = resources.getAnyValues("cluster");
if (clusterConfs != null && clusterConfs.length > 0) {
for (AnyValue clusterConf : clusterConfs) {
try {
Class type = classLoader.loadClass(clusterConf.getValue("value"));
if (!ClusterAgent.class.isAssignableFrom(type)) {
logger.log(Level.SEVERE, "load application cluster resource, but not " + ClusterAgent.class.getSimpleName() + " error: " + clusterConf);
} else {
ClusterAgent cluster = (ClusterAgent) type.getDeclaredConstructor().newInstance();
cluster.setNodeid(this.nodeid);
cluster.init(clusterConf);
clusters.add(cluster);
}
} catch (Exception e) {
logger.log(Level.SEVERE, "load application cluster resource error: " + clusterConf, e);
}
}
}
}
if (transportGroup == null) {
final AtomicInteger counter = new AtomicInteger();
transportExec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 8, (Runnable r) -> {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("Redkale-Transport-Thread-" + counter.incrementAndGet());
return t;
});
try {
transportGroup = AsynchronousChannelGroup.withCachedThreadPool(transportExec, 1);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (transportPool == null) {
final int capacity = bufferCapacity;
transportPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, bufferPoolSize,
(Object... params) -> ByteBuffer.allocateDirect(capacity), null, (e) -> {
if (e == null || e.isReadOnly() || e.capacity() != capacity) return false;
e.clear();
return true;
});
}
this.clusterAgents = clusters == null ? null : clusters.toArray(new ClusterAgent[clusters.size()]);
this.sncpTransportFactory = TransportFactory.create(transportExec, transportPool, transportGroup, (SSLContext) null, readTimeoutSeconds, writeTimeoutSeconds, strategy);
DefaultAnyValue tarnsportConf = DefaultAnyValue.create(TransportFactory.NAME_POOLMAXCONNS, System.getProperty("net.transport.poolmaxconns", "100"))
.addValue(TransportFactory.NAME_PINGINTERVAL, System.getProperty("net.transport.pinginterval", "30"))
.addValue(TransportFactory.NAME_CHECKINTERVAL, System.getProperty("net.transport.checkinterval", "30"));
this.sncpTransportFactory.init(tarnsportConf, Sncp.PING_BUFFER, Sncp.PONG_BUFFER.remaining());
Thread.currentThread().setContextClassLoader(this.classLoader);
this.serverClassLoader = new RedkaleClassLoader(this.classLoader);
}
public ResourceFactory getResourceFactory() {
return resourceFactory;
}
public TransportFactory getSncpTransportFactory() {
return sncpTransportFactory;
}
public ClusterAgent[] getClusterAgents() {
return clusterAgents;
}
public RedkaleClassLoader getClassLoader() {
return classLoader;
}
public RedkaleClassLoader getServerClassLoader() {
return serverClassLoader;
}
public List<NodeServer> getNodeServers() {
return new ArrayList<>(servers);
}
public List<DataSource> getDataSources() {
return new ArrayList<>(dataSources);
}
public List<CacheSource> getCacheSources() {
return new ArrayList<>(cacheSources);
}
public int getNodeid() {
return nodeid;
}
public File getHome() {
return home;
}
public URI getConfPath() {
return confPath;
}
public long getStartTime() {
return startTime;
}
public AnyValue getAppConfig() {
return config;
}
public void init() throws Exception {
System.setProperty("net.transport.poolmaxconns", "100");
System.setProperty("net.transport.pinginterval", "30");
System.setProperty("net.transport.checkinterval", "30");
System.setProperty("convert.bson.tiny", "true");
System.setProperty("convert.json.tiny", "true");
System.setProperty("convert.bson.pool.size", "128");
System.setProperty("convert.json.pool.size", "128");
System.setProperty("convert.bson.writer.buffer.defsize", "4096");
System.setProperty("convert.json.writer.buffer.defsize", "4096");
final String confpath = this.confPath.toString();
final String homepath = this.home.getCanonicalPath();
if ("file".equals(this.confPath.getScheme())) {
File persist = new File(new File(confPath), "persistence.xml");
if (persist.isFile()) System.setProperty(DataSources.DATASOURCE_CONFPATH, persist.getCanonicalPath());
} else {
System.setProperty(DataSources.DATASOURCE_CONFPATH, confpath + (confpath.endsWith("/") ? "" : "/") + "persistence.xml");
}
String pidstr = "";
try { //JDK 9+
Class phclass = Class.forName("java.lang.ProcessHandle");
Object phobj = phclass.getMethod("current").invoke(null);
Object pid = phclass.getMethod("pid").invoke(phobj);
pidstr = "APP_PID = " + pid + "\r\n";
} catch (Throwable t) {
}
logger.log(Level.INFO, pidstr + "APP_JAVA = " + System.getProperty("java.version") + "\r\n" + RESNAME_APP_ADDR + " = " + this.localAddress.getHostAddress() + "\r\n" + RESNAME_APP_HOME + " = " + homepath + "\r\n" + RESNAME_APP_CONF + " = " + confpath);
String lib = config.getValue("lib", "${APP_HOME}/libs/*").trim().replace("${APP_HOME}", homepath);
lib = lib.isEmpty() ? confpath : (lib + ";" + confpath);
Server.loadLib(classLoader, logger, lib);
//------------------------------------------------------------------------
final AnyValue resources = config.getAnyValue("resources");
if (resources != null) {
resourceFactory.register(RESNAME_APP_GRES, AnyValue.class, resources);
final AnyValue properties = resources.getAnyValue("properties");
if (properties != null) {
String dfloads = properties.getValue("load");
if (dfloads != null) {
for (String dfload : dfloads.split(";")) {
if (dfload.trim().isEmpty()) continue;
final URI df = (dfload.indexOf('/') < 0) ? URI.create(confpath + (confpath.endsWith("/") ? "" : "/") + dfload) : new File(dfload).toURI();
if (!"file".equals(df.getScheme()) || new File(df).isFile()) {
Properties ps = new Properties();
try {
InputStream in = df.toURL().openStream();
ps.load(in);
in.close();
ps.forEach((x, y) -> resourceFactory.register("property." + x, y.toString().replace("${APP_HOME}", homepath)));
} catch (Exception e) {
logger.log(Level.WARNING, "load properties(" + dfload + ") error", e);
}
}
}
}
for (AnyValue prop : properties.getAnyValues("property")) {
String name = prop.getValue("name");
String value = prop.getValue("value");
if (name == null || value == null) continue;
value = value.replace("${APP_HOME}", homepath);
if (name.startsWith("system.property.")) {
System.setProperty(name.substring("system.property.".length()), value);
} else if (name.startsWith("mimetype.property.")) {
MimeType.add(name.substring("mimetype.property.".length()), value);
} else if (name.startsWith("property.")) {
resourceFactory.register(name, value);
} else {
resourceFactory.register("property." + name, value);
}
}
}
}
this.resourceFactory.register(BsonFactory.root());
this.resourceFactory.register(JsonFactory.root());
this.resourceFactory.register(BsonFactory.root().getConvert());
this.resourceFactory.register(JsonFactory.root().getConvert());
this.resourceFactory.register("bsonconvert", Convert.class, BsonFactory.root().getConvert());
this.resourceFactory.register("jsonconvert", Convert.class, JsonFactory.root().getConvert());
//只有WatchService才能加载Application、WatchFactory
final Application application = this;
this.resourceFactory.register(new ResourceFactory.ResourceLoader() {
@Override
public void load(ResourceFactory rf, final Object src, String resourceName, Field field, final Object attachment) {
try {
Resource res = field.getAnnotation(Resource.class);
if (res == null) return;
if (src instanceof Service && Sncp.isRemote((Service) src)) return; //远程模式不得注入
Class type = field.getType();
if (type == Application.class) {
field.set(src, application);
} else if (type == ResourceFactory.class) {
boolean serv = RESNAME_SERVER_RESFACTORY.equals(res.name()) || res.name().equalsIgnoreCase("server");
field.set(src, serv ? rf : (res.name().isEmpty() ? application.resourceFactory : null));
} else if (type == TransportFactory.class) {
field.set(src, application.sncpTransportFactory);
} else if (type == NodeSncpServer.class) {
NodeServer server = null;
for (NodeServer ns : application.getNodeServers()) {
if (ns.getClass() == NodeSncpServer.class) continue;
if (res.name().equals(ns.server.getName())) {
server = ns;
break;
}
}
field.set(src, server);
} else if (type == NodeHttpServer.class) {
NodeServer server = null;
for (NodeServer ns : application.getNodeServers()) {
if (ns.getClass() == NodeHttpServer.class) continue;
if (res.name().equals(ns.server.getName())) {
server = ns;
break;
}
}
field.set(src, server);
} else if (type == NodeWatchServer.class) {
NodeServer server = null;
for (NodeServer ns : application.getNodeServers()) {
if (ns.getClass() == NodeWatchServer.class) continue;
if (res.name().equals(ns.server.getName())) {
server = ns;
break;
}
}
field.set(src, server);
}
// if (type == WatchFactory.class) {
// field.set(src, application.watchFactory);
// }
} catch (Exception e) {
logger.log(Level.SEVERE, "Resource inject error", e);
}
}
@Override
public boolean autoNone() {
return false;
}
}, Application.class, ResourceFactory.class, TransportFactory.class, NodeSncpServer.class, NodeHttpServer.class, NodeWatchServer.class);
//--------------------------------------------------------------------------
initResources();
}
private void initResources() throws Exception {
//-------------------------------------------------------------------------
final AnyValue resources = config.getAnyValue("resources");
if (resources != null) {
//------------------------------------------------------------------------
for (AnyValue conf : resources.getAnyValues("group")) {
final String group = conf.getValue("name", "");
final String protocol = conf.getValue("protocol", Transport.DEFAULT_PROTOCOL).toUpperCase();
if (!"TCP".equalsIgnoreCase(protocol) && !"UDP".equalsIgnoreCase(protocol)) {
throw new RuntimeException("Not supported Transport Protocol " + conf.getValue("protocol"));
}
TransportGroupInfo ginfo = new TransportGroupInfo(group, protocol, new LinkedHashSet<>());
for (AnyValue node : conf.getAnyValues("node")) {
final InetSocketAddress addr = new InetSocketAddress(node.getValue("addr"), node.getIntValue("port"));
ginfo.putAddress(addr);
}
sncpTransportFactory.addGroupInfo(ginfo);
}
for (AnyValue conf : resources.getAnyValues("listener")) {
final String listenClass = conf.getValue("value", "");
if (listenClass.isEmpty()) continue;
Class clazz = classLoader.loadClass(listenClass);
if (!ApplicationListener.class.isAssignableFrom(clazz)) continue;
@SuppressWarnings("unchecked")
ApplicationListener listener = (ApplicationListener) clazz.getDeclaredConstructor().newInstance();
listener.init(config);
this.listeners.add(listener);
}
}
//------------------------------------------------------------------------
}
public void restoreConfig() throws IOException {
if (!"file".equals(this.confPath.getScheme())) return;
synchronized (this) {
File confFile = new File(this.confPath.toString(), "application.xml");
confFile.renameTo(new File(this.confPath.toString(), "application_" + String.format("%1$tY%1$tm%1$td%1$tH%1$tM%1$tS", System.currentTimeMillis()) + ".xml"));
final PrintStream ps = new PrintStream(new FileOutputStream(confFile));
ps.append(config.toXML("application"));
ps.close();
}
}
private void startSelfServer() throws Exception {
final Application application = this;
new Thread() {
{
setName("Redkale-Application-SelfServer-Thread");
}
@Override
public void run() {
try {
final DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(true);
channel.socket().setSoTimeout(3000);
channel.bind(new InetSocketAddress("127.0.0.1", config.getIntValue("port")));
boolean loop = true;
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (loop) {
buffer.clear();
SocketAddress address = channel.receive(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
if ("SHUTDOWN".equalsIgnoreCase(new String(bytes))) {
try {
long s = System.currentTimeMillis();
logger.info(application.getClass().getSimpleName() + " shutdowning");
application.shutdown();
buffer.clear();
buffer.put("SHUTDOWN OK".getBytes());
buffer.flip();
channel.send(buffer, address);
long e = System.currentTimeMillis() - s;
logger.info(application.getClass().getSimpleName() + " shutdown in " + e + " ms");
application.serversLatch.countDown();
System.exit(0);
} catch (Exception ex) {
logger.log(Level.INFO, "SHUTDOWN FAIL", ex);
buffer.clear();
buffer.put("SHUTDOWN FAIL".getBytes());
buffer.flip();
channel.send(buffer, address);
}
} else if ("APIDOC".equalsIgnoreCase(new String(bytes))) {
try {
new ApiDocsService(application).run();
buffer.clear();
buffer.put("APIDOC OK".getBytes());
buffer.flip();
channel.send(buffer, address);
} catch (Exception ex) {
buffer.clear();
buffer.put("APIDOC FAIL".getBytes());
buffer.flip();
channel.send(buffer, address);
}
}
}
} catch (Exception e) {
logger.log(Level.INFO, "Control fail", e);
System.exit(1);
}
}
}.start();
}
private void sendCommand(String command) throws Exception {
final DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(true);
channel.connect(new InetSocketAddress("127.0.0.1", config.getIntValue("port")));
ByteBuffer buffer = ByteBuffer.allocate(128);
buffer.put(command.getBytes());
buffer.flip();
channel.write(buffer);
buffer.clear();
channel.configureBlocking(true);
try {
channel.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
channel.close();
logger.info("Send: " + command + ", Reply: " + new String(bytes));
Thread.sleep(1000);
} catch (Exception e) {
if (e instanceof PortUnreachableException) {
if ("APIDOC".equalsIgnoreCase(command)) {
final Application application = Application.create(true);
application.init();
application.start();
new ApiDocsService(application).run();
logger.info("APIDOC OK");
return;
}
}
throw e;
}
}
public void start() throws Exception {
final AnyValue[] entrys = config.getAnyValues("server");
CountDownLatch timecd = new CountDownLatch(entrys.length);
final List<AnyValue> sncps = new ArrayList<>();
final List<AnyValue> others = new ArrayList<>();
final List<AnyValue> watchs = new ArrayList<>();
for (final AnyValue entry : entrys) {
if (entry.getValue("protocol", "").toUpperCase().startsWith("SNCP")) {
sncps.add(entry);
} else if (entry.getValue("protocol", "").toUpperCase().startsWith("WATCH")) {
watchs.add(entry);
} else {
others.add(entry);
}
}
if (watchs.size() > 1) throw new RuntimeException("Found one more WATCH Server");
this.watching = !watchs.isEmpty();
runServers(timecd, sncps); //必须确保SNCP服务都启动后再启动其他服务
runServers(timecd, others);
runServers(timecd, watchs); //必须在所有服务都启动后再启动WATCH服务
timecd.await();
//if (!singletonrun) signalHandle();
//if (!singletonrun) clearPersistData();
logger.info(this.getClass().getSimpleName() + " started in " + (System.currentTimeMillis() - startTime) + " ms\r\n");
if (!singletonrun) this.serversLatch.await();
}
// private void clearPersistData() {
// File cachedir = new File(home, "cache");
// if (!cachedir.isDirectory()) return;
// File[] lfs = cachedir.listFiles();
// if (lfs != null) {
// for (File file : lfs) {
// if (file.getName().startsWith("persist-")) file.delete();
// }
// }
// }
// private void signalHandle() {
// //http://www.comptechdoc.org/os/linux/programming/linux_pgsignals.html
// String[] sigs = new String[]{"HUP", "TERM", "INT", "QUIT", "KILL", "TSTP", "USR1", "USR2", "STOP"};
// List<sun.misc.Signal> list = new ArrayList<>();
// for (String sig : sigs) {
// try {
// list.add(new sun.misc.Signal(sig));
// } catch (Exception e) {
// }
// }
// sun.misc.SignalHandler handler = new sun.misc.SignalHandler() {
//
// private volatile boolean runed;
//
// @Override
// public void handle(Signal sig) {
// if (runed) return;
// runed = true;
// logger.info(Application.this.getClass().getSimpleName() + " stoped\r\n");
// System.exit(0);
// }
// };
// for (Signal sig : list) {
// try {
// Signal.handle(sig, handler);
// } catch (Exception e) {
// }
// }
// }
@SuppressWarnings("unchecked")
private void runServers(CountDownLatch timecd, final List<AnyValue> serconfs) throws Exception {
this.servicecdl = new CountDownLatch(serconfs.size());
CountDownLatch sercdl = new CountDownLatch(serconfs.size());
final AtomicBoolean inited = new AtomicBoolean(false);
final Map<String, Class<? extends NodeServer>> nodeClasses = new HashMap<>();
for (final AnyValue serconf : serconfs) {
Thread thread = new Thread() {
{
String host = serconf.getValue("host", "0.0.0.0").replace("0.0.0.0", "*");
setName("Redkale-" + serconf.getValue("protocol", "Server").toUpperCase() + "-" + host + ":" + serconf.getIntValue("port") + "-Thread");
this.setDaemon(true);
}
@Override
public void run() {
try {
//Thread ctd = Thread.currentThread();
//ctd.setContextClassLoader(new URLClassLoader(new URL[0], ctd.getContextClassLoader()));
final String protocol = serconf.getValue("protocol", "").replaceFirst("\\..+", "").toUpperCase();
NodeServer server = null;
if ("SNCP".equals(protocol)) {
server = NodeSncpServer.createNodeServer(Application.this, serconf);
} else if ("WATCH".equalsIgnoreCase(protocol)) {
DefaultAnyValue serconf2 = (DefaultAnyValue) serconf;
DefaultAnyValue rest = (DefaultAnyValue) serconf2.getAnyValue("rest");
if (rest == null) {
rest = new DefaultAnyValue();
serconf2.addValue("rest", rest);
}
rest.setValue("base", WatchServlet.class.getName());
server = new NodeWatchServer(Application.this, serconf);
} else if ("HTTP".equalsIgnoreCase(protocol)) {
server = new NodeHttpServer(Application.this, serconf);
} else {
if (!inited.get()) {
synchronized (nodeClasses) {
if (!inited.getAndSet(true)) { //加载自定义的协议SOCKS
ClassFilter profilter = new ClassFilter(classLoader, NodeProtocol.class, NodeServer.class, (Class[]) null);
ClassFilter.Loader.load(home, serconf.getValue("excludelibs", "").split(";"), profilter);
final Set<FilterEntry<NodeServer>> entrys = profilter.getFilterEntrys();
for (FilterEntry<NodeServer> entry : entrys) {
final Class<? extends NodeServer> type = entry.getType();
NodeProtocol pros = type.getAnnotation(NodeProtocol.class);
for (String p : pros.value()) {
p = p.toUpperCase();
if ("SNCP".equals(p) || "HTTP".equals(p)) continue;
final Class<? extends NodeServer> old = nodeClasses.get(p);
if (old != null && old != type) {
throw new RuntimeException("Protocol(" + p + ") had NodeServer-Class(" + old.getName() + ") but repeat NodeServer-Class(" + type.getName() + ")");
}
nodeClasses.put(p, type);
}
}
}
}
}
Class<? extends NodeServer> nodeClass = nodeClasses.get(protocol);
if (nodeClass != null) server = NodeServer.create(nodeClass, Application.this, serconf);
}
if (server == null) {
logger.log(Level.SEVERE, "Not found Server Class for protocol({0})", serconf.getValue("protocol"));
System.exit(0);
}
servers.add(server);
server.init(serconf);
if (!singletonrun) server.start();
timecd.countDown();
sercdl.countDown();
} catch (Exception ex) {
logger.log(Level.WARNING, serconf + " runServers error", ex);
Application.this.serversLatch.countDown();
}
}
};
thread.start();
}
sercdl.await();
}
public static <T extends Service> T singleton(Class<T> serviceClass, Class<? extends Service>... extServiceClasses) throws Exception {
return singleton("", serviceClass, extServiceClasses);
}
public static <T extends Service> T singleton(String name, Class<T> serviceClass, Class<? extends Service>... extServiceClasses) throws Exception {
if (serviceClass == null) throw new IllegalArgumentException("serviceClass is null");
final Application application = Application.create(true);
System.setProperty("red" + "kale-singleton-serviceclass", serviceClass.getName());
if (extServiceClasses != null && extServiceClasses.length > 0) {
StringBuilder sb = new StringBuilder();
for (Class clazz : extServiceClasses) {
if (sb.length() > 0) sb.append(',');
sb.append(clazz.getName());
}
System.setProperty("red" + "kale-singleton-extserviceclasses", sb.toString());
}
application.init();
application.start();
for (NodeServer server : application.servers) {
T service = server.resourceFactory.find(name, serviceClass);
if (service != null) return service;
}
if (Modifier.isAbstract(serviceClass.getModifiers())) throw new IllegalArgumentException("abstract class not allowed");
if (serviceClass.isInterface()) throw new IllegalArgumentException("interface class not allowed");
throw new IllegalArgumentException(serviceClass.getName() + " maybe have zero not-final public method");
}
public static Application create(final boolean singleton) throws IOException {
final String home = new File(System.getProperty(RESNAME_APP_HOME, "")).getCanonicalPath().replace('\\', '/');
System.setProperty(RESNAME_APP_HOME, home);
String confsubpath = System.getProperty(RESNAME_APP_CONF, "conf");
URI appconf;
if (confsubpath.contains("://")) {
appconf = URI.create(confsubpath + (confsubpath.endsWith("/") ? "" : "/") + "application.xml");
} else if (confsubpath.charAt(0) == '/' || confsubpath.indexOf(':') > 0) {
appconf = new File(confsubpath, "application.xml").toURI();
} else {
appconf = new File(new File(home, confsubpath), "application.xml").toURI();
}
return new Application(singleton, load(appconf.toURL().openStream()));
}
public static void main(String[] args) throws Exception {
Utility.midnight(); //先初始化一下Utility
Thread.currentThread().setName("Redkale-Application-Main-Thread");
//运行主程序
final Application application = Application.create(false);
if (System.getProperty("CMD") != null) {
application.sendCommand(System.getProperty("CMD"));
return;
} else if (System.getProperty("SHUTDOWN") != null) { //兼容旧接口
application.sendCommand("SHUTDOWN");
return;
}
application.init();
application.startSelfServer();
try {
for (ApplicationListener listener : application.listeners) {
listener.preStart(application);
}
application.start();
} catch (Exception e) {
application.logger.log(Level.SEVERE, "Application start error", e);
System.exit(0);
}
System.exit(0);
}
NodeSncpServer findNodeSncpServer(final InetSocketAddress sncpAddr) {
for (NodeServer node : servers) {
if (node.isSNCP() && sncpAddr.equals(node.getSncpAddress())) {
return (NodeSncpServer) node;
}
}
return null;
}
public void shutdown() throws Exception {
for (ApplicationListener listener : this.listeners) {
try {
listener.preShutdown(this);
} catch (Exception e) {
logger.log(Level.WARNING, listener.getClass() + " preShutdown erroneous", e);
}
}
List<NodeServer> localServers = new ArrayList<>(servers); //顺序sncps, others, watchs
Collections.reverse(localServers); //倒序, 必须让watchs先关闭watch包含服务发现和注销逻辑
localServers.stream().forEach((server) -> {
try {
server.shutdown();
} catch (Exception t) {
logger.log(Level.WARNING, " shutdown server(" + server.getSocketAddress() + ") error", t);
} finally {
serversLatch.countDown();
}
});
if (clusterAgents != null) {
for (ClusterAgent cluster : clusterAgents) {
if (cluster != null) cluster.destroy(cluster.getConfig());
}
}
for (DataSource source : dataSources) {
if (source == null) continue;
try {
source.getClass().getMethod("close").invoke(source);
} catch (Exception e) {
logger.log(Level.FINER, source.getClass() + " close DataSource erroneous", e);
}
}
for (CacheSource source : cacheSources) {
if (source == null) continue;
try {
source.getClass().getMethod("close").invoke(source);
} catch (Exception e) {
logger.log(Level.FINER, source.getClass() + " close CacheSource erroneous", e);
}
}
this.sncpTransportFactory.shutdownNow();
}
private static int parseLenth(String value, int defValue) {
if (value == null) return defValue;
value = value.toUpperCase().replace("B", "");
if (value.endsWith("G")) return Integer.decode(value.replace("G", "")) * 1024 * 1024 * 1024;
if (value.endsWith("M")) return Integer.decode(value.replace("M", "")) * 1024 * 1024;
if (value.endsWith("K")) return Integer.decode(value.replace("K", "")) * 1024;
return Integer.decode(value);
}
private static AnyValue load(final InputStream in0) {
final DefaultAnyValue any = new DefaultAnyValue();
try (final InputStream in = in0) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(in);
Element root = doc.getDocumentElement();
load(any, root);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
return any;
}
private static void load(final DefaultAnyValue any, final Node root) {
final String home = System.getProperty(RESNAME_APP_HOME);
NamedNodeMap nodes = root.getAttributes();
if (nodes == null) return;
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
any.addValue(node.getNodeName(), node.getNodeValue().replace("${APP_HOME}", home));
}
NodeList children = root.getChildNodes();
if (children == null) return;
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE) continue;
DefaultAnyValue sub = new DefaultAnyValue();
load(sub, node);
any.addValue(node.getNodeName(), sub);
}
}
}