From 129169cb8755b73bbe86d26a9a4721e54a1be63d Mon Sep 17 00:00:00 2001 From: kamhung <22250530@qq.com> Date: Wed, 9 Dec 2015 10:29:45 +0800 Subject: [PATCH] --- src/org/redkale/boot/Application.java | 566 ++++++ src/org/redkale/boot/ClassFilter.java | 436 +++++ src/org/redkale/boot/LogFileHandler.java | 267 +++ src/org/redkale/boot/NodeHttpServer.java | 133 ++ src/org/redkale/boot/NodeProtocol.java | 19 + src/org/redkale/boot/NodeServer.java | 442 +++++ src/org/redkale/boot/NodeSncpServer.java | 69 + src/org/redkale/convert/AnyEncoder.java | 40 + src/org/redkale/convert/ArrayDecoder.java | 79 + src/org/redkale/convert/ArrayEncoder.java | 75 + .../redkale/convert/CollectionDecoder.java | 69 + .../redkale/convert/CollectionEncoder.java | 65 + src/org/redkale/convert/Convert.java | 26 + src/org/redkale/convert/ConvertColumn.java | 44 + .../redkale/convert/ConvertColumnEntry.java | 67 + src/org/redkale/convert/ConvertColumns.java | 24 + src/org/redkale/convert/ConvertEntity.java | 25 + src/org/redkale/convert/ConvertException.java | 28 + src/org/redkale/convert/ConvertType.java | 28 + src/org/redkale/convert/DeMember.java | 64 + src/org/redkale/convert/Decodeable.java | 27 + src/org/redkale/convert/EnMember.java | 76 + src/org/redkale/convert/Encodeable.java | 27 + src/org/redkale/convert/Factory.java | 385 ++++ src/org/redkale/convert/HashedMap.java | 70 + src/org/redkale/convert/MapDecoder.java | 78 + src/org/redkale/convert/MapEncoder.java | 62 + src/org/redkale/convert/ObjectDecoder.java | 157 ++ src/org/redkale/convert/ObjectEncoder.java | 233 +++ src/org/redkale/convert/Reader.java | 112 ++ src/org/redkale/convert/SimpledCoder.java | 42 + src/org/redkale/convert/Writer.java | 120 ++ .../convert/bson/BsonByteBufferWriter.java | 134 ++ src/org/redkale/convert/bson/BsonConvert.java | 155 ++ src/org/redkale/convert/bson/BsonFactory.java | 62 + src/org/redkale/convert/bson/BsonReader.java | 323 ++++ .../convert/bson/BsonSimpledCoder.java | 17 + src/org/redkale/convert/bson/BsonWriter.java | 297 ++++ .../convert/ext/BigIntegerSimpledCoder.java | 38 + .../convert/ext/BoolArraySimpledCoder.java | 68 + .../redkale/convert/ext/BoolSimpledCoder.java | 32 + .../convert/ext/ByteArraySimpledCoder.java | 68 + .../redkale/convert/ext/ByteSimpledCoder.java | 32 + .../convert/ext/CharArraySimpledCoder.java | 68 + .../redkale/convert/ext/CharSimpledCoder.java | 33 + .../convert/ext/DLongSimpledCoder.java | 40 + .../redkale/convert/ext/DateSimpledCoder.java | 33 + .../convert/ext/DoubleArraySimpledCoder.java | 68 + .../convert/ext/DoubleSimpledCoder.java | 32 + .../redkale/convert/ext/EnumSimpledCoder.java | 44 + .../convert/ext/FloatArraySimpledCoder.java | 68 + .../convert/ext/FloatSimpledCoder.java | 32 + .../convert/ext/InetAddressSimpledCoder.java | 70 + .../convert/ext/IntArraySimpledCoder.java | 68 + .../redkale/convert/ext/IntSimpledCoder.java | 32 + .../convert/ext/LongArraySimpledCoder.java | 68 + .../redkale/convert/ext/LongSimpledCoder.java | 33 + .../convert/ext/NumberSimpledCoder.java | 32 + .../convert/ext/PatternSimpledCoder.java | 38 + .../convert/ext/ShortArraySimpledCoder.java | 68 + .../convert/ext/ShortSimpledCoder.java | 32 + .../convert/ext/StringArraySimpledCoder.java | 68 + .../convert/ext/StringSimpledCoder.java | 32 + .../redkale/convert/ext/TypeSimpledCoder.java | 42 + .../json/InetAddressJsonSimpledCoder.java | 68 + .../convert/json/JsonByteBufferWriter.java | 348 ++++ src/org/redkale/convert/json/JsonConvert.java | 137 ++ src/org/redkale/convert/json/JsonFactory.java | 60 + src/org/redkale/convert/json/JsonReader.java | 577 ++++++ .../convert/json/JsonSimpledCoder.java | 17 + src/org/redkale/convert/json/JsonWriter.java | 268 +++ src/org/redkale/net/Async.java | 25 + src/org/redkale/net/AsyncConnection.java | 570 ++++++ src/org/redkale/net/AsyncDatagramChannel.java | 1327 ++++++++++++++ src/org/redkale/net/Context.java | 132 ++ src/org/redkale/net/PrepareRunner.java | 98 ++ src/org/redkale/net/PrepareServlet.java | 73 + src/org/redkale/net/ProtocolServer.java | 185 ++ src/org/redkale/net/Request.java | 112 ++ src/org/redkale/net/Response.java | 228 +++ src/org/redkale/net/SSLBuilder.java | 160 ++ src/org/redkale/net/Server.java | 188 ++ src/org/redkale/net/Servlet.java | 27 + src/org/redkale/net/Transport.java | 254 +++ src/org/redkale/net/WorkThread.java | 31 + src/org/redkale/net/http/AuthIgnore.java | 22 + .../redkale/net/http/BasedHttpServlet.java | 260 +++ src/org/redkale/net/http/HttpCacheable.java | 25 + src/org/redkale/net/http/HttpContext.java | 59 + .../redkale/net/http/HttpPrepareServlet.java | 152 ++ src/org/redkale/net/http/HttpRequest.java | 531 ++++++ .../redkale/net/http/HttpResourceServlet.java | 260 +++ src/org/redkale/net/http/HttpResponse.java | 561 ++++++ src/org/redkale/net/http/HttpServer.java | 127 ++ src/org/redkale/net/http/HttpServlet.java | 29 + src/org/redkale/net/http/MimeType.java | 240 +++ src/org/redkale/net/http/MultiContext.java | 284 +++ src/org/redkale/net/http/MultiPart.java | 115 ++ src/org/redkale/net/http/WebAction.java | 22 + src/org/redkale/net/http/WebInitParam.java | 24 + src/org/redkale/net/http/WebServlet.java | 28 + src/org/redkale/net/http/WebSocket.java | 387 ++++ src/org/redkale/net/http/WebSocketBinary.java | 23 + src/org/redkale/net/http/WebSocketEngine.java | 92 + src/org/redkale/net/http/WebSocketGroup.java | 128 ++ src/org/redkale/net/http/WebSocketNode.java | 196 +++ src/org/redkale/net/http/WebSocketPacket.java | 123 ++ src/org/redkale/net/http/WebSocketRunner.java | 529 ++++++ .../redkale/net/http/WebSocketServlet.java | 145 ++ src/org/redkale/net/sncp/ServiceWrapper.java | 92 + src/org/redkale/net/sncp/Sncp.java | 1108 ++++++++++++ src/org/redkale/net/sncp/SncpCall.java | 24 + src/org/redkale/net/sncp/SncpClient.java | 406 +++++ src/org/redkale/net/sncp/SncpDyn.java | 24 + src/org/redkale/net/sncp/SncpDynServlet.java | 407 +++++ src/org/redkale/net/sncp/SncpFuture.java | 94 + .../redkale/net/sncp/SncpPrepareServlet.java | 94 + src/org/redkale/net/sncp/SncpRemote.java | 23 + src/org/redkale/net/sncp/SncpRequest.java | 148 ++ src/org/redkale/net/sncp/SncpResponse.java | 83 + src/org/redkale/net/sncp/SncpServer.java | 66 + src/org/redkale/net/sncp/SncpServlet.java | 32 + .../redkale/net/sncp/SncpTargetAddress.java | 24 + .../service/DataCacheListenerService.java | 41 + .../service/DataSQLListenerService.java | 118 ++ .../redkale/service/DataSourceService.java | 342 ++++ src/org/redkale/service/LocalService.java | 23 + src/org/redkale/service/MultiRun.java | 30 + src/org/redkale/service/RetResult.java | 113 ++ src/org/redkale/service/Service.java | 43 + .../redkale/service/WebSocketNodeService.java | 76 + src/org/redkale/source/DataCacheListener.java | 21 + .../source/DataCallArrayAttribute.java | 57 + src/org/redkale/source/DataCallAttribute.java | 72 + src/org/redkale/source/DataConnection.java | 29 + src/org/redkale/source/DataDefaultSource.java | 1554 +++++++++++++++++ src/org/redkale/source/DataSQLListener.java | 20 + src/org/redkale/source/DataSource.java | 244 +++ .../redkale/source/DistributeGenerator.java | 44 + src/org/redkale/source/EntityCache.java | 654 +++++++ src/org/redkale/source/EntityInfo.java | 371 ++++ src/org/redkale/source/FilterBean.java | 15 + src/org/redkale/source/FilterColumn.java | 46 + src/org/redkale/source/FilterExpress.java | 46 + src/org/redkale/source/FilterGroup.java | 62 + src/org/redkale/source/FilterGroups.java | 23 + src/org/redkale/source/FilterJoinColumn.java | 50 + src/org/redkale/source/FilterJoinNode.java | 315 ++++ src/org/redkale/source/FilterNode.java | 958 ++++++++++ src/org/redkale/source/FilterNodeBean.java | 353 ++++ src/org/redkale/source/Flipper.java | 111 ++ src/org/redkale/source/JDBCPoolSource.java | 238 +++ src/org/redkale/source/Range.java | 284 +++ src/org/redkale/source/ReckonType.java | 21 + src/org/redkale/source/VirtualEntity.java | 21 + src/org/redkale/util/AnyValue.java | 434 +++++ src/org/redkale/util/Attribute.java | 323 ++++ src/org/redkale/util/AutoLoad.java | 24 + src/org/redkale/util/ByteArray.java | 161 ++ src/org/redkale/util/Creator.java | 207 +++ src/org/redkale/util/DLong.java | 81 + src/org/redkale/util/DebugMethodVisitor.java | 150 ++ src/org/redkale/util/Ignore.java | 22 + src/org/redkale/util/LogLevel.java | 24 + src/org/redkale/util/Nameable.java | 15 + src/org/redkale/util/ObjectNode.java | 26 + src/org/redkale/util/ObjectPool.java | 99 ++ src/org/redkale/util/Reproduce.java | 132 ++ src/org/redkale/util/ResourceFactory.java | 240 +++ src/org/redkale/util/SelectColumn.java | 136 ++ src/org/redkale/util/Sheet.java | 88 + src/org/redkale/util/TypeToken.java | 26 + src/org/redkale/util/Utility.java | 517 ++++++ src/org/redkale/watch/WatchFactory.java | 99 ++ src/org/redkale/watch/WatchNode.java | 21 + src/org/redkale/watch/WatchNumber.java | 49 + src/org/redkale/watch/WatchSupplier.java | 47 + src/org/redkale/watch/Watchable.java | 34 + 178 files changed, 27404 insertions(+) create mode 100644 src/org/redkale/boot/Application.java create mode 100644 src/org/redkale/boot/ClassFilter.java create mode 100644 src/org/redkale/boot/LogFileHandler.java create mode 100644 src/org/redkale/boot/NodeHttpServer.java create mode 100644 src/org/redkale/boot/NodeProtocol.java create mode 100644 src/org/redkale/boot/NodeServer.java create mode 100644 src/org/redkale/boot/NodeSncpServer.java create mode 100644 src/org/redkale/convert/AnyEncoder.java create mode 100644 src/org/redkale/convert/ArrayDecoder.java create mode 100644 src/org/redkale/convert/ArrayEncoder.java create mode 100644 src/org/redkale/convert/CollectionDecoder.java create mode 100644 src/org/redkale/convert/CollectionEncoder.java create mode 100644 src/org/redkale/convert/Convert.java create mode 100644 src/org/redkale/convert/ConvertColumn.java create mode 100644 src/org/redkale/convert/ConvertColumnEntry.java create mode 100644 src/org/redkale/convert/ConvertColumns.java create mode 100644 src/org/redkale/convert/ConvertEntity.java create mode 100644 src/org/redkale/convert/ConvertException.java create mode 100644 src/org/redkale/convert/ConvertType.java create mode 100644 src/org/redkale/convert/DeMember.java create mode 100644 src/org/redkale/convert/Decodeable.java create mode 100644 src/org/redkale/convert/EnMember.java create mode 100644 src/org/redkale/convert/Encodeable.java create mode 100644 src/org/redkale/convert/Factory.java create mode 100644 src/org/redkale/convert/HashedMap.java create mode 100644 src/org/redkale/convert/MapDecoder.java create mode 100644 src/org/redkale/convert/MapEncoder.java create mode 100644 src/org/redkale/convert/ObjectDecoder.java create mode 100644 src/org/redkale/convert/ObjectEncoder.java create mode 100644 src/org/redkale/convert/Reader.java create mode 100644 src/org/redkale/convert/SimpledCoder.java create mode 100644 src/org/redkale/convert/Writer.java create mode 100644 src/org/redkale/convert/bson/BsonByteBufferWriter.java create mode 100644 src/org/redkale/convert/bson/BsonConvert.java create mode 100644 src/org/redkale/convert/bson/BsonFactory.java create mode 100644 src/org/redkale/convert/bson/BsonReader.java create mode 100644 src/org/redkale/convert/bson/BsonSimpledCoder.java create mode 100644 src/org/redkale/convert/bson/BsonWriter.java create mode 100644 src/org/redkale/convert/ext/BigIntegerSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/BoolArraySimpledCoder.java create mode 100644 src/org/redkale/convert/ext/BoolSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/ByteArraySimpledCoder.java create mode 100644 src/org/redkale/convert/ext/ByteSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/CharArraySimpledCoder.java create mode 100644 src/org/redkale/convert/ext/CharSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/DLongSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/DateSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/DoubleArraySimpledCoder.java create mode 100644 src/org/redkale/convert/ext/DoubleSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/EnumSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/FloatArraySimpledCoder.java create mode 100644 src/org/redkale/convert/ext/FloatSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/InetAddressSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/IntArraySimpledCoder.java create mode 100644 src/org/redkale/convert/ext/IntSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/LongArraySimpledCoder.java create mode 100644 src/org/redkale/convert/ext/LongSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/NumberSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/PatternSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/ShortArraySimpledCoder.java create mode 100644 src/org/redkale/convert/ext/ShortSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/StringArraySimpledCoder.java create mode 100644 src/org/redkale/convert/ext/StringSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/TypeSimpledCoder.java create mode 100644 src/org/redkale/convert/json/InetAddressJsonSimpledCoder.java create mode 100644 src/org/redkale/convert/json/JsonByteBufferWriter.java create mode 100644 src/org/redkale/convert/json/JsonConvert.java create mode 100644 src/org/redkale/convert/json/JsonFactory.java create mode 100644 src/org/redkale/convert/json/JsonReader.java create mode 100644 src/org/redkale/convert/json/JsonSimpledCoder.java create mode 100644 src/org/redkale/convert/json/JsonWriter.java create mode 100644 src/org/redkale/net/Async.java create mode 100644 src/org/redkale/net/AsyncConnection.java create mode 100644 src/org/redkale/net/AsyncDatagramChannel.java create mode 100644 src/org/redkale/net/Context.java create mode 100644 src/org/redkale/net/PrepareRunner.java create mode 100644 src/org/redkale/net/PrepareServlet.java create mode 100644 src/org/redkale/net/ProtocolServer.java create mode 100644 src/org/redkale/net/Request.java create mode 100644 src/org/redkale/net/Response.java create mode 100644 src/org/redkale/net/SSLBuilder.java create mode 100644 src/org/redkale/net/Server.java create mode 100644 src/org/redkale/net/Servlet.java create mode 100644 src/org/redkale/net/Transport.java create mode 100644 src/org/redkale/net/WorkThread.java create mode 100644 src/org/redkale/net/http/AuthIgnore.java create mode 100644 src/org/redkale/net/http/BasedHttpServlet.java create mode 100644 src/org/redkale/net/http/HttpCacheable.java create mode 100644 src/org/redkale/net/http/HttpContext.java create mode 100644 src/org/redkale/net/http/HttpPrepareServlet.java create mode 100644 src/org/redkale/net/http/HttpRequest.java create mode 100644 src/org/redkale/net/http/HttpResourceServlet.java create mode 100644 src/org/redkale/net/http/HttpResponse.java create mode 100644 src/org/redkale/net/http/HttpServer.java create mode 100644 src/org/redkale/net/http/HttpServlet.java create mode 100644 src/org/redkale/net/http/MimeType.java create mode 100644 src/org/redkale/net/http/MultiContext.java create mode 100644 src/org/redkale/net/http/MultiPart.java create mode 100644 src/org/redkale/net/http/WebAction.java create mode 100644 src/org/redkale/net/http/WebInitParam.java create mode 100644 src/org/redkale/net/http/WebServlet.java create mode 100644 src/org/redkale/net/http/WebSocket.java create mode 100644 src/org/redkale/net/http/WebSocketBinary.java create mode 100644 src/org/redkale/net/http/WebSocketEngine.java create mode 100644 src/org/redkale/net/http/WebSocketGroup.java create mode 100644 src/org/redkale/net/http/WebSocketNode.java create mode 100644 src/org/redkale/net/http/WebSocketPacket.java create mode 100644 src/org/redkale/net/http/WebSocketRunner.java create mode 100644 src/org/redkale/net/http/WebSocketServlet.java create mode 100644 src/org/redkale/net/sncp/ServiceWrapper.java create mode 100644 src/org/redkale/net/sncp/Sncp.java create mode 100644 src/org/redkale/net/sncp/SncpCall.java create mode 100644 src/org/redkale/net/sncp/SncpClient.java create mode 100644 src/org/redkale/net/sncp/SncpDyn.java create mode 100644 src/org/redkale/net/sncp/SncpDynServlet.java create mode 100644 src/org/redkale/net/sncp/SncpFuture.java create mode 100644 src/org/redkale/net/sncp/SncpPrepareServlet.java create mode 100644 src/org/redkale/net/sncp/SncpRemote.java create mode 100644 src/org/redkale/net/sncp/SncpRequest.java create mode 100644 src/org/redkale/net/sncp/SncpResponse.java create mode 100644 src/org/redkale/net/sncp/SncpServer.java create mode 100644 src/org/redkale/net/sncp/SncpServlet.java create mode 100644 src/org/redkale/net/sncp/SncpTargetAddress.java create mode 100644 src/org/redkale/service/DataCacheListenerService.java create mode 100644 src/org/redkale/service/DataSQLListenerService.java create mode 100644 src/org/redkale/service/DataSourceService.java create mode 100644 src/org/redkale/service/LocalService.java create mode 100644 src/org/redkale/service/MultiRun.java create mode 100644 src/org/redkale/service/RetResult.java create mode 100644 src/org/redkale/service/Service.java create mode 100644 src/org/redkale/service/WebSocketNodeService.java create mode 100644 src/org/redkale/source/DataCacheListener.java create mode 100644 src/org/redkale/source/DataCallArrayAttribute.java create mode 100644 src/org/redkale/source/DataCallAttribute.java create mode 100644 src/org/redkale/source/DataConnection.java create mode 100644 src/org/redkale/source/DataDefaultSource.java create mode 100644 src/org/redkale/source/DataSQLListener.java create mode 100644 src/org/redkale/source/DataSource.java create mode 100644 src/org/redkale/source/DistributeGenerator.java create mode 100644 src/org/redkale/source/EntityCache.java create mode 100644 src/org/redkale/source/EntityInfo.java create mode 100644 src/org/redkale/source/FilterBean.java create mode 100644 src/org/redkale/source/FilterColumn.java create mode 100644 src/org/redkale/source/FilterExpress.java create mode 100644 src/org/redkale/source/FilterGroup.java create mode 100644 src/org/redkale/source/FilterGroups.java create mode 100644 src/org/redkale/source/FilterJoinColumn.java create mode 100644 src/org/redkale/source/FilterJoinNode.java create mode 100644 src/org/redkale/source/FilterNode.java create mode 100644 src/org/redkale/source/FilterNodeBean.java create mode 100644 src/org/redkale/source/Flipper.java create mode 100644 src/org/redkale/source/JDBCPoolSource.java create mode 100644 src/org/redkale/source/Range.java create mode 100644 src/org/redkale/source/ReckonType.java create mode 100644 src/org/redkale/source/VirtualEntity.java create mode 100644 src/org/redkale/util/AnyValue.java create mode 100644 src/org/redkale/util/Attribute.java create mode 100644 src/org/redkale/util/AutoLoad.java create mode 100644 src/org/redkale/util/ByteArray.java create mode 100644 src/org/redkale/util/Creator.java create mode 100644 src/org/redkale/util/DLong.java create mode 100644 src/org/redkale/util/DebugMethodVisitor.java create mode 100644 src/org/redkale/util/Ignore.java create mode 100644 src/org/redkale/util/LogLevel.java create mode 100644 src/org/redkale/util/Nameable.java create mode 100644 src/org/redkale/util/ObjectNode.java create mode 100644 src/org/redkale/util/ObjectPool.java create mode 100644 src/org/redkale/util/Reproduce.java create mode 100644 src/org/redkale/util/ResourceFactory.java create mode 100644 src/org/redkale/util/SelectColumn.java create mode 100644 src/org/redkale/util/Sheet.java create mode 100644 src/org/redkale/util/TypeToken.java create mode 100644 src/org/redkale/util/Utility.java create mode 100644 src/org/redkale/watch/WatchFactory.java create mode 100644 src/org/redkale/watch/WatchNode.java create mode 100644 src/org/redkale/watch/WatchNumber.java create mode 100644 src/org/redkale/watch/WatchSupplier.java create mode 100644 src/org/redkale/watch/Watchable.java diff --git a/src/org/redkale/boot/Application.java b/src/org/redkale/boot/Application.java new file mode 100644 index 000000000..01523cd17 --- /dev/null +++ b/src/org/redkale/boot/Application.java @@ -0,0 +1,566 @@ +/* + * 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.boot.ClassFilter.FilterEntry; +import org.redkale.util.AnyValue.DefaultAnyValue; +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.nio.file.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.function.*; +import java.util.logging.*; +import javax.xml.parsers.*; +import org.redkale.convert.bson.*; +import org.redkale.convert.json.*; +import org.redkale.net.*; +import org.redkale.net.sncp.*; +import org.redkale.service.*; +import org.redkale.source.*; +import org.redkale.util.*; +import org.redkale.watch.*; +import org.w3c.dom.*; + +/** + * 编译时需要加入: -XDignore.symbol.file=true + *

+ * 进程启动类,程序启动后读取application.xml,进行classpath扫描动态加载Service与Servlet + * 优先加载所有SNCP协议的服务, 再加载其他协议服务, + * 最后进行Service、Servlet与其他资源之间的依赖注入。 + * + * + * @author zhangjx + */ +public final class Application { + + //当前进程启动的时间, 类型: long + public static final String RESNAME_APP_TIME = "APP_TIME"; + + //当前进程的根目录, 类型:String + public static final String RESNAME_APP_HOME = "APP_HOME"; + + //application.xml 文件中resources节点的内容, 类型: AnyValue + public static final String RESNAME_APP_GRES = "APP_GRES"; + + //当前进程节点的name, 类型:String + public static final String RESNAME_APP_NODE = "APP_NODE"; + + //当前进程节点的IP地址, 类型:InetAddress、String + public static final String RESNAME_APP_ADDR = "APP_ADDR"; + + //当前SNCP Server的IP地址+端口集合 类型: Map、HashMap + public static final String RESNAME_APP_NODES = "APP_NODES"; + + //当前Service的IP地址+端口 类型: SocketAddress、InetSocketAddress、String + public static final String RESNAME_SERVER_ADDR = "SERVER_ADDR"; // SERVER_ADDR + + //当前SNCP Server所属的组 类型: String + public static final String RESNAME_SERVER_GROUP = "SERVER_GROUP"; + + //当前Service所属的组 类型: Set、String[] + public static final String RESNAME_SNCP_GROUPS = Sncp.RESNAME_SNCP_GROUPS; // SNCP_GROUPS + + protected final Map globalNodes = new HashMap<>(); + + private final Map> globalGroups = new HashMap<>(); + + protected final List transports = new ArrayList<>(); + + protected final InetAddress localAddress; + + protected final List sources = new CopyOnWriteArrayList<>(); + + protected final List servers = new CopyOnWriteArrayList<>(); + + protected CountDownLatch servicecdl; //会出现两次赋值 + + //-------------------------------------------------------------------------------------------- + private final ResourceFactory factory = ResourceFactory.root(); + + private final WatchFactory watch = WatchFactory.root(); + + private File home; + + private final Logger logger; + + private final AnyValue config; + + private final long startTime = System.currentTimeMillis(); + + private final CountDownLatch serversLatch; + + private Application(final AnyValue config) { + this.config = config; + + final File root = new File(System.getProperty(RESNAME_APP_HOME)); + this.factory.register(RESNAME_APP_TIME, long.class, this.startTime); + this.factory.register(RESNAME_APP_HOME, Path.class, root.toPath()); + this.factory.register(RESNAME_APP_HOME, File.class, root); + try { + this.factory.register(RESNAME_APP_HOME, root.getCanonicalPath()); + this.home = root.getCanonicalFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + String localaddr = config.getValue("address", "").trim(); + this.localAddress = localaddr.isEmpty() ? Utility.localInetAddress() : new InetSocketAddress(localaddr, 0).getAddress(); + Application.this.factory.register(RESNAME_APP_ADDR, Application.this.localAddress.getHostAddress()); + Application.this.factory.register(RESNAME_APP_ADDR, InetAddress.class, Application.this.localAddress); + { + String node = config.getValue("node", "").trim(); + if (node.isEmpty()) { + StringBuilder sb = new StringBuilder(); + byte[] bs = this.localAddress.getAddress(); + int v1 = bs[bs.length - 2] & 0xff; + int v2 = bs[bs.length - 1] & 0xff; + if (v1 <= 0xf) sb.append('0'); + sb.append(Integer.toHexString(v1)); + if (v2 <= 0xf) sb.append('0'); + sb.append(Integer.toHexString(v2)); + node = sb.toString(); + } + Application.this.factory.register(RESNAME_APP_NODE, node); + System.setProperty(RESNAME_APP_NODE, node); + } + //以下是初始化日志配置 + final File logconf = new File(root, "conf/logging.properties"); + if (logconf.isFile() && logconf.canRead()) { + try { + final String rootpath = root.getCanonicalPath().replace('\\', '/'); + FileInputStream fin = new FileInputStream(logconf); + 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")) { + prop.setProperty("handlers", handlers.replace("java.util.logging.FileHandler", 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); + } + + public ResourceFactory getResourceFactory() { + return factory; + } + + public WatchFactory getWatchFactory() { + return watch; + } + + public File getHome() { + return home; + } + + public long getStartTime() { + return startTime; + } + + private void initLogging() { + + } + + public void init() throws Exception { + System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "" + Runtime.getRuntime().availableProcessors() * 4); + 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"); + + File persist = new File(this.home, "conf/persistence.xml"); + final String homepath = this.home.getCanonicalPath(); + if (persist.isFile()) System.setProperty(DataDefaultSource.DATASOURCE_CONFPATH, persist.getCanonicalPath()); + logger.log(Level.INFO, RESNAME_APP_HOME + "=" + homepath + "\r\n" + RESNAME_APP_ADDR + "=" + this.localAddress.getHostAddress()); + String lib = config.getValue("lib", "").trim().replace("${APP_HOME}", homepath); + lib = lib.isEmpty() ? (homepath + "/conf") : (lib + ";" + homepath + "/conf"); + Server.loadLib(logger, lib); + initLogging(); + if (this.localAddress != null) { + byte[] bs = this.localAddress.getAddress(); + int v = (0xff & bs[bs.length - 2]) % 10 * 100 + (0xff & bs[bs.length - 1]); + this.factory.register("property.datasource.nodeid", "" + v); + } + //------------------------------------------------------------------------ + final AnyValue resources = config.getAnyValue("resources"); + if (resources != null) { + factory.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; + dfload = dfload.trim().replace("${APP_HOME}", home.getCanonicalPath()).replace('\\', '/'); + final File df = (dfload.indexOf('/') < 0) ? new File(home, "conf/" + dfload) : new File(dfload); + if (df.isFile()) { + Properties ps = new Properties(); + InputStream in = new FileInputStream(df); + ps.load(in); + in.close(); + ps.forEach((x, y) -> factory.register("property." + x, y)); + } + } + } + for (AnyValue prop : properties.getAnyValues("property")) { + String name = prop.getValue("name"); + String value = prop.getValue("value"); + if (name == null || value == null) continue; + if (name.startsWith("system.property.")) { + System.setProperty(name.substring("system.property.".length()), value); + } else { + factory.register("property." + name, value); + } + } + } + } + this.factory.register(BsonFactory.root()); + this.factory.register(JsonFactory.root()); + this.factory.register(BsonFactory.root().getConvert()); + this.factory.register(JsonFactory.root().getConvert()); + 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", ""); + 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")); + } + Set addrs = globalGroups.get(group); + if (addrs == null) { + addrs = new LinkedHashSet<>(); + globalGroups.put(group, addrs); + } + for (AnyValue node : conf.getAnyValues("node")) { + final InetSocketAddress addr = new InetSocketAddress(node.getValue("addr"), node.getIntValue("port")); + addrs.add(addr); + String oldgroup = globalNodes.get(addr); + if (oldgroup != null) throw new RuntimeException(addr + " had one more group " + (globalNodes.get(addr))); + globalNodes.put(addr, group); + } + } + } + //------------------------------------------------------------------------ + } + + private void startSelfServer() throws Exception { + final Application application = this; + new Thread() { + { + setName("Application-Control-Thread"); + } + + @Override + public void run() { + try { + final DatagramChannel channel = DatagramChannel.open(); + channel.configureBlocking(true); + channel.socket().setSoTimeout(3000); + channel.bind(new InetSocketAddress(config.getValue("host", "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); + } + } + } + } catch (Exception e) { + logger.log(Level.INFO, "Control fail", e); + System.exit(1); + } + } + }.start(); + } + + private void sendShutDown() throws Exception { + final DatagramChannel channel = DatagramChannel.open(); + channel.configureBlocking(true); + channel.connect(new InetSocketAddress(config.getValue("host", "127.0.0.1"), config.getIntValue("port"))); + ByteBuffer buffer = ByteBuffer.allocate(128); + buffer.put("SHUTDOWN".getBytes()); + buffer.flip(); + channel.write(buffer); + buffer.clear(); + channel.configureBlocking(false); + channel.read(buffer); + buffer.flip(); + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + channel.close(); + logger.info(new String(bytes)); + Thread.sleep(500); + } + + public void start() throws Exception { + final AnyValue[] entrys = config.getAnyValues("server"); + CountDownLatch timecd = new CountDownLatch(entrys.length); + final List sncps = new ArrayList<>(); + final List others = new ArrayList<>(); + for (final AnyValue entry : entrys) { + if (entry.getValue("protocol", "").toUpperCase().startsWith("SNCP")) { + sncps.add(entry); + } else { + others.add(entry); + } + } + if (!sncps.isEmpty() && globalNodes.isEmpty()) throw new RuntimeException("found SNCP Server node but not found node info."); + + factory.register(RESNAME_APP_NODES, new TypeToken>() { + }.getType(), globalNodes); + factory.register(RESNAME_APP_NODES, new TypeToken>() { + }.getType(), globalNodes); + + factory.register(RESNAME_APP_NODES, new TypeToken>>() { + }.getType(), globalGroups); + factory.register(RESNAME_APP_NODES, new TypeToken>>() { + }.getType(), globalGroups); + + runServers(timecd, sncps); //必须确保sncp都启动后再启动其他协议 + runServers(timecd, others); + timecd.await(); + logger.info(this.getClass().getSimpleName() + " started in " + (System.currentTimeMillis() - startTime) + " ms"); + this.serversLatch.await(); + } + + @SuppressWarnings("unchecked") + private void runServers(CountDownLatch timecd, final List serconfs) throws Exception { + this.servicecdl = new CountDownLatch(serconfs.size()); + CountDownLatch sercdl = new CountDownLatch(serconfs.size()); + final AtomicBoolean inited = new AtomicBoolean(false); + final Map> nodeClasses = new HashMap<>(); + for (final AnyValue serconf : serconfs) { + Thread thread = new Thread() { + { + String host = serconf.getValue("host", "").replace("0.0.0.0", "[0]"); + setName(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 = new NodeSncpServer(Application.this, serconf); + } else if ("HTTP".equals(protocol) || "HTTPS".equals(protocol)) { + server = new NodeHttpServer(Application.this, serconf); + } else { + if (!inited.get()) { + synchronized (nodeClasses) { + if (!inited.get()) { + inited.set(true); + ClassFilter profilter = new ClassFilter(NodeProtocol.class, NodeServer.class); + ClassFilter.Loader.load(home, profilter); + final Set> entrys = profilter.getFilterEntrys(); + for (FilterEntry entry : entrys) { + final Class type = entry.getType(); + NodeProtocol pros = type.getAnnotation(NodeProtocol.class); + for (String p : pros.value()) { + p = p.toUpperCase(); + if ("SNCP".equals(p) || "HTTP".equals(p) || "HTTPS".equals(p)) continue; + final Class 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 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); + 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 singleton(Class serviceClass) throws Exception { + return singleton(serviceClass, false); + } + + public static T singleton(Class serviceClass, boolean remote) throws Exception { + final Application application = Application.create(); + Consumer executor = (x) -> Executors.newFixedThreadPool(8).submit(x); + T service = remote ? Sncp.createRemoteService("", executor, serviceClass, null, new LinkedHashSet<>(), null) + : Sncp.createLocalService("", executor, serviceClass, null, new LinkedHashSet<>(), null, null); + application.init(); + application.factory.register(service); + application.servicecdl = new CountDownLatch(1); + final NodeServer server = new NodeHttpServer(application, null); + server.init(application.config); + server.factory.inject(service, server); + return service; + } + + private static Application create() throws IOException { + final String home = new File(System.getProperty(RESNAME_APP_HOME, "")).getCanonicalPath(); + System.setProperty(RESNAME_APP_HOME, home); + File appfile = new File(home, "conf/application.xml"); + return new Application(load(new FileInputStream(appfile))); + } + + public static void main(String[] args) throws Exception { + //运行主程序 + final Application application = Application.create(); + if (System.getProperty("SHUTDOWN") != null) { + application.sendShutDown(); + return; + } + application.init(); + application.startSelfServer(); + try { + application.start(); + } catch (Exception e) { + application.logger.log(Level.SEVERE, "Application start error", e); + System.exit(0); + } + System.exit(0); + } + + Set findGlobalGroup(String group) { + if (group == null) return null; + Set set = globalGroups.get(group); + return set == null ? null : new LinkedHashSet<>(set); + } + + private void shutdown() throws Exception { + servers.stream().forEach((server) -> { + try { + server.shutdown(); + } catch (Exception t) { + logger.log(Level.WARNING, " shutdown server(" + server.getSocketAddress() + ") error", t); + } finally { + serversLatch.countDown(); + } + }); + for (DataSource source : sources) { + try { + source.getClass().getMethod("close").invoke(source); + } catch (Exception e) { + logger.log(Level.FINER, "close DataSource erroneous", e); + } + } + } + + 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); + } + + } +} diff --git a/src/org/redkale/boot/ClassFilter.java b/src/org/redkale/boot/ClassFilter.java new file mode 100644 index 000000000..e543c953a --- /dev/null +++ b/src/org/redkale/boot/ClassFilter.java @@ -0,0 +1,436 @@ +/* + * 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.Ignore; +import org.redkale.util.AutoLoad; +import org.redkale.util.AnyValue; +import org.redkale.util.AnyValue.DefaultAnyValue; +import java.io.*; +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.jar.*; +import java.util.logging.*; +import java.util.regex.*; + +/** + * class过滤器, 符合条件的class会保留下来存入FilterEntry。 + * + * @author zhangjx + * @param + */ +public final class ClassFilter { + + private final Set> entrys = new HashSet<>(); + + private boolean refused; + + private Class superClass; + + private Class annotationClass; + + private Pattern[] includePatterns; + + private Pattern[] excludePatterns; + + private List ors; + + private List ands; + + private AnyValue conf; + + public ClassFilter(Class annotationClass, Class superClass) { + this(annotationClass, superClass, null); + } + + public ClassFilter(Class annotationClass, Class superClass, AnyValue conf) { + this.annotationClass = annotationClass; + this.superClass = superClass; + this.conf = conf; + } + + public ClassFilter or(ClassFilter filter) { + if (ors == null) ors = new ArrayList<>(); + ors.add(filter); + return this; + } + + public ClassFilter and(ClassFilter filter) { + if (ands == null) ands = new ArrayList<>(); + ands.add(filter); + return this; + } + + /** + * 获取符合条件的class集合 + *

+ * @return + */ + public final Set> getFilterEntrys() { + return entrys; + } + + /** + * 自动扫描地过滤指定的class + *

+ * @param property + * @param clazzname + */ + @SuppressWarnings("unchecked") + public final void filter(AnyValue property, String clazzname) { + filter(property, clazzname, true); + } + + /** + * 过滤指定的class + *

+ * @param property application.xml中对应class节点下的property属性项 + * @param clazzname class名称 + * @param autoscan 为true表示自动扫描的, false表示显著调用filter, AutoLoad的注解将被忽略 + */ + public final void filter(AnyValue property, String clazzname, boolean autoscan) { + boolean r = accept0(property, clazzname); + ClassFilter cf = r ? this : null; + if (r && ands != null) { + for (ClassFilter filter : ands) { + if (!filter.accept(property, clazzname)) return; + } + } + if (!r && ors != null) { + for (ClassFilter filter : ors) { + if (filter.accept(property, clazzname)) { + cf = filter; + break; + } + } + } + if (cf == null) return; + try { + Class clazz = Class.forName(clazzname); + if (!cf.accept(property, clazz, autoscan)) return; + if (cf.conf != null) { + if (property == null) { + property = cf.conf; + } else { + if (property instanceof DefaultAnyValue) { + ((DefaultAnyValue) property).addAll(cf.conf); + } else { + DefaultAnyValue dav = new DefaultAnyValue(); + dav.addAll(property); + dav.addAll(cf.conf); + property = dav; + } + } + } + entrys.add(new FilterEntry(clazz, autoscan, property)); + } catch (Throwable cfe) { + } + } + + private static Pattern[] toPattern(String[] regs) { + if (regs == null) return null; + int i = 0; + Pattern[] rs = new Pattern[regs.length]; + for (String reg : regs) { + if (reg == null || reg.trim().isEmpty()) continue; + rs[i++] = Pattern.compile(reg.trim()); + } + if (i == 0) return null; + if (i == rs.length) return rs; + Pattern[] ps = new Pattern[i]; + System.arraycopy(rs, 0, ps, 0, i); + return ps; + } + + /** + * 判断class是否有效 + *

+ * @param property + * @param classname + * @return + */ + public boolean accept(AnyValue property, String classname) { + boolean r = accept0(property, classname); + if (r && ands != null) { + for (ClassFilter filter : ands) { + if (!filter.accept(property, classname)) return false; + } + } + if (!r && ors != null) { + for (ClassFilter filter : ors) { + if (filter.accept(property, classname)) return true; + } + } + return r; + } + + private boolean accept0(AnyValue property, String classname) { + if (this.refused) return false; + if (classname.startsWith("java.") || classname.startsWith("javax.")) return false; + if (excludePatterns != null) { + for (Pattern reg : excludePatterns) { + if (reg.matcher(classname).matches()) return false; + } + } + if (includePatterns != null) { + for (Pattern reg : includePatterns) { + if (reg.matcher(classname).matches()) return true; + } + } + return includePatterns == null; + } + + /** + * 判断class是否有效 + *

+ * @param property + * @param clazz + * @param autoscan + * @return + */ + @SuppressWarnings("unchecked") + public boolean accept(AnyValue property, Class clazz, boolean autoscan) { + if (this.refused || !Modifier.isPublic(clazz.getModifiers())) return false; + if (clazz.getAnnotation(Ignore.class) != null) return false; + if (autoscan) { + AutoLoad auto = (AutoLoad) clazz.getAnnotation(AutoLoad.class); + if (auto != null && !auto.value()) return false; + } + if (annotationClass != null && clazz.getAnnotation(annotationClass) == null) return false; + return superClass == null || (clazz != superClass && superClass.isAssignableFrom(clazz)); + } + + public void setSuperClass(Class superClass) { + this.superClass = superClass; + } + + public void setAnnotationClass(Class annotationClass) { + this.annotationClass = annotationClass; + } + + public Pattern[] getIncludePatterns() { + return includePatterns; + } + + public void setIncludePatterns(String[] includePatterns) { + this.includePatterns = toPattern(includePatterns); + } + + public Pattern[] getExcludePatterns() { + return excludePatterns; + } + + public void setExcludePatterns(String[] excludePatterns) { + this.excludePatterns = toPattern(excludePatterns); + } + + public Class getAnnotationClass() { + return annotationClass; + } + + public Class getSuperClass() { + return superClass; + } + + public boolean isRefused() { + return refused; + } + + public void setRefused(boolean refused) { + this.refused = refused; + } + + /** + * 存放符合条件的class与class指定的属性项 + *

+ * @param + */ + public static final class FilterEntry { + + private final HashSet groups = new LinkedHashSet<>(); + + private final String name; + + private final Class type; + + private final AnyValue property; + + private final boolean autoload; + + public FilterEntry(Class type, AnyValue property) { + this(type, false, property); + } + + public FilterEntry(Class type, final boolean autoload, AnyValue property) { + this.type = type; + String str = property == null ? null : property.getValue("groups"); + if (str != null) groups.addAll(Arrays.asList(str.split(";"))); + this.property = property; + this.autoload = autoload; + this.name = property == null ? "" : property.getValue("name", ""); + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "[thread=" + Thread.currentThread().getName() + + ", type=" + this.type.getSimpleName() + ", name=" + name + ", groups=" + groups + "]"; + } + + @Override + public int hashCode() { + return this.type.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + return (this.type == ((FilterEntry) obj).type && this.groups.equals(((FilterEntry) obj).groups) && this.name.equals(((FilterEntry) obj).name)); + } + + public Class getType() { + return type; + } + + public String getName() { + return name; + } + + public AnyValue getProperty() { + return property; + } + + public HashSet getGroups() { + return groups; + } + + public boolean isAutoload() { + return autoload; + } + + } + + /** + * class加载类 + */ + public static class Loader { + + protected static final Logger logger = Logger.getLogger(Loader.class.getName()); + + protected static final ConcurrentMap> cache = new ConcurrentHashMap<>(); + + public static void close() { + cache.clear(); + } + + /** + * 加载当前线程的classpath扫描所有class进行过滤 + *

+ * @param exclude 不需要扫描的文件夹, 可以为null + * @param filters + * @throws IOException + */ + public static void load(final File exclude, final ClassFilter... filters) throws IOException { + URLClassLoader loader = (URLClassLoader) Thread.currentThread().getContextClassLoader(); + List urlfiles = new ArrayList<>(2); + List urljares = new ArrayList<>(2); + final URL exurl = exclude != null ? exclude.toURI().toURL() : null; + for (URL url : loader.getURLs()) { + if (exurl != null && exurl.sameFile(url)) continue; + if (url.getPath().endsWith(".jar")) { + urljares.add(url); + } else { + urlfiles.add(url); + } + } + + List files = new ArrayList<>(); + boolean debug = logger.isLoggable(Level.FINEST); + StringBuilder debugstr = new StringBuilder(); + for (final URL url : urljares) { + Set classes = cache.get(url); + if (classes == null) { + synchronized (cache) { + if (cache.get(url) == null) { + classes = new CopyOnWriteArraySet<>(); + cache.put(url, classes); + } else { + classes = cache.get(url); + } + } + try (JarFile jar = new JarFile(URLDecoder.decode(url.getFile(), "UTF-8"))) { + Enumeration it = jar.entries(); + while (it.hasMoreElements()) { + String entryname = it.nextElement().getName().replace('/', '.'); + if (entryname.endsWith(".class") && entryname.indexOf('$') < 0) { + String classname = entryname.substring(0, entryname.length() - 6); + if (classname.startsWith("javax.") || classname.startsWith("com.sun.") || classname.startsWith("com.mysql.")) continue; + classes.add(classname); + if (debug) debugstr.append(classname).append("\r\n"); + for (final ClassFilter filter : filters) { + if (filter != null) filter.filter(null, classname); + } + } + } + } + } else { + for (String classname : classes) { + for (final ClassFilter filter : filters) { + if (filter != null) filter.filter(null, classname); + } + } + } + } + for (final URL url : urlfiles) { + Set classes = cache.get(url); + if (classes == null) { + synchronized (cache) { + if (cache.get(url) == null) { + classes = new CopyOnWriteArraySet<>(); + cache.put(url, classes); + } else { + classes = cache.get(url); + } + } + files.clear(); + File root = new File(url.getFile()); + String rootpath = root.getPath(); + loadClassFiles(exclude, root, files); + for (File f : files) { + String classname = f.getPath().substring(rootpath.length() + 1, f.getPath().length() - 6).replace(File.separatorChar, '.'); + if (classname.startsWith("javax.") || classname.startsWith("org.") || classname.startsWith("com.mysql.")) continue; + classes.add(classname); + if (debug) debugstr.append(classname).append("\r\n"); + for (final ClassFilter filter : filters) { + if (filter != null) filter.filter(null, classname); + } + } + } else { + for (String classname : classes) { + for (final ClassFilter filter : filters) { + if (filter != null) filter.filter(null, classname); + } + } + } + } + //if (debug) logger.log(Level.INFO, "scan classes: \r\n{0}", debugstr); + } + + private static void loadClassFiles(File exclude, File root, List files) { + if (root.isFile() && root.getName().endsWith(".class")) { + files.add(root); + } else if (root.isDirectory()) { + if (exclude != null && exclude.equals(root)) return; + for (File f : root.listFiles()) { + loadClassFiles(exclude, f, files); + } + } + } + } +} diff --git a/src/org/redkale/boot/LogFileHandler.java b/src/org/redkale/boot/LogFileHandler.java new file mode 100644 index 000000000..9b5d7e4fd --- /dev/null +++ b/src/org/redkale/boot/LogFileHandler.java @@ -0,0 +1,267 @@ +/* + * 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 java.io.*; +import java.nio.file.*; +import static java.nio.file.StandardCopyOption.*; +import java.time.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.logging.*; +import java.util.logging.Formatter; + +/** + * 自定义的日志存储类 + *

+ * @author zhangjx + */ +public class LogFileHandler extends Handler { + + public static class SncpLogFileHandler extends LogFileHandler { + + @Override + public String getPrefix() { + return "sncp-"; + } + } + + public static class LoggingFormater extends Formatter { + + private static final String format = "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%tL %4$s %2$s\r\n%5$s%6$s\r\n"; + + @Override + public String format(LogRecord record) { + String source; + if (record.getSourceClassName() != null) { + source = record.getSourceClassName(); + if (record.getSourceMethodName() != null) { + source += " " + record.getSourceMethodName(); + } + } else { + source = record.getLoggerName(); + } + String message = formatMessage(record); + String throwable = ""; + if (record.getThrown() != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw) { + @Override + public void println() { + super.print("\r\n"); + } + }; + pw.println(); + record.getThrown().printStackTrace(pw); + pw.close(); + throwable = sw.toString(); + } + return String.format(format, + System.currentTimeMillis(), + source, + record.getLoggerName(), + record.getLevel().getName(), + message, + throwable); + } + + } + + protected final LinkedBlockingQueue records = new LinkedBlockingQueue(); + + private String pattern; + + private int limit; //文件大小限制 + + private final AtomicInteger index = new AtomicInteger(); + + private int count = 1; //文件限制 + + private long tomorrow; + + private boolean append; + + private final AtomicLong length = new AtomicLong(); + + private File logfile; + + private OutputStream stream; + + public LogFileHandler() { + updateTomorrow(); + configure(); + open(); + } + + private void updateTomorrow() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.add(Calendar.DAY_OF_YEAR, 1); + long t = cal.getTimeInMillis(); + if (this.tomorrow != t) index.set(0); + this.tomorrow = t; + } + + private void open() { + final String name = "Logging-" + getClass().getSimpleName() + "-Thread"; + new Thread() { + { + setName(name); + setDaemon(true); + } + + @Override + public void run() { + while (true) { + try { + LogRecord record = records.take(); + final boolean bigger = (limit > 0 && limit <= length.get()); + if (bigger || tomorrow <= record.getMillis()) { + updateTomorrow(); + if (stream != null) { + stream.close(); + if (bigger) { + for (int i = Math.min(count - 2, index.get() - 1); i > 0; i--) { + File greater = new File(logfile.getPath() + "." + i); + if (greater.exists()) Files.move(greater.toPath(), new File(logfile.getPath() + "." + (i + 1)).toPath(), REPLACE_EXISTING, ATOMIC_MOVE); + } + Files.move(logfile.toPath(), new File(logfile.getPath() + ".1").toPath(), REPLACE_EXISTING, ATOMIC_MOVE); + } + stream = null; + } + } + if (stream == null) { + index.incrementAndGet(); + java.time.LocalDate date = LocalDate.now(); + logfile = new File(pattern.replace("%m", String.valueOf((date.getYear() * 100 + date.getMonthValue()))).replace("%d", String.valueOf((date.getYear() * 10000 + date.getMonthValue() * 100 + date.getDayOfMonth())))); + logfile.getParentFile().mkdirs(); + length.set(logfile.length()); + stream = new FileOutputStream(logfile, append); + } + //----------------------写日志------------------------- + String message = getFormatter().format(record); + String encoding = getEncoding(); + byte[] bytes = encoding == null ? message.getBytes() : message.getBytes(encoding); + stream.write(bytes); + length.addAndGet(bytes.length); + } catch (Exception e) { + ErrorManager err = getErrorManager(); + if (err != null) err.error(null, e, ErrorManager.WRITE_FAILURE); + } + } + + } + }.start(); + } + + public String getPrefix() { + return ""; + } + + private void configure() { + LogManager manager = LogManager.getLogManager(); + String cname = LogFileHandler.class.getName(); + pattern = manager.getProperty(cname + ".pattern"); + if (pattern == null) { + pattern = "logs-%m/" + getPrefix() + "log-%d.log"; + } else { + int pos = pattern.lastIndexOf('/'); + if (pos > 0) { + pattern = pattern.substring(0, pos + 1) + getPrefix() + pattern.substring(pos + 1); + } else { + pattern = getPrefix() + pattern; + } + } + String limitstr = manager.getProperty(cname + ".limit"); + try { + if (limitstr != null) limit = Math.abs(Integer.decode(limitstr)); + } catch (Exception e) { + } + String countstr = manager.getProperty(cname + ".count"); + try { + if (countstr != null) count = Math.max(1, Math.abs(Integer.decode(countstr))); + } catch (Exception e) { + } + String appendstr = manager.getProperty(cname + ".append"); + try { + if (appendstr != null) append = "true".equalsIgnoreCase(appendstr) || "1".equals(appendstr); + } catch (Exception e) { + } + String levelstr = manager.getProperty(cname + ".level"); + try { + if (levelstr != null) { + Level l = Level.parse(levelstr); + setLevel(l != null ? l : Level.ALL); + } + } catch (Exception e) { + } + String filterstr = manager.getProperty(cname + ".filter"); + try { + if (filterstr != null) { + Class clz = ClassLoader.getSystemClassLoader().loadClass(filterstr); + setFilter((Filter) clz.newInstance()); + } + } catch (Exception e) { + } + String formatterstr = manager.getProperty(cname + ".formatter"); + try { + if (formatterstr != null) { + Class clz = ClassLoader.getSystemClassLoader().loadClass(formatterstr); + setFormatter((Formatter) clz.newInstance()); + } + } catch (Exception e) { + } + if (getFormatter() == null) setFormatter(new SimpleFormatter()); + + String encodingstr = manager.getProperty(cname + ".encoding"); + try { + if (encodingstr != null) setEncoding(encodingstr); + } catch (Exception e) { + } + } + + @Override + public void publish(LogRecord record) { + final String sourceClassName = record.getSourceClassName(); + if (sourceClassName == null || true) { + StackTraceElement[] ses = new Throwable().getStackTrace(); + for (int i = 2; i < ses.length; i++) { + if (ses[i].getClassName().startsWith("java.util.logging")) continue; + record.setSourceClassName('[' + Thread.currentThread().getName() + "] " + ses[i].getClassName()); + record.setSourceMethodName(ses[i].getMethodName()); + break; + } + } else { + record.setSourceClassName('[' + Thread.currentThread().getName() + "] " + sourceClassName); + } + records.offer(record); + } + + @Override + public void flush() { + try { + if (stream != null) stream.flush(); + } catch (Exception e) { + ErrorManager err = getErrorManager(); + if (err != null) err.error(null, e, ErrorManager.FLUSH_FAILURE); + } + } + + @Override + public void close() throws SecurityException { + try { + if (stream != null) stream.close(); + } catch (Exception e) { + ErrorManager err = getErrorManager(); + if (err != null) err.error(null, e, ErrorManager.CLOSE_FAILURE); + } + } + +} diff --git a/src/org/redkale/boot/NodeHttpServer.java b/src/org/redkale/boot/NodeHttpServer.java new file mode 100644 index 000000000..431effdad --- /dev/null +++ b/src/org/redkale/boot/NodeHttpServer.java @@ -0,0 +1,133 @@ +/* + * 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.net.http.WebServlet; +import org.redkale.net.http.HttpServer; +import org.redkale.net.http.HttpServlet; +import org.redkale.util.AnyValue; +import org.redkale.boot.ClassFilter.FilterEntry; +import org.redkale.util.AnyValue.DefaultAnyValue; +import java.lang.reflect.*; +import java.net.InetSocketAddress; +import java.util.*; +import java.util.logging.*; +import javax.annotation.*; +import org.redkale.net.*; +import org.redkale.net.http.*; +import org.redkale.net.sncp.*; +import org.redkale.service.*; +import org.redkale.util.*; + +/** + * HTTP Server节点的配置Server + * + * @author zhangjx + */ +@NodeProtocol({"HTTP", "HTTPS"}) +public final class NodeHttpServer extends NodeServer { + + private final HttpServer httpServer; + + public NodeHttpServer(Application application, AnyValue serconf) { + super(application, application.getResourceFactory().createChild(), createServer(application, serconf)); + this.httpServer = (HttpServer) server; + } + + private static Server createServer(Application application, AnyValue serconf) { + return new HttpServer(application.getStartTime(), application.getWatchFactory()); + } + + @Override + public InetSocketAddress getSocketAddress() { + return httpServer == null ? null : httpServer.getSocketAddress(); + } + + @Override + protected ClassFilter createServletClassFilter() { + return createClassFilter(null, WebServlet.class, HttpServlet.class, null, "servlets", "servlet"); + } + + @Override + protected void loadServlet(ClassFilter servletFilter) throws Exception { + if (httpServer != null) loadHttpServlet(this.nodeConf.getAnyValue("servlets"), servletFilter); + } + + @Override + protected void loadService(ClassFilter serviceFilter) throws Exception { + super.loadService(serviceFilter); + initWebSocketService(); + } + + private void initWebSocketService() { + final NodeServer self = this; + final ResourceFactory regFactory = application.getResourceFactory(); + factory.add(WebSocketNode.class, (ResourceFactory rf, final Object src, Field field, Object attachment) -> { + try { + Resource rs = field.getAnnotation(Resource.class); + if (rs == null) return; + if (!(src instanceof WebSocketServlet)) return; + String rcname = rs.name(); + if (rcname.equals(ResourceFactory.RESOURCE_PARENT_NAME)) rcname = ((WebSocketServlet) src).name(); + synchronized (regFactory) { + Service nodeService = (Service) rf.find(rcname, WebSocketNode.class); + if (nodeService == null) { + nodeService = Sncp.createLocalService(rcname, getExecutor(), (Class) WebSocketNodeService.class, + getSncpAddress(), sncpDefaultGroups, sncpSameGroupTransports, sncpDiffGroupTransports); + regFactory.register(rcname, WebSocketNode.class, nodeService); + factory.inject(nodeService, self); + logger.fine("[" + Thread.currentThread().getName() + "] Load " + nodeService); + if (getSncpAddress() != null) { + NodeSncpServer sncpServer = null; + for (NodeServer node : application.servers) { + if (node.isSNCP() && getSncpAddress().equals(node.getSncpAddress())) { + sncpServer = (NodeSncpServer) node; + } + } + ServiceWrapper wrapper = new ServiceWrapper(WebSocketNodeService.class, nodeService, rcname, getSncpGroup(), sncpDefaultGroups, null); + sncpServer.getSncpServer().addService(wrapper); + } + } + field.set(src, nodeService); + } + } catch (Exception e) { + logger.log(Level.SEVERE, "WebSocketNode inject error", e); + } + }); + } + + protected void loadHttpServlet(final AnyValue conf, final ClassFilter filter) throws Exception { + final StringBuilder sb = logger.isLoggable(Level.FINE) ? new StringBuilder() : null; + final String prefix = conf == null ? "" : conf.getValue("prefix", ""); + final String threadName = "[" + Thread.currentThread().getName() + "] "; + for (FilterEntry en : filter.getFilterEntrys()) { + Class clazz = (Class) en.getType(); + if (Modifier.isAbstract(clazz.getModifiers())) continue; + WebServlet ws = clazz.getAnnotation(WebServlet.class); + if (ws == null || ws.value().length == 0) continue; + final HttpServlet servlet = clazz.newInstance(); + factory.inject(servlet, this); + String[] mappings = ws.value(); + if (ws.fillurl() && !prefix.isEmpty()) { + for (int i = 0; i < mappings.length; i++) { + mappings[i] = prefix + mappings[i]; + } + } + DefaultAnyValue servletConf = (DefaultAnyValue) en.getProperty(); + WebInitParam[] webparams = ws.initParams(); + if (webparams.length > 0) { + if (servletConf == null) servletConf = new DefaultAnyValue(); + for (WebInitParam webparam : webparams) { + servletConf.addValue(webparam.name(), webparam.value()); + } + } + this.httpServer.addHttpServlet(servlet, servletConf, mappings); + if (sb != null) sb.append(threadName).append(" Loaded ").append(clazz.getName()).append(" --> ").append(Arrays.toString(mappings)).append(LINE_SEPARATOR); + } + if (sb != null && sb.length() > 0) logger.log(Level.FINE, sb.toString()); + } + +} diff --git a/src/org/redkale/boot/NodeProtocol.java b/src/org/redkale/boot/NodeProtocol.java new file mode 100644 index 000000000..fd7de4509 --- /dev/null +++ b/src/org/redkale/boot/NodeProtocol.java @@ -0,0 +1,19 @@ +/* + * 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 java.lang.annotation.*; + +/** + * + * @author zhangjx + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface NodeProtocol { + String[] value(); +} diff --git a/src/org/redkale/boot/NodeServer.java b/src/org/redkale/boot/NodeServer.java new file mode 100644 index 000000000..54edad91d --- /dev/null +++ b/src/org/redkale/boot/NodeServer.java @@ -0,0 +1,442 @@ +/* + * 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 static org.redkale.boot.Application.*; +import org.redkale.boot.ClassFilter.FilterEntry; +import org.redkale.net.sncp.ServiceWrapper; +import org.redkale.net.Server; +import org.redkale.net.sncp.Sncp; +import org.redkale.service.Service; +import org.redkale.util.AnyValue; +import org.redkale.util.AnyValue.DefaultAnyValue; +import java.io.*; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import java.util.function.Consumer; +import java.util.logging.*; +import javax.annotation.*; +import org.redkale.net.*; +import org.redkale.net.http.*; +import org.redkale.service.*; +import org.redkale.source.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public abstract class NodeServer { + + public static final String LINE_SEPARATOR = "\r\n"; + + protected final Logger logger; + + protected final boolean fine; + + protected final Application application; + + protected final ResourceFactory factory; + + protected final Server server; + + private String sncpGroup = null; //当前Server的SNCP协议的组 + + private String nodeProtocol = Transport.DEFAULT_PROTOCOL; + + private InetSocketAddress sncpAddress; //HttpServer中的sncpAddress 为所属group对应的SncpServer, 为null表示只是单节点,没有分布式结构 + + protected Consumer consumer; + + protected AnyValue nodeConf; + + protected final HashSet sncpDefaultGroups = new LinkedHashSet<>(); + + protected final List sncpSameGroupTransports = new ArrayList<>(); + + protected final List sncpDiffGroupTransports = new ArrayList<>(); + + protected final Set localServiceWrappers = new LinkedHashSet<>(); + + protected final Set remoteServiceWrappers = new LinkedHashSet<>(); + + public NodeServer(Application application, ResourceFactory factory, Server server) { + this.application = application; + this.factory = factory; + this.server = server; + this.logger = Logger.getLogger(this.getClass().getSimpleName()); + this.fine = logger.isLoggable(Level.FINE); + } + + protected Consumer getExecutor() throws Exception { + if (server == null) return null; + final Field field = Server.class.getDeclaredField("context"); + field.setAccessible(true); + return new Consumer() { + + private Context context; + + @Override + public void accept(Runnable t) { + if (context == null && server != null) { + try { + this.context = (Context) field.get(server); + } catch (Exception e) { + logger.log(Level.SEVERE, "Server (" + server.getSocketAddress() + ") cannot find Context", e); + } + } + context.submit(t); + } + + }; + } + + public static NodeServer create(Class clazz, Application application, AnyValue serconf) { + try { + return clazz.getConstructor(Application.class, AnyValue.class).newInstance(application, serconf); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void init(AnyValue config) throws Exception { + this.nodeConf = config == null ? AnyValue.create() : config; + if (isSNCP()) { // SNCP协议 + String host = this.nodeConf.getValue("host", "0.0.0.0").replace("0.0.0.0", ""); + this.sncpAddress = new InetSocketAddress(host.isEmpty() ? application.localAddress.getHostAddress() : host, this.nodeConf.getIntValue("port")); + this.sncpGroup = application.globalNodes.get(this.sncpAddress); + if (this.sncpGroup == null) throw new RuntimeException("Server (" + String.valueOf(config).replaceAll("\\s+", " ") + ") not found info"); + if (server != null) this.nodeProtocol = server.getProtocol(); + } + initGroup(); + if (this.sncpAddress != null) this.factory.register(RESNAME_SERVER_ADDR, this.sncpAddress); + if (this.sncpGroup != null) this.factory.register(RESNAME_SERVER_GROUP, this.sncpGroup); + { + //设置root文件夹 + String webroot = config.getValue("root", "root"); + File myroot = new File(webroot); + if (!webroot.contains(":") && !webroot.startsWith("/")) { + myroot = new File(System.getProperty(Application.RESNAME_APP_HOME), webroot); + } + final String homepath = myroot.getCanonicalPath(); + Server.loadLib(logger, config.getValue("lib", "") + ";" + homepath + "/lib/*;" + homepath + "/classes"); + if (server != null) server.init(config); + } + initResource(); + //prepare(); + ClassFilter servletFilter = createServletClassFilter(); + ClassFilter serviceFilter = createServiceClassFilter(); + long s = System.currentTimeMillis(); + if (servletFilter == null) { + ClassFilter.Loader.load(application.getHome(), serviceFilter); + } else { + ClassFilter.Loader.load(application.getHome(), serviceFilter, servletFilter); + } + long e = System.currentTimeMillis() - s; + logger.info(this.getClass().getSimpleName() + " load filter class in " + e + " ms"); + loadService(serviceFilter); //必须在servlet之前 + loadServlet(servletFilter); + } + + protected abstract void loadServlet(ClassFilter servletFilter) throws Exception; + + private void initResource() { + final NodeServer self = this; + //--------------------------------------------------------------------------------------------- + final ResourceFactory regFactory = application.getResourceFactory(); + factory.add(DataSource.class, (ResourceFactory rf, final Object src, Field field, final Object attachment) -> { + try { + Resource rs = field.getAnnotation(Resource.class); + if (rs == null) return; + if ((src instanceof Service) && Sncp.isRemote((Service) src)) return; + DataSource source = new DataDefaultSource(rs.name()); + application.sources.add(source); + regFactory.register(rs.name(), DataSource.class, source); + List sameGroupTransports = sncpSameGroupTransports; + List diffGroupTransports = sncpDiffGroupTransports; + try { + Field ts = src.getClass().getDeclaredField("_sameGroupTransports"); + ts.setAccessible(true); + Transport[] lts = (Transport[]) ts.get(src); + sameGroupTransports = Arrays.asList(lts); + + ts = src.getClass().getDeclaredField("_diffGroupTransports"); + ts.setAccessible(true); + lts = (Transport[]) ts.get(src); + diffGroupTransports = Arrays.asList(lts); + } catch (Exception e) { + //src 不含 MultiRun 方法 + } + if (factory.find(rs.name(), DataCacheListener.class) == null) { + Service cacheListenerService = Sncp.createLocalService(rs.name(), getExecutor(), DataCacheListenerService.class, this.sncpAddress, sncpDefaultGroups, sameGroupTransports, diffGroupTransports); + regFactory.register(rs.name(), DataCacheListener.class, cacheListenerService); + ServiceWrapper wrapper = new ServiceWrapper(DataCacheListenerService.class, cacheListenerService, rs.name(), sncpGroup, sncpDefaultGroups, null); + localServiceWrappers.add(wrapper); + if (consumer != null) consumer.accept(wrapper); + rf.inject(cacheListenerService, self); + } + field.set(src, source); + rf.inject(source, self); // 给 "datasource.nodeid" 赋值 + } catch (Exception e) { + logger.log(Level.SEVERE, "DataSource inject error", e); + } + }); + } + + private void initGroup() { + final AnyValue[] services = this.nodeConf.getAnyValues("services"); + final String[] groups = services.length < 1 ? new String[]{""} : services[0].getValue("groups", "").split(";"); + this.sncpDefaultGroups.addAll(Arrays.asList(groups)); + if (!isSNCP()) { + NodeSncpServer sncpServer = null; + for (NodeServer node : application.servers) { + if (!node.isSNCP()) continue; + if (!this.sncpDefaultGroups.contains(node.sncpGroup)) continue; + sncpServer = (NodeSncpServer) node; + break; + } + if (sncpServer == null && (groups.length == 1 && groups[0].isEmpty())) { + for (NodeServer node : application.servers) { + if (!node.isSNCP()) continue; + sncpServer = (NodeSncpServer) node; + break; + } + } + if (sncpServer != null) { + this.sncpAddress = sncpServer.getSncpAddress(); + this.sncpGroup = sncpServer.getSncpGroup(); + this.sncpDefaultGroups.clear(); + this.sncpDefaultGroups.addAll(sncpServer.sncpDefaultGroups); + this.sncpSameGroupTransports.addAll(sncpServer.sncpSameGroupTransports); + this.sncpDiffGroupTransports.addAll(sncpServer.sncpDiffGroupTransports); + return; + } + } + final Set sameGroupAddrs = application.findGlobalGroup(this.sncpGroup); + final Map> diffGroupAddrs = new HashMap<>(); + for (String groupitem : groups) { + final Set addrs = application.findGlobalGroup(groupitem); + if (addrs == null || groupitem.equals(this.sncpGroup)) continue; + diffGroupAddrs.put(groupitem, addrs); + } + if (sameGroupAddrs != null) { + sameGroupAddrs.remove(this.sncpAddress); + for (InetSocketAddress iaddr : sameGroupAddrs) { + sncpSameGroupTransports.add(loadTransport(this.sncpGroup, getNodeProtocol(), iaddr)); + } + } + diffGroupAddrs.forEach((k, v) -> sncpDiffGroupTransports.add(loadTransport(k, getNodeProtocol(), v))); + } + + @SuppressWarnings("unchecked") + protected void loadService(ClassFilter serviceFilter) throws Exception { + if (serviceFilter == null) return; + final String threadName = "[" + Thread.currentThread().getName() + "] "; + final Set> entrys = serviceFilter.getFilterEntrys(); + ResourceFactory regFactory = isSNCP() ? application.getResourceFactory() : factory; + final Set sg = application.findGlobalGroup(this.sncpGroup); + for (FilterEntry entry : entrys) { //service实现类 + final Class type = entry.getType(); + if (type.isInterface()) continue; + if (Modifier.isFinal(type.getModifiers())) continue; + if (!Modifier.isPublic(type.getModifiers())) continue; + if (Modifier.isAbstract(type.getModifiers())) continue; + if (type.getAnnotation(Ignore.class) != null) continue; + if (!isSNCP() && factory.find(entry.getName(), type) != null) continue; + final Set sameGroupAddrs = new LinkedHashSet<>(); + final Map> diffGroupAddrs = new HashMap<>(); + final HashSet groups = entry.getGroups(); + for (String g : groups) { + if (g.isEmpty()) continue; + if (g.equals(this.sncpGroup) && sg != null) sameGroupAddrs.addAll(sg); + Set set = application.findGlobalGroup(g); + if (set == null) throw new RuntimeException(type.getName() + " has illegal group (" + groups + ")"); + if (!g.equals(this.sncpGroup)) { + diffGroupAddrs.put(g, set); + } + } + List diffGroupTransports = new ArrayList<>(); + diffGroupAddrs.forEach((k, v) -> diffGroupTransports.add(loadTransport(k, server.getProtocol(), v))); + + ServiceWrapper wrapper; + if ((sameGroupAddrs.isEmpty() && diffGroupAddrs.isEmpty()) || sameGroupAddrs.contains(this.sncpAddress) || type.getAnnotation(LocalService.class) != null) { //本地模式 + sameGroupAddrs.remove(this.sncpAddress); + List sameGroupTransports = new ArrayList<>(); + for (InetSocketAddress iaddr : sameGroupAddrs) { + Set tset = new HashSet<>(); + tset.add(iaddr); + sameGroupTransports.add(loadTransport(this.sncpGroup, server.getProtocol(), tset)); + } + Service service = Sncp.createLocalService(entry.getName(), getExecutor(), type, this.sncpAddress, groups, sameGroupTransports, diffGroupTransports); + wrapper = new ServiceWrapper(type, service, this.sncpGroup, entry); + if (fine) logger.fine("[" + Thread.currentThread().getName() + "] " + service + " loaded"); + } else { + sameGroupAddrs.remove(this.sncpAddress); + StringBuilder g = new StringBuilder(); + diffGroupAddrs.forEach((k, v) -> { + if (g.length() > 0) g.append(';'); + g.append(k); + sameGroupAddrs.addAll(v); + }); + if (sameGroupAddrs.isEmpty()) throw new RuntimeException(type.getName() + " has no remote address on group (" + groups + ")"); + Service service = Sncp.createRemoteService(entry.getName(), getExecutor(), type, this.sncpAddress, groups, loadTransport(g.toString(), server.getProtocol(), sameGroupAddrs)); + wrapper = new ServiceWrapper(type, service, "", entry); + if (fine) logger.fine("[" + Thread.currentThread().getName() + "] " + service + " loaded"); + } + if (factory.find(wrapper.getName(), wrapper.getType()) == null) { + regFactory.register(wrapper.getName(), wrapper.getType(), wrapper.getService()); + if (wrapper.getService() instanceof DataSource) { + regFactory.register(wrapper.getName(), DataSource.class, wrapper.getService()); + } else if (wrapper.getService() instanceof DataCacheListener) { + regFactory.register(wrapper.getName(), DataCacheListener.class, wrapper.getService()); + } else if (wrapper.getService() instanceof DataSQLListener) { + regFactory.register(wrapper.getName(), DataSQLListener.class, wrapper.getService()); + } else if (wrapper.getService() instanceof WebSocketNode) { + regFactory.register(wrapper.getName(), WebSocketNode.class, wrapper.getService()); + } + if (wrapper.isRemote()) { + remoteServiceWrappers.add(wrapper); + } else { + localServiceWrappers.add(wrapper); + if (consumer != null) consumer.accept(wrapper); + } + } else if (isSNCP() && !entry.isAutoload()) { + throw new RuntimeException(ServiceWrapper.class.getSimpleName() + "(class:" + type.getName() + ", name:" + entry.getName() + ", group:" + groups + ") is repeat."); + } + } + application.servicecdl.countDown(); + application.servicecdl.await(); + + final StringBuilder sb = logger.isLoggable(Level.INFO) ? new StringBuilder() : null; + //---------------- inject ---------------- + new HashSet<>(localServiceWrappers).forEach(y -> { + factory.inject(y.getService(), NodeServer.this); + }); + remoteServiceWrappers.forEach(y -> { + factory.inject(y.getService(), NodeServer.this); + if (sb != null) { + sb.append(threadName).append("RemoteService(").append(y.getType()).append(':').append(y.getName()).append(") loaded").append(LINE_SEPARATOR); + } + }); + //----------------- init ----------------- + localServiceWrappers.parallelStream().forEach(y -> { + long s = System.currentTimeMillis(); + y.getService().init(y.getConf()); + long e = System.currentTimeMillis() - s; + if (e > 2 && sb != null) { + sb.append(threadName).append("LocalService(").append(y.getType()).append(':').append(y.getName()).append(") init ").append(e).append("ms").append(LINE_SEPARATOR); + } + }); + if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString()); + } + + protected Transport loadTransport(String group, String protocol, InetSocketAddress addr) { + if (addr == null) return null; + Set set = new HashSet<>(); + set.add(addr); + return loadTransport(group, protocol, set); + } + + protected Transport loadTransport(String group, String protocol, Set addrs) { + Transport transport = null; + if (!addrs.isEmpty()) { + synchronized (application.transports) { + for (Transport tran : application.transports) { + if (tran.match(addrs)) { + transport = tran; + break; + } + } + if (transport == null) { + transport = new Transport(group + "_" + application.transports.size(), protocol, application.getWatchFactory(), 32, addrs); + logger.info(transport + " created"); + application.transports.add(transport); + } + } + } + return transport; + } + + protected abstract ClassFilter createServletClassFilter(); + + protected ClassFilter createServiceClassFilter() { + return createClassFilter(this.sncpGroup, null, Service.class, Annotation.class, "services", "service"); + } + + protected ClassFilter createClassFilter(final String localGroup, Class ref, + Class inter, Class ref2, String properties, String property) { + ClassFilter cf = new ClassFilter(ref, inter, null); + if (properties == null && properties == null) return cf; + if (this.nodeConf == null) return cf; + AnyValue[] proplist = this.nodeConf.getAnyValues(properties); + if (proplist == null || proplist.length < 1) return cf; + cf = null; + for (AnyValue list : proplist) { + DefaultAnyValue prop = null; + String sc = list.getValue("groups"); + if (sc == null) sc = localGroup; + if (sc != null) { + prop = new AnyValue.DefaultAnyValue(); + prop.addValue("groups", sc); + } + ClassFilter filter = new ClassFilter(ref, inter, prop); + for (AnyValue av : list.getAnyValues(property)) { + filter.filter(av, av.getValue("value"), false); + } + if (list.getBoolValue("autoload", true)) { + String includes = list.getValue("includes", ""); + String excludes = list.getValue("excludes", ""); + filter.setIncludePatterns(includes.split(";")); + filter.setExcludePatterns(excludes.split(";")); + } else if (ref2 == null || ref2 == Annotation.class) { //service如果是autoload=false则不需要加载 + filter.setRefused(true); + } else if (ref2 != Annotation.class) { + filter.setAnnotationClass(ref2); + } + cf = (cf == null) ? filter : cf.or(filter); + } + return cf; + } + + public abstract InetSocketAddress getSocketAddress(); + + public boolean isSNCP() { + return false; + } + + public InetSocketAddress getSncpAddress() { + return sncpAddress; + } + + public String getSncpGroup() { + return sncpGroup; + } + + public String getNodeProtocol() { + return nodeProtocol; + } + + public void start() throws IOException { + server.start(); + } + + public void shutdown() throws IOException { + final StringBuilder sb = logger.isLoggable(Level.INFO) ? new StringBuilder() : null; + localServiceWrappers.forEach(y -> { + long s = System.currentTimeMillis(); + y.getService().destroy(y.getConf()); + long e = System.currentTimeMillis() - s; + if (e > 2 && sb != null) { + sb.append("LocalService(").append(y.getType()).append(':').append(y.getName()).append(") destroy ").append(e).append("ms").append(LINE_SEPARATOR); + } + }); + if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString()); + server.shutdown(); + } + +} diff --git a/src/org/redkale/boot/NodeSncpServer.java b/src/org/redkale/boot/NodeSncpServer.java new file mode 100644 index 000000000..31f1f8528 --- /dev/null +++ b/src/org/redkale/boot/NodeSncpServer.java @@ -0,0 +1,69 @@ +/* + * 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 java.net.*; +import java.util.logging.*; +import org.redkale.net.*; +import org.redkale.net.sncp.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +@NodeProtocol({"SNCP"}) +public final class NodeSncpServer extends NodeServer { + + private final SncpServer sncpServer; + + public NodeSncpServer(Application application, AnyValue serconf) { + super(application, application.getResourceFactory().createChild(), createServer(application, serconf)); + this.sncpServer = (SncpServer) this.server; + this.consumer = sncpServer == null ? null : x -> sncpServer.addService(x); + } + + private static Server createServer(Application application, AnyValue serconf) { + return new SncpServer(application.getStartTime(), application.getWatchFactory()); + } + + @Override + public InetSocketAddress getSocketAddress() { + return sncpServer == null ? null : sncpServer.getSocketAddress(); + } + + @Override + public void init(AnyValue config) throws Exception { + super.init(config); + //------------------------------------------------------------------- + if (sncpServer == null) return; //调试时server才可能为null + final StringBuilder sb = logger.isLoggable(Level.FINE) ? new StringBuilder() : null; + final String threadName = "[" + Thread.currentThread().getName() + "] "; + for (SncpServlet en : sncpServer.getSncpServlets()) { + if (sb != null) sb.append(threadName).append(" Loaded ").append(en).append(LINE_SEPARATOR); + } + if (sb != null && sb.length() > 0) logger.log(Level.FINE, sb.toString()); + } + + @Override + public boolean isSNCP() { + return true; + } + + public SncpServer getSncpServer() { + return sncpServer; + } + + @Override + protected void loadServlet(ClassFilter servletFilter) throws Exception { + } + + @Override + protected ClassFilter createServletClassFilter() { + return null; + } + +} diff --git a/src/org/redkale/convert/AnyEncoder.java b/src/org/redkale/convert/AnyEncoder.java new file mode 100644 index 000000000..72685f9a8 --- /dev/null +++ b/src/org/redkale/convert/AnyEncoder.java @@ -0,0 +1,40 @@ +/* + * 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.convert; + +import java.lang.reflect.Type; + +/** + * 对不明类型的对象进行序列化; BSON序列化时将对象的类名写入Writer,JSON则不写入。 + * + * @author zhangjx + * @param + */ +public final class AnyEncoder implements Encodeable { + + final Factory factory; + + AnyEncoder(Factory factory) { + this.factory = factory; + } + + @Override + @SuppressWarnings("unchecked") + public void convertTo(final Writer out, final T value) { + if (value == null) { + out.writeNull(); + } else { + out.wirteClassName(factory.getEntity(value.getClass())); + factory.loadEncoder(value.getClass()).convertTo(out, value); + } + } + + @Override + public Type getType() { + return Object.class; + } + +} diff --git a/src/org/redkale/convert/ArrayDecoder.java b/src/org/redkale/convert/ArrayDecoder.java new file mode 100644 index 000000000..4259402d4 --- /dev/null +++ b/src/org/redkale/convert/ArrayDecoder.java @@ -0,0 +1,79 @@ +/* + * 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.convert; + +import java.lang.reflect.*; +import java.util.*; + +/** + * 对象数组的序列化,不包含int[]、long[]这样的primitive class数组. + * 数组长度不能超过 32767。 在BSON中数组长度设定的是short,对于大于32767长度的数组传输会影响性能,所以没有采用int存储。 + * 支持一定程度的泛型。 + * + * @author zhangjx + * @param + */ +@SuppressWarnings("unchecked") +public final class ArrayDecoder implements Decodeable { + + private final Type type; + + private final Type componentType; + + private final Class componentClass; + + private final Decodeable decoder; + + public ArrayDecoder(final Factory factory, final Type type) { + this.type = type; + if (type instanceof GenericArrayType) { + Type t = ((GenericArrayType) type).getGenericComponentType(); + this.componentType = t instanceof TypeVariable ? Object.class : t; + } else if ((type instanceof Class) && ((Class) type).isArray()) { + this.componentType = ((Class) type).getComponentType(); + } else { + throw new ConvertException("(" + type + ") is not a array type"); + } + if (this.componentType instanceof ParameterizedType) { + this.componentClass = (Class) ((ParameterizedType) this.componentType).getRawType(); + } else { + this.componentClass = (Class) this.componentType; + } + factory.register(type, this); + this.decoder = factory.loadDecoder(this.componentType); + } + + @Override + public T[] convertFrom(Reader in) { + final int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) return null; + final Decodeable localdecoder = this.decoder; + final List result = new ArrayList(); + if (len == Reader.SIGN_NOLENGTH) { + while (in.hasNext()) { + result.add(localdecoder.convertFrom(in)); + } + } else { + for (int i = 0; i < len; i++) { + result.add(localdecoder.convertFrom(in)); + } + } + in.readArrayE(); + T[] rs = (T[]) Array.newInstance((Class) this.componentClass, result.size()); + return result.toArray(rs); + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "{componentType:" + this.componentType + ", decoder:" + this.decoder + "}"; + } + + @Override + public Type getType() { + return type; + } + +} diff --git a/src/org/redkale/convert/ArrayEncoder.java b/src/org/redkale/convert/ArrayEncoder.java new file mode 100644 index 000000000..31421784c --- /dev/null +++ b/src/org/redkale/convert/ArrayEncoder.java @@ -0,0 +1,75 @@ +/* + * 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.convert; + +import java.lang.reflect.*; + +/** + * 对象数组的反序列化,不包含int[]、long[]这样的primitive class数组. + * 数组长度不能超过 32767。 在BSON中数组长度设定的是short,对于大于32767长度的数组传输会影响性能,所以没有采用int存储。 + * 支持一定程度的泛型。 + * + * @author zhangjx + * @param + */ +@SuppressWarnings("unchecked") +public final class ArrayEncoder implements Encodeable { + + private final Type type; + + private final Type componentType; + + private final Encodeable anyEncoder; + + private final Encodeable encoder; + + public ArrayEncoder(final Factory factory, final Type type) { + this.type = type; + if (type instanceof GenericArrayType) { + Type t = ((GenericArrayType) type).getGenericComponentType(); + this.componentType = t instanceof TypeVariable ? Object.class : t; + } else if ((type instanceof Class) && ((Class) type).isArray()) { + this.componentType = ((Class) type).getComponentType(); + } else { + throw new ConvertException("(" + type + ") is not a array type"); + } + factory.register(type, this); + this.encoder = factory.loadEncoder(this.componentType); + this.anyEncoder = factory.getAnyEncoder(); + } + + @Override + public void convertTo(Writer out, T[] value) { + if (value == null) { + out.writeNull(); + return; + } + if (value.length == 0) { + out.writeArrayB(0); + out.writeArrayE(); + return; + } + out.writeArrayB(value.length); + final Type comp = this.componentType; + boolean first = true; + for (Object v : value) { + if (!first) out.writeArrayMark(); + ((v != null && v.getClass() == comp) ? encoder : anyEncoder).convertTo(out, v); + if (first) first = false; + } + out.writeArrayE(); + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "{componentType:" + this.componentType + ", encoder:" + this.encoder + "}"; + } + + @Override + public Type getType() { + return type; + } +} diff --git a/src/org/redkale/convert/CollectionDecoder.java b/src/org/redkale/convert/CollectionDecoder.java new file mode 100644 index 000000000..95d7613e7 --- /dev/null +++ b/src/org/redkale/convert/CollectionDecoder.java @@ -0,0 +1,69 @@ +/* + * 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.convert; + +import org.redkale.util.Creator; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; + +/** + * 对象集合的反序列化. + * 集合大小不能超过 32767。 在BSON中集合大小设定的是short,对于大于32767长度的集合传输会影响性能,所以没有采用int存储。 + * 支持一定程度的泛型。 + * + * @author zhangjx + * @param + */ +@SuppressWarnings("unchecked") +public final class CollectionDecoder implements Decodeable> { + + private final Type type; + + private final Type componentType; + + protected Creator> creator; + + private final Decodeable decoder; + + public CollectionDecoder(final Factory factory, final Type type) { + this.type = type; + if (type instanceof ParameterizedType) { + final ParameterizedType pt = (ParameterizedType) type; + this.componentType = pt.getActualTypeArguments()[0]; + this.creator = factory.loadCreator((Class) pt.getRawType()); + factory.register(type, this); + this.decoder = factory.loadDecoder(this.componentType); + } else { + throw new ConvertException("collectiondecoder not support the type (" + type + ")"); + } + } + + @Override + public Collection convertFrom(Reader in) { + final int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) return null; + final Decodeable localdecoder = this.decoder; + final Collection result = this.creator.create(); + if (len == Reader.SIGN_NOLENGTH) { + while (in.hasNext()) { + result.add(localdecoder.convertFrom(in)); + } + } else { + for (int i = 0; i < len; i++) { + result.add(localdecoder.convertFrom(in)); + } + } + in.readArrayE(); + return result; + } + + @Override + public Type getType() { + return type; + } + +} diff --git a/src/org/redkale/convert/CollectionEncoder.java b/src/org/redkale/convert/CollectionEncoder.java new file mode 100644 index 000000000..fe6206d17 --- /dev/null +++ b/src/org/redkale/convert/CollectionEncoder.java @@ -0,0 +1,65 @@ +/* + * 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.convert; + +import java.lang.reflect.*; +import java.util.Collection; + +/** + * 对象集合的序列化. + * 集合大小不能超过 32767。 在BSON中集合大小设定的是short,对于大于32767长度的集合传输会影响性能,所以没有采用int存储。 + * 支持一定程度的泛型。 + * + * @author zhangjx + * @param + */ +@SuppressWarnings("unchecked") +public final class CollectionEncoder implements Encodeable> { + + private final Type type; + + private final Encodeable encoder; + + public CollectionEncoder(final Factory factory, final Type type) { + this.type = type; + if (type instanceof ParameterizedType) { + Type t = ((ParameterizedType) type).getActualTypeArguments()[0]; + if (t instanceof TypeVariable) { + this.encoder = factory.getAnyEncoder(); + } else { + this.encoder = factory.loadEncoder(t); + } + } else { + this.encoder = factory.getAnyEncoder(); + } + } + + @Override + public void convertTo(Writer out, Collection value) { + if (value == null) { + out.writeNull(); + return; + } + if (value.isEmpty()) { + out.writeArrayB(0); + out.writeArrayE(); + return; + } + out.writeArrayB(value.size()); + boolean first = true; + for (Object v : value) { + if (!first) out.writeArrayMark(); + encoder.convertTo(out, v); + if (first) first = false; + } + out.writeArrayE(); + } + + @Override + public Type getType() { + return type; + } +} diff --git a/src/org/redkale/convert/Convert.java b/src/org/redkale/convert/Convert.java new file mode 100644 index 000000000..5e93b5f37 --- /dev/null +++ b/src/org/redkale/convert/Convert.java @@ -0,0 +1,26 @@ +/* + * 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.convert; + +/** + * 序列化操作类 + * + * @author zhangjx + * @param + * @param + */ +public abstract class Convert { + + protected final Factory factory; + + protected Convert(Factory factory) { + this.factory = factory; + } + + public Factory getFactory() { + return this.factory; + } +} diff --git a/src/org/redkale/convert/ConvertColumn.java b/src/org/redkale/convert/ConvertColumn.java new file mode 100644 index 000000000..974b0db73 --- /dev/null +++ b/src/org/redkale/convert/ConvertColumn.java @@ -0,0 +1,44 @@ +/* + * 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.convert; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +/** + * 依附在setter、getter方法、字段进行简单的配置 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({METHOD, FIELD}) +@Retention(RUNTIME) +@Repeatable(ConvertColumns.class) +public @interface ConvertColumn { + + /** + * 给字段取个别名, 只对JSON有效 + * + * @return + */ + String name() default ""; + + /** + * 解析/序列化时是否屏蔽该字段 + * + * @return + */ + boolean ignore() default false; + + /** + * 解析/序列化定制化的TYPE + * + * @return + */ + ConvertType type() default ConvertType.ALL; +} diff --git a/src/org/redkale/convert/ConvertColumnEntry.java b/src/org/redkale/convert/ConvertColumnEntry.java new file mode 100644 index 000000000..273139d26 --- /dev/null +++ b/src/org/redkale/convert/ConvertColumnEntry.java @@ -0,0 +1,67 @@ +/* + * 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.convert; + +/** + * ConvertColumn 对应的实体类 + * + * @author zhangjx + */ +public final class ConvertColumnEntry { + + private String name = ""; + + private boolean ignore; + + private ConvertType convertType; + + public ConvertColumnEntry() { + } + + public ConvertColumnEntry(ConvertColumn column) { + if (column == null) return; + this.name = column.name(); + this.ignore = column.ignore(); + this.convertType = column.type(); + } + + public ConvertColumnEntry(String name, boolean ignore) { + this.name = name; + this.ignore = ignore; + this.convertType = ConvertType.ALL; + } + + public ConvertColumnEntry(String name, boolean ignore, ConvertType convertType) { + this.name = name; + this.ignore = ignore; + this.convertType = convertType; + } + + public String name() { + return name == null ? "" : name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean ignore() { + return ignore; + } + + public void setIgnore(boolean ignore) { + this.ignore = ignore; + } + + public ConvertType type() { + return convertType == null ? ConvertType.ALL : convertType; + } + + public void setConvertType(ConvertType convertType) { + this.convertType = convertType; + } + +} diff --git a/src/org/redkale/convert/ConvertColumns.java b/src/org/redkale/convert/ConvertColumns.java new file mode 100644 index 000000000..9e4fbb4eb --- /dev/null +++ b/src/org/redkale/convert/ConvertColumns.java @@ -0,0 +1,24 @@ +/* + * 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.convert; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +/** + * ConvertColumn 的多用类 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({METHOD, FIELD}) +@Retention(RUNTIME) +public @interface ConvertColumns { + + ConvertColumn[] value(); +} diff --git a/src/org/redkale/convert/ConvertEntity.java b/src/org/redkale/convert/ConvertEntity.java new file mode 100644 index 000000000..6449df6cd --- /dev/null +++ b/src/org/redkale/convert/ConvertEntity.java @@ -0,0 +1,25 @@ +/* + * 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.convert; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 用于类名的别名, 类似javax.persistence.Table + * 该值必须是全局唯一 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({TYPE}) +@Retention(RUNTIME) +public @interface ConvertEntity { + + String value(); +} diff --git a/src/org/redkale/convert/ConvertException.java b/src/org/redkale/convert/ConvertException.java new file mode 100644 index 000000000..4fdc3f6ce --- /dev/null +++ b/src/org/redkale/convert/ConvertException.java @@ -0,0 +1,28 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.convert; + +/** + * + * @author zhangjx + */ +public class ConvertException extends RuntimeException { + + public ConvertException() { + super(); + } + + public ConvertException(String s) { + super(s); + } + + public ConvertException(String message, Throwable cause) { + super(message, cause); + } + + public ConvertException(Throwable cause) { + super(cause); + } +} diff --git a/src/org/redkale/convert/ConvertType.java b/src/org/redkale/convert/ConvertType.java new file mode 100644 index 000000000..e079007d6 --- /dev/null +++ b/src/org/redkale/convert/ConvertType.java @@ -0,0 +1,28 @@ +/* + * 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.convert; + +/** + * + * @author zhangjx + */ +public enum ConvertType { + + JSON(1), + BSON(2), + ALL(127); + + private final int value; + + private ConvertType(int v) { + this.value = v; + } + + public boolean contains(ConvertType type) { + if (type == null) return false; + return this.value >= type.value && (this.value & type.value) > 0; + } +} diff --git a/src/org/redkale/convert/DeMember.java b/src/org/redkale/convert/DeMember.java new file mode 100644 index 000000000..75de5e173 --- /dev/null +++ b/src/org/redkale/convert/DeMember.java @@ -0,0 +1,64 @@ +/* + * 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.convert; + +import org.redkale.util.Attribute; + +/** + * + * @author zhangjx + * @param + * @param + * @param + */ +@SuppressWarnings("unchecked") +public final class DeMember implements Comparable> { + + protected final Attribute attribute; + + protected Decodeable decoder; + + public DeMember(final Attribute attribute) { + this.attribute = attribute; + } + + public DeMember(Attribute attribute, Decodeable decoder) { + this(attribute); + this.decoder = decoder; + } + + public final void read(R in, T obj) { + this.attribute.set(obj, decoder.convertFrom(in)); + } + + public Attribute getAttribute() { + return this.attribute; + } + + @Override + public final int compareTo(DeMember o) { + if (o == null) return 1; + return this.attribute.field().compareTo(o.attribute.field()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof DeMember)) return false; + DeMember other = (DeMember) obj; + return compareTo(other) == 0; + } + + @Override + public int hashCode() { + return this.attribute.field().hashCode(); + } + + @Override + public String toString() { + return "DeMember{" + "attribute=" + attribute.field() + ", decoder=" + decoder + '}'; + } +} diff --git a/src/org/redkale/convert/Decodeable.java b/src/org/redkale/convert/Decodeable.java new file mode 100644 index 000000000..a3c02cec1 --- /dev/null +++ b/src/org/redkale/convert/Decodeable.java @@ -0,0 +1,27 @@ +/* + * 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.convert; + +import java.lang.reflect.Type; + +/** + * + * @author zhangjx + * @param + * @param + */ +public interface Decodeable { + + public T convertFrom(final R in); + + /** + * 泛型映射接口 + * + * @return + */ + public Type getType(); + +} diff --git a/src/org/redkale/convert/EnMember.java b/src/org/redkale/convert/EnMember.java new file mode 100644 index 000000000..5d2c24b57 --- /dev/null +++ b/src/org/redkale/convert/EnMember.java @@ -0,0 +1,76 @@ +/* + * 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.convert; + +import org.redkale.util.Attribute; + +/** + * + * @author zhangjx + * @param + * @param + * @param + */ +@SuppressWarnings("unchecked") +public final class EnMember implements Comparable> { + + private final Attribute attribute; + + final Encodeable encoder; + + private final boolean istring; + + //private final boolean isnumber; + private final boolean isbool; + + public EnMember(Attribute attribute, Encodeable encoder) { + this.attribute = attribute; + this.encoder = encoder; + Class t = attribute.type(); + this.istring = CharSequence.class.isAssignableFrom(t); + this.isbool = t == Boolean.class || t == boolean.class; + //this.isnumber = Number.class.isAssignableFrom(t) || (!this.isbool && t.isPrimitive()); + } + + public boolean write(final W out, final boolean comma, final T obj) { + F value = attribute.get(obj); + if (value == null) return comma; + if (out.isTiny()) { + if (istring) { + if (((CharSequence) value).length() == 0) return comma; + } else if (isbool) { + if (!((Boolean) value)) return comma; + } + } + out.writeField(comma, attribute); + encoder.convertTo(out, value); + return true; + } + + @Override + public final int compareTo(EnMember o) { + if (o == null) return 1; + return this.attribute.field().compareTo(o.attribute.field()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof EnMember)) return false; + EnMember other = (EnMember) obj; + return compareTo(other) == 0; + } + + @Override + public int hashCode() { + return this.attribute.field().hashCode(); + } + + @Override + public String toString() { + return "EnMember{" + "attribute=" + attribute.field() + ", encoder=" + encoder + '}'; + } +} diff --git a/src/org/redkale/convert/Encodeable.java b/src/org/redkale/convert/Encodeable.java new file mode 100644 index 000000000..9d5f1eb29 --- /dev/null +++ b/src/org/redkale/convert/Encodeable.java @@ -0,0 +1,27 @@ +/* + * 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.convert; + +import java.lang.reflect.Type; + +/** + * + * @author zhangjx + * @param + * @param + */ +public interface Encodeable { + + public void convertTo(final W out, T value); + + /** + * 泛型映射接口 + * + * @return + */ + public Type getType(); + +} diff --git a/src/org/redkale/convert/Factory.java b/src/org/redkale/convert/Factory.java new file mode 100644 index 000000000..4b0f07231 --- /dev/null +++ b/src/org/redkale/convert/Factory.java @@ -0,0 +1,385 @@ +/* + * 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.convert; + +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; +import java.lang.reflect.*; +import java.math.BigInteger; +import java.net.*; +import static org.redkale.convert.ext.InetAddressSimpledCoder.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.regex.*; +import org.redkale.convert.ext.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + * @param + * @param + */ +@SuppressWarnings("unchecked") +public abstract class Factory { + + private final Factory parent; + + protected Convert convert; + + protected boolean tiny; + + private final Encodeable anyEncoder = new AnyEncoder(this); + + //----------------------------------------------------------------------------------- + private final HashedMap creators = new HashedMap(); + + private final Map entitys = new ConcurrentHashMap(); + + private final HashedMap> decoders = new HashedMap(); + + private final HashedMap> encoders = new HashedMap(); + + private final HashMap columnEntrys = new HashMap(); + + private final Set skipIgnores = new HashSet(); + + private boolean skipAllIgnore = false; + + protected Factory(Factory parent, boolean tiny) { + this.tiny = tiny; + this.parent = parent; + if (parent == null) { + //--------------------------------------------------------- + this.register(boolean.class, BoolSimpledCoder.instance); + this.register(Boolean.class, BoolSimpledCoder.instance); + + this.register(byte.class, ByteSimpledCoder.instance); + this.register(Byte.class, ByteSimpledCoder.instance); + + this.register(short.class, ShortSimpledCoder.instance); + this.register(Short.class, ShortSimpledCoder.instance); + + this.register(char.class, CharSimpledCoder.instance); + this.register(Character.class, CharSimpledCoder.instance); + + this.register(int.class, IntSimpledCoder.instance); + this.register(Integer.class, IntSimpledCoder.instance); + + this.register(long.class, LongSimpledCoder.instance); + this.register(Long.class, LongSimpledCoder.instance); + + this.register(float.class, FloatSimpledCoder.instance); + this.register(Float.class, FloatSimpledCoder.instance); + + this.register(double.class, DoubleSimpledCoder.instance); + this.register(Double.class, DoubleSimpledCoder.instance); + + this.register(Number.class, NumberSimpledCoder.instance); + this.register(String.class, StringSimpledCoder.instance); + this.register(java.util.Date.class, DateSimpledCoder.instance); + this.register(BigInteger.class, BigIntegerSimpledCoder.instance); + this.register(InetAddress.class, InetAddressSimpledCoder.instance); + this.register(DLong.class, DLongSimpledCoder.instance); + this.register(Class.class, TypeSimpledCoder.instance); + this.register(InetSocketAddress.class, InetSocketAddressSimpledCoder.instance); + this.register(Pattern.class, PatternSimpledCoder.instance); + //--------------------------------------------------------- + this.register(boolean[].class, BoolArraySimpledCoder.instance); + this.register(byte[].class, ByteArraySimpledCoder.instance); + this.register(short[].class, ShortArraySimpledCoder.instance); + this.register(char[].class, CharArraySimpledCoder.instance); + this.register(int[].class, IntArraySimpledCoder.instance); + this.register(long[].class, LongArraySimpledCoder.instance); + this.register(float[].class, FloatArraySimpledCoder.instance); + this.register(double[].class, DoubleArraySimpledCoder.instance); + this.register(String[].class, StringArraySimpledCoder.instance); + //--------------------------------------------------------- + } + } + + public Factory parent() { + return this.parent; + } + + public abstract ConvertType getConvertType(); + + public abstract boolean isReversible(); + + public abstract Factory createChild(); + + public abstract Factory createChild(boolean tiny); + + public Convert getConvert() { + return convert; + } + + public void setTiny(boolean tiny) { + this.tiny = tiny; + } + + public ConvertColumnEntry findRef(AccessibleObject field) { + if (field == null) return null; + ConvertColumnEntry en = this.columnEntrys.get(field); + if (en != null) return en; + final ConvertType ct = this.getConvertType(); + for (ConvertColumn ref : field.getAnnotationsByType(ConvertColumn.class)) { + if (ref.type().contains(ct)) { + ConvertColumnEntry entry = new ConvertColumnEntry(ref); + if (skipAllIgnore) { + entry.setIgnore(false); + return entry; + } + if (skipIgnores.isEmpty()) return entry; + if (skipIgnores.contains(((Member) field).getDeclaringClass())) entry.setIgnore(false); + return entry; + } + } + return null; + } + + final String getEntity(Class clazz) { + ConvertEntity ce = (ConvertEntity) clazz.getAnnotation(ConvertEntity.class); + if (ce != null && findEntity(ce.value()) == null) entitys.put(ce.value(), clazz); + return ce == null ? clazz.getName() : ce.value(); + } + + private Class findEntity(String name) { + Class clazz = entitys.get(name); + return parent == null ? clazz : parent.findEntity(name); + } + + final Class getEntity(String name) { + Class clazz = findEntity(name); + try { + return clazz == null ? Class.forName(name) : clazz; + } catch (Exception ex) { + throw new ConvertException("convert entity is " + name, ex); + } + } + + /** + * 使所有类的所有被声明为ConvertColumn.ignore = true 的字段或方法变为ConvertColumn.ignore = false + *

+ * @param skipIgnore + */ + public final void registerSkipAllIgnore(final boolean skipIgnore) { + this.skipAllIgnore = skipIgnore; + } + + /** + * 使该类所有被声明为ConvertColumn.ignore = true 的字段或方法变为ConvertColumn.ignore = false + *

+ * @param type + */ + public final void registerSkipIgnore(final Class type) { + skipIgnores.add(type); + } + + public final void register(final Class type, boolean ignore, String... columns) { + for (String column : columns) { + register(type, column, new ConvertColumnEntry(column, ignore)); + } + } + + public final boolean register(final Class type, String column, ConvertColumnEntry entry) { + if (type == null || column == null || entry == null) return false; + try { + final Field field = type.getDeclaredField(column); + String get = "get"; + if (field.getType() == boolean.class || field.getType() == Boolean.class) get = "is"; + char[] cols = column.toCharArray(); + cols[0] = Character.toUpperCase(cols[0]); + String col2 = new String(cols); + try { + register(type.getMethod(get + col2), entry); + } catch (Exception ex) { + } + try { + register(type.getMethod("set" + col2, field.getType()), entry); + } catch (Exception ex) { + } + return register(field, entry); + } catch (Exception e) { + return false; + } + } + + public final boolean register(final AccessibleObject field, final ConvertColumnEntry entry) { + if (field == null || entry == null) return false; + this.columnEntrys.put(field, entry); + return true; + } + + public final void register(final Class clazz, final Creator creator) { + creators.put(clazz, creator); + } + + public final Creator findCreator(Class type) { + Creator creator = creators.get(type); + if (creator != null) return creator; + return this.parent == null ? null : this.parent.findCreator(type); + } + + public final Creator loadCreator(Class type) { + Creator result = findCreator(type); + if (result == null) { + result = Creator.create(type); + creators.put(type, result); + } + return result; + } + + //---------------------------------------------------------------------- + public final Encodeable getAnyEncoder() { + return (Encodeable) anyEncoder; + } + + public final void register(final Type clazz, final SimpledCoder coder) { + decoders.put(clazz, coder); + encoders.put(clazz, coder); + } + + public final void register(final Type clazz, final Decodeable decoder) { + decoders.put(clazz, decoder); + } + + public final void register(final Type clazz, final Encodeable printer) { + encoders.put(clazz, printer); + } + + public final Decodeable findDecoder(final Type type) { + Decodeable rs = (Decodeable) decoders.get(type); + if (rs != null) return rs; + return this.parent == null ? null : this.parent.findDecoder(type); + } + + public final Encodeable findEncoder(final Type type) { + Encodeable rs = (Encodeable) encoders.get(type); + if (rs != null) return rs; + return this.parent == null ? null : this.parent.findEncoder(type); + } + + public final Decodeable loadDecoder(final Type type) { + Decodeable decoder = findDecoder(type); + if (decoder != null) return decoder; + if (type instanceof GenericArrayType) return new ArrayDecoder(this, type); + Class clazz; + if (type instanceof ParameterizedType) { + final ParameterizedType pts = (ParameterizedType) type; + clazz = (Class) (pts).getRawType(); + } else if (type instanceof Class) { + clazz = (Class) type; + } else { + throw new ConvertException("not support the type (" + type + ")"); + } + decoder = findDecoder(clazz); + if (decoder != null) return decoder; + return createDecoder(type, clazz); + } + + public final Decodeable createDecoder(final Type type) { + Class clazz; + if (type instanceof ParameterizedType) { + final ParameterizedType pts = (ParameterizedType) type; + clazz = (Class) (pts).getRawType(); + } else if (type instanceof Class) { + clazz = (Class) type; + } else { + throw new ConvertException("not support the type (" + type + ")"); + } + return createDecoder(type, clazz); + } + + private Decodeable createDecoder(final Type type, final Class clazz) { + Decodeable decoder = null; + ObjectDecoder od = null; + if (clazz.isEnum()) { + decoder = new EnumSimpledCoder(clazz); + } else if (clazz.isArray()) { + decoder = new ArrayDecoder(this, type); + } else if (Collection.class.isAssignableFrom(clazz)) { + decoder = new CollectionDecoder(this, type); + } else if (Map.class.isAssignableFrom(clazz)) { + decoder = new MapDecoder(this, type); + } else if (clazz == Object.class) { + od = new ObjectDecoder(type); + decoder = od; + } else if (!clazz.getName().startsWith("java.")) { + od = new ObjectDecoder(type); + decoder = od; + } + if (decoder == null) throw new ConvertException("not support the type (" + type + ")"); + register(type, decoder); + if (od != null) od.init(this); + return decoder; + } + + public final Encodeable loadEncoder(final Type type) { + Encodeable encoder = findEncoder(type); + if (encoder != null) return encoder; + if (type instanceof GenericArrayType) return new ArrayEncoder(this, type); + Class clazz; + if (type instanceof ParameterizedType) { + final ParameterizedType pts = (ParameterizedType) type; + clazz = (Class) (pts).getRawType(); + } else if (type instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) type; + Type t = Object.class; + if (tv.getBounds().length == 1) { + t = tv.getBounds()[0]; + } + if (!(t instanceof Class)) t = Object.class; + clazz = (Class) t; + } else if (type instanceof Class) { + clazz = (Class) type; + } else { + throw new ConvertException("not support the type (" + type + ")"); + } + encoder = findEncoder(clazz); + if (encoder != null) return encoder; + return createEncoder(type, clazz); + } + + public final Encodeable createEncoder(final Type type) { + Class clazz; + if (type instanceof ParameterizedType) { + final ParameterizedType pts = (ParameterizedType) type; + clazz = (Class) (pts).getRawType(); + } else if (type instanceof Class) { + clazz = (Class) type; + } else { + throw new ConvertException("not support the type (" + type + ")"); + } + return createEncoder(type, clazz); + } + + private Encodeable createEncoder(final Type type, final Class clazz) { + Encodeable encoder = null; + ObjectEncoder oe = null; + if (clazz.isEnum()) { + encoder = new EnumSimpledCoder(clazz); + } else if (clazz.isArray()) { + encoder = new ArrayEncoder(this, type); + } else if (Collection.class.isAssignableFrom(clazz)) { + encoder = new CollectionEncoder(this, type); + } else if (Map.class.isAssignableFrom(clazz)) { + encoder = new MapEncoder(this, type); + } else if (clazz == Object.class) { + return (Encodeable) this.anyEncoder; + } else if (!clazz.getName().startsWith("java.")) { + oe = new ObjectEncoder(type); + encoder = oe; + } + if (encoder == null) throw new ConvertException("not support the type (" + type + ")"); + register(type, encoder); + if (oe != null) oe.init(this); + return encoder; + + } + +} diff --git a/src/org/redkale/convert/HashedMap.java b/src/org/redkale/convert/HashedMap.java new file mode 100644 index 000000000..f30dd9385 --- /dev/null +++ b/src/org/redkale/convert/HashedMap.java @@ -0,0 +1,70 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.convert; + +import java.lang.reflect.*; + +/** + * 只增不减的伪Map类 + * + * @author zhangjx + * @param + * @param + */ +@SuppressWarnings("unchecked") +public final class HashedMap { + + protected final transient Entry[] table; + + public HashedMap() { + this(128); + } + + public HashedMap(int initCapacity) { + this.table = new Entry[Math.max(initCapacity, 16)]; + } + + public final V get(final K key) { + final K k = key; + final Entry[] data = this.table; + Entry entry = data[k.hashCode() & (data.length - 1)]; + while (entry != null) { + if (k == entry.key) return entry.value; + entry = entry.next; + } + return null; + } + + public final V put(K key, V value) { + final K k = key; + final Entry[] data = this.table; + final int index = k.hashCode() & (data.length - 1); + Entry entry = data[index]; + while (entry != null) { + if (k == entry.key) { + entry.value = value; + return entry.value; + } + entry = entry.next; + } + data[index] = new Entry(key, value, data[index]); + return null; + } + + protected static final class Entry { + + protected V value; + + protected final K key; + + protected final Entry next; + + protected Entry(K key, V value, Entry next) { + this.key = key; + this.value = value; + this.next = next; + } + } +} diff --git a/src/org/redkale/convert/MapDecoder.java b/src/org/redkale/convert/MapDecoder.java new file mode 100644 index 000000000..8dc6bd6e6 --- /dev/null +++ b/src/org/redkale/convert/MapDecoder.java @@ -0,0 +1,78 @@ +/* + * 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.convert; + +import org.redkale.util.Creator; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * + * @author zhangjx + * @param + * @param + */ +@SuppressWarnings("unchecked") +public final class MapDecoder implements Decodeable> { + + private final Type type; + + private final Type keyType; + + private final Type valueType; + + protected Creator> creator; + + private final Decodeable keyDecoder; + + private final Decodeable valueDecoder; + + public MapDecoder(final Factory factory, final Type type) { + this.type = type; + if (type instanceof ParameterizedType) { + final ParameterizedType pt = (ParameterizedType) type; + this.keyType = pt.getActualTypeArguments()[0]; + this.valueType = pt.getActualTypeArguments()[1]; + this.creator = factory.loadCreator((Class) pt.getRawType()); + factory.register(type, this); + this.keyDecoder = factory.loadDecoder(this.keyType); + this.valueDecoder = factory.loadDecoder(this.valueType); + } else { + throw new ConvertException("mapdecoder not support the type (" + type + ")"); + } + } + + @Override + public Map convertFrom(Reader in) { + final int len = in.readMapB(); + if (len == Reader.SIGN_NULL) return null; + final Map result = this.creator.create(); + if (len == Reader.SIGN_NOLENGTH) { + while (in.hasNext()) { + K key = keyDecoder.convertFrom(in); + in.skipBlank(); + V value = valueDecoder.convertFrom(in); + result.put(key, value); + } + } else { + for (int i = 0; i < len; i++) { + K key = keyDecoder.convertFrom(in); + in.skipBlank(); + V value = valueDecoder.convertFrom(in); + result.put(key, value); + } + } + in.readMapE(); + return result; + } + + @Override + public Type getType() { + return this.type; + } + +} diff --git a/src/org/redkale/convert/MapEncoder.java b/src/org/redkale/convert/MapEncoder.java new file mode 100644 index 000000000..c8ba007e4 --- /dev/null +++ b/src/org/redkale/convert/MapEncoder.java @@ -0,0 +1,62 @@ +/* + * 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.convert; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * + * @author zhangjx + * @param + * @param + */ +@SuppressWarnings("unchecked") +public final class MapEncoder implements Encodeable> { + + private final Type type; + + private final Encodeable keyencoder; + + private final Encodeable valencoder; + + public MapEncoder(final Factory factory, final Type type) { + this.type = type; + if (type instanceof ParameterizedType) { + final Type[] pt = ((ParameterizedType) type).getActualTypeArguments(); + this.keyencoder = factory.loadEncoder(pt[0]); + this.valencoder = factory.loadEncoder(pt[1]); + } else { + this.keyencoder = factory.getAnyEncoder(); + this.valencoder = factory.getAnyEncoder(); + } + } + + @Override + public void convertTo(Writer out, Map value) { + final Map values = value; + if (values == null) { + out.writeNull(); + return; + } + out.writeMapB(values.size()); + boolean first = true; + for (Map.Entry en : values.entrySet()) { + if (!first) out.writeArrayMark(); + this.keyencoder.convertTo(out, en.getKey()); + out.writeMapMark(); + this.valencoder.convertTo(out, en.getValue()); + if (first) first = false; + } + out.writeMapE(); + } + + @Override + public Type getType() { + return type; + } +} diff --git a/src/org/redkale/convert/ObjectDecoder.java b/src/org/redkale/convert/ObjectDecoder.java new file mode 100644 index 000000000..ebe16a2e0 --- /dev/null +++ b/src/org/redkale/convert/ObjectDecoder.java @@ -0,0 +1,157 @@ +/* + * 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.convert; + +import static org.redkale.convert.ObjectEncoder.TYPEZERO; +import org.redkale.util.Creator; +import java.lang.reflect.*; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * + * @author zhangjx + * @param + * @param + */ +@SuppressWarnings("unchecked") +public final class ObjectDecoder implements Decodeable { + + protected final Type type; + + protected final Class typeClass; + + protected Creator creator; + + protected DeMember[] members; + + protected Factory factory; + + private boolean inited = false; + + private final Object lock = new Object(); + + protected ObjectDecoder(Type type) { + this.type = ((type instanceof Class) && ((Class) type).isInterface()) ? Object.class : type; + if (type instanceof ParameterizedType) { + final ParameterizedType pt = (ParameterizedType) type; + this.typeClass = (Class) pt.getRawType(); + } else { + this.typeClass = (Class) type; + } + this.members = new DeMember[0]; + } + + public void init(final Factory factory) { + this.factory = factory; + try { + if (type == Object.class) return; + + Class clazz = null; + if (type instanceof ParameterizedType) { + final ParameterizedType pts = (ParameterizedType) type; + clazz = (Class) (pts).getRawType(); + } else if (!(type instanceof Class)) { + throw new ConvertException("[" + type + "] is no a class"); + } else { + clazz = (Class) type; + } + final Type[] virGenericTypes = this.typeClass.getTypeParameters(); + final Type[] realGenericTypes = (type instanceof ParameterizedType) ? ((ParameterizedType) type).getActualTypeArguments() : TYPEZERO; + this.creator = factory.loadCreator(clazz); + final Set list = new HashSet(); + try { + ConvertColumnEntry ref; + for (final Field field : clazz.getFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + ref = factory.findRef(field); + if (ref != null && ref.ignore()) continue; + Type t = ObjectEncoder.makeGenericType(field.getGenericType(), virGenericTypes, realGenericTypes); + list.add(new DeMember(ObjectEncoder.createAttribute(factory, clazz, field, null, null), factory.loadDecoder(t))); + } + final boolean reversible = factory.isReversible(); + for (final Method method : clazz.getMethods()) { + if (Modifier.isStatic(method.getModifiers())) continue; + if (Modifier.isAbstract(method.getModifiers())) continue; + if (method.isSynthetic()) continue; + if (method.getName().length() < 4) continue; + if (!method.getName().startsWith("set")) continue; + if (method.getParameterTypes().length != 1) continue; + if (method.getReturnType() != void.class) continue; + if (reversible) { + boolean is = method.getParameterTypes()[0] == boolean.class || method.getParameterTypes()[0] == Boolean.class; + try { + clazz.getMethod(method.getName().replaceFirst("set", is ? "is" : "get")); + } catch (Exception e) { + continue; + } + } + ref = factory.findRef(method); + if (ref != null && ref.ignore()) continue; + Type t = ObjectEncoder.makeGenericType(method.getGenericParameterTypes()[0], virGenericTypes, realGenericTypes); + list.add(new DeMember(ObjectEncoder.createAttribute(factory, clazz, null, null, method), factory.loadDecoder(t))); + } + this.members = list.toArray(new DeMember[list.size()]); + Arrays.sort(this.members); + } catch (Exception ex) { + throw new ConvertException(ex); + } + } finally { + inited = true; + synchronized (lock) { + lock.notifyAll(); + } + } + } + + /** + * 对象格式: [0x1][short字段个数][字段名][字段值]...[0x2] + * + * @param in + * @return + */ + @Override + public final T convertFrom(final R in) { + final String clazz = in.readClassName(); + if (clazz != null && !clazz.isEmpty()) return (T) factory.loadDecoder(factory.getEntity(clazz)).convertFrom(in); + if (in.readObjectB() == Reader.SIGN_NULL) return null; + if (!this.inited) { + synchronized (lock) { + try { + lock.wait(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + final T result = this.creator.create(); + final AtomicInteger index = new AtomicInteger(); + while (in.hasNext()) { + DeMember member = in.readField(index, members); + in.skipBlank(); + if (member == null) { + in.skipValue(); //跳过该属性的值 + } else { + member.read(in, result); + } + index.incrementAndGet(); + } + in.readObjectE(); + return result; + } + + @Override + public final Type getType() { + return this.type; + } + + @Override + public String toString() { + return "ObjectDecoder{" + "type=" + type + ", members=" + Arrays.toString(members) + '}'; + } +} diff --git a/src/org/redkale/convert/ObjectEncoder.java b/src/org/redkale/convert/ObjectEncoder.java new file mode 100644 index 000000000..cc1476783 --- /dev/null +++ b/src/org/redkale/convert/ObjectEncoder.java @@ -0,0 +1,233 @@ +/* + * 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.convert; + +import org.redkale.util.Attribute; +import java.lang.reflect.*; +import java.util.*; + +/** + * + * @author zhangjx + * @param + * @param + */ +@SuppressWarnings("unchecked") +public final class ObjectEncoder implements Encodeable { + + static final Type[] TYPEZERO = new Type[0]; + + protected final Type type; + + protected final Class typeClass; + + protected EnMember[] members; + + protected Factory factory; + + private boolean inited = false; + + private final Object lock = new Object(); + + protected ObjectEncoder(Type type) { + this.type = type; + if (type instanceof ParameterizedType) { + final ParameterizedType pt = (ParameterizedType) type; + this.typeClass = (Class) pt.getRawType(); + } else { + this.typeClass = (Class) type; + } + this.members = new EnMember[0]; + } + + static Type makeGenericType(final Type type, final Type[] virGenericTypes, final Type[] realGenericTypes) { + if (type instanceof Class) { + return type; + } else if (type instanceof ParameterizedType) { + final ParameterizedType pt = (ParameterizedType) type; + Type[] paramTypes = pt.getActualTypeArguments(); + final Type[] newTypes = new Type[paramTypes.length]; + int count = 0; + for (int i = 0; i < newTypes.length; i++) { + newTypes[i] = makeGenericType(paramTypes[i], virGenericTypes, realGenericTypes); + if (paramTypes[i] == newTypes[i]) count++; + } + if (count == paramTypes.length) return pt; + return new ParameterizedType() { + + @Override + public Type[] getActualTypeArguments() { + return newTypes; + } + + @Override + public Type getRawType() { + return pt.getRawType(); + } + + @Override + public Type getOwnerType() { + return pt.getOwnerType(); + } + + }; + } + if (realGenericTypes == null) return type; + if (type instanceof WildcardType) { + final WildcardType wt = (WildcardType) type; + for (Type f : wt.getUpperBounds()) { + for (int i = 0; i < virGenericTypes.length; i++) { + if (virGenericTypes[i] == f) return realGenericTypes.length == 0 ? Object.class : realGenericTypes[i]; + } + } + } else if (type instanceof TypeVariable) { + for (int i = 0; i < virGenericTypes.length; i++) { + if (virGenericTypes[i] == type) return i >= realGenericTypes.length ? Object.class : realGenericTypes[i]; + } + } + return type; + } + + private static String readGetSetFieldName(Method method) { + if (method == null) return null; + String fname = method.getName(); + if (!fname.startsWith("is") && !fname.startsWith("get") && !fname.startsWith("set")) return fname; + fname = fname.substring(fname.startsWith("is") ? 2 : 3); + if (fname.length() > 1 && !(fname.charAt(1) >= 'A' && fname.charAt(1) <= 'Z')) { + fname = Character.toLowerCase(fname.charAt(0)) + fname.substring(1); + } else if (fname.length() == 1) { + fname = "" + Character.toLowerCase(fname.charAt(0)); + } + return fname; + } + + static Attribute createAttribute(final Factory factory, Class clazz, final Field field, final Method getter, final Method setter) { + String fieldalias = null; + if (field != null) { // public field + ConvertColumnEntry ref = factory.findRef(field); + fieldalias = ref == null || ref.name().isEmpty() ? field.getName() : ref.name(); + } else if (getter != null) { + ConvertColumnEntry ref = factory.findRef(getter); + String mfieldname = readGetSetFieldName(getter); + if (ref == null) { + try { + ref = factory.findRef(clazz.getDeclaredField(mfieldname)); + } catch (Exception e) { + } + } + fieldalias = ref == null || ref.name().isEmpty() ? mfieldname : ref.name(); + } else { // setter != null + ConvertColumnEntry ref = factory.findRef(setter); + String mfieldname = readGetSetFieldName(setter); + if (ref == null) { + try { + ref = factory.findRef(clazz.getDeclaredField(mfieldname)); + } catch (Exception e) { + } + } + fieldalias = ref == null || ref.name().isEmpty() ? mfieldname : ref.name(); + } + return Attribute.create(clazz, fieldalias, field, getter, setter); + } + + public void init(final Factory factory) { + this.factory = factory; + try { + if (type == Object.class) return; + //if (!(type instanceof Class)) throw new ConvertException("[" + type + "] is no a class"); + final Class clazz = this.typeClass; + final Set list = new HashSet(); + final Type[] virGenericTypes = this.typeClass.getTypeParameters(); + final Type[] realGenericTypes = (type instanceof ParameterizedType) ? ((ParameterizedType) type).getActualTypeArguments() : null; + if (realGenericTypes != null) { + // println(type + "," + Arrays.toString(virGenericTypes) + ", " + Arrays.toString(realGenericTypes)); + } + try { + ConvertColumnEntry ref; + for (final Field field : clazz.getFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + ref = factory.findRef(field); + if (ref != null && ref.ignore()) continue; + Type t = makeGenericType(field.getGenericType(), virGenericTypes, realGenericTypes); + list.add(new EnMember(createAttribute(factory, clazz, field, null, null), factory.loadEncoder(t))); + } + final boolean reversible = factory.isReversible(); + for (final Method method : clazz.getMethods()) { + if (Modifier.isStatic(method.getModifiers())) continue; + if (Modifier.isAbstract(method.getModifiers())) continue; + if (method.isSynthetic()) continue; + if (method.getName().length() < 3) continue; + if (method.getName().equals("getClass")) continue; + if (!method.getName().startsWith("is") && !method.getName().startsWith("get")) continue; + if (method.getParameterTypes().length != 0) continue; + if (method.getReturnType() == void.class) continue; + if (reversible) { + boolean is = method.getName().startsWith("is"); + try { + clazz.getMethod(method.getName().replaceFirst(is ? "is" : "get", "set"), method.getReturnType()); + } catch (Exception e) { + continue; + } + } + ref = factory.findRef(method); + if (ref != null && ref.ignore()) continue; + Type t = makeGenericType(method.getGenericReturnType(), virGenericTypes, realGenericTypes); + list.add(new EnMember(createAttribute(factory, clazz, null, method, null), factory.loadEncoder(t))); + } + this.members = list.toArray(new EnMember[list.size()]); + Arrays.sort(this.members); + + } catch (Exception ex) { + throw new ConvertException(ex); + } + } finally { + inited = true; + synchronized (lock) { + lock.notifyAll(); + } + } + } + + @Override + public final void convertTo(W out, T value) { + if (value == null) { + out.wirteClassName(null); + out.writeNull(); + return; + } + if (!this.inited) { + synchronized (lock) { + try { + lock.wait(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + if (value != null && value.getClass() != this.typeClass) { + final Class clz = value.getClass(); + out.wirteClassName(factory.getEntity(clz)); + factory.loadEncoder(clz).convertTo(out, value); + return; + } + out.writeObjectB(members.length, value); + boolean comma = false; + for (EnMember member : members) { + comma = member.write(out, comma, value); + } + out.writeObjectE(value); + } + + @Override + public final Type getType() { + return this.type; + } + + @Override + public String toString() { + return "ObjectEncoder{" + "type=" + type + ", members=" + Arrays.toString(members) + '}'; + } +} diff --git a/src/org/redkale/convert/Reader.java b/src/org/redkale/convert/Reader.java new file mode 100644 index 000000000..743474ac4 --- /dev/null +++ b/src/org/redkale/convert/Reader.java @@ -0,0 +1,112 @@ +/* + * 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.convert; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * + * @author zhangjx + */ +public interface Reader { + + public static final short SIGN_NULL = -1; + + public static final short SIGN_NOLENGTH = -2; + + /** + * 是否还存在下个元素或字段 + * + * @return + */ + public boolean hasNext(); + + /** + * 跳过值(不包含值前面的字段) + */ + public void skipValue(); + + /** + * /跳过字段与值之间的多余内容, json就是跳过:符, map跳过: + */ + public void skipBlank(); + + /** + * 读取对象的开头 返回字段数 + * + * @return + */ + public int readObjectB(); + + /** + * 读取对象的尾端 + * + */ + public void readObjectE(); + + /** + * 读取数组的开头并返回数组的长度 + * + * @return + */ + public int readArrayB(); + + /** + * 读取数组的尾端 + * + */ + public void readArrayE(); + + /** + * 读取map的开头并返回map的size + * + * @return + */ + public int readMapB(); + + /** + * 读取数组的尾端 + * + */ + public void readMapE(); + + /** + * 根据字段读取字段对应的DeMember + * + * @param index + * @param members + * @return + */ + public DeMember readField(final AtomicInteger index, final DeMember[] members); + + public boolean readBoolean(); + + public byte readByte(); + + public char readChar(); + + public short readShort(); + + public int readInt(); + + public long readLong(); + + public float readFloat(); + + public double readDouble(); + + /** + * 读取无转义字符长度不超过255的字符串, 例如枚举值、字段名、类名字符串等 + * + * @return + */ + public String readSmallString(); + + public String readClassName(); + + public String readString(); + +} diff --git a/src/org/redkale/convert/SimpledCoder.java b/src/org/redkale/convert/SimpledCoder.java new file mode 100644 index 000000000..fef1ec2cc --- /dev/null +++ b/src/org/redkale/convert/SimpledCoder.java @@ -0,0 +1,42 @@ +/* + * 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.convert; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * + * @author zhangjx + * @param + * @param + * @param + */ +public abstract class SimpledCoder implements Decodeable, Encodeable { + + private Type type; + + @Override + public abstract void convertTo(final W out, final T value); + + @Override + public abstract T convertFrom(final R in); + + @Override + @SuppressWarnings("unchecked") + public Class getType() { + if (type == null) { + Type[] ts = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments(); + type = ts[ts.length - 1]; + } + return (Class) type; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } +} diff --git a/src/org/redkale/convert/Writer.java b/src/org/redkale/convert/Writer.java new file mode 100644 index 000000000..4da8710b8 --- /dev/null +++ b/src/org/redkale/convert/Writer.java @@ -0,0 +1,120 @@ +/* + * 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.convert; + +import org.redkale.util.Attribute; + +/** + * + * @author zhangjx + */ +public interface Writer { + + /** + * 当tiny=true时, 字符串为空、boolean为false的字段值都会被跳过, 不会输出。 + *

+ * @return + */ + public boolean isTiny(); + + /** + * 输出null值 + */ + public void writeNull(); + + /** + * + * @param clazz + */ + public void wirteClassName(String clazz); + + /** + * 输出一个对象前的操作 + * + * @param fieldCount 字段个数 + * + * @param obj + */ + public void writeObjectB(int fieldCount, Object obj); + + /** + * 输出一个对象后的操作 + * + * @param obj + */ + public void writeObjectE(Object obj); + + /** + * 输出一个数组前的操作 + * + * @param size 数组长度 + */ + public void writeArrayB(int size); + + /** + * 输出数组元素间的间隔符 + * + */ + public void writeArrayMark(); + + /** + * 输出一个数组后的操作 + * + */ + public void writeArrayE(); + + /** + * 输出一个Map前的操作 + * + * @param size map大小 + */ + public void writeMapB(int size); + + /** + * 输出一个Map中key与value间的间隔符 + * + */ + public void writeMapMark(); + + /** + * 输出一个Map后的操作 + * + */ + public void writeMapE(); + + /** + * 输出一个字段 + * + * @param comma 是否非第一个字段 + * @param attribute + */ + public void writeField(boolean comma, Attribute attribute); + + public void writeBoolean(boolean value); + + public void writeByte(byte value); + + public void writeChar(char value); + + public void writeShort(short value); + + public void writeInt(int value); + + public void writeLong(long value); + + public void writeFloat(float value); + + public void writeDouble(double value); + + /** + * 写入无转义字符长度不超过255的字符串, 例如枚举值、字段名、类名字符串等 * + * + * @param value + */ + public void writeSmallString(String value); + + public void writeString(String value); +} diff --git a/src/org/redkale/convert/bson/BsonByteBufferWriter.java b/src/org/redkale/convert/bson/BsonByteBufferWriter.java new file mode 100644 index 000000000..ba724bbc1 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonByteBufferWriter.java @@ -0,0 +1,134 @@ +/* + * 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.convert.bson; + +import java.nio.*; +import java.util.function.*; + +/** + * + * @author zhangjx + */ +public final class BsonByteBufferWriter extends BsonWriter { + + private final Supplier supplier; + + private ByteBuffer[] buffers; + + private int index; + + protected BsonByteBufferWriter(Supplier supplier) { + super((byte[]) null); + this.supplier = supplier; + } + + @Override + public ByteBuffer[] toBuffers() { + if (buffers == null) return new ByteBuffer[0]; + for (int i = index; i < this.buffers.length; i++) { + ByteBuffer buf = this.buffers[i]; + if (buf.position() != 0) buf.flip(); + } + return this.buffers; + } + + @Override + public byte[] toArray() { + if (buffers == null) return new byte[0]; + int pos = 0; + byte[] bytes = new byte[this.count]; + for (ByteBuffer buf : toBuffers()) { + int r = buf.remaining(); + buf.get(bytes, pos, r); + buf.flip(); + pos += r; + } + return bytes; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "[count=" + this.count + "]"; + } + + @Override + public BsonByteBufferWriter setTiny(boolean tiny) { + this.tiny = tiny; + return this; + } + + @Override + protected int expand(final int byteLength) { + if (this.buffers == null) { + this.index = 0; + this.buffers = new ByteBuffer[]{supplier.get()}; + } + ByteBuffer buffer = this.buffers[index]; + if (!buffer.hasRemaining()) { + buffer.flip(); + buffer = supplier.get(); + ByteBuffer[] bufs = new ByteBuffer[this.buffers.length + 1]; + System.arraycopy(this.buffers, 0, bufs, 0, this.buffers.length); + bufs[this.buffers.length] = buffer; + this.buffers = bufs; + this.index++; + } + int len = buffer.remaining(); + int size = 0; + while (len < byteLength) { + buffer = supplier.get(); + ByteBuffer[] bufs = new ByteBuffer[this.buffers.length + 1]; + System.arraycopy(this.buffers, 0, bufs, 0, this.buffers.length); + bufs[this.buffers.length] = buffer; + this.buffers = bufs; + len += buffer.remaining(); + size++; + } + return size; + } + + @Override + public void writeTo(final byte[] chs, final int start, final int len) { + if (expand(len) == 0) { + this.buffers[index].put(chs, start, len); + } else { + ByteBuffer buffer = this.buffers[index]; + final int end = start + len; + int remain = len; //还剩多少没有写 + while (remain > 0) { + final int br = buffer.remaining(); + if (remain > br) { //一个buffer写不完 + buffer.put(chs, end - remain, br); + buffer = nextByteBuffer(); + remain -= br; + } else { + buffer.put(chs, end - remain, remain); + remain = 0; + } + } + } + this.count += len; + } + + private ByteBuffer nextByteBuffer() { + this.buffers[this.index].flip(); + return this.buffers[++this.index]; + } + + @Override + public void writeTo(final byte ch) { + expand(1); + this.buffers[index].put(ch); + count++; + } + + @Override + protected boolean recycle() { + this.index = 0; + this.buffers = null; + return false; + } +} diff --git a/src/org/redkale/convert/bson/BsonConvert.java b/src/org/redkale/convert/bson/BsonConvert.java new file mode 100644 index 000000000..38618f675 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonConvert.java @@ -0,0 +1,155 @@ +/* + * 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.convert.bson; + +import java.lang.reflect.*; +import java.nio.*; +import java.util.function.*; +import org.redkale.convert.*; +import org.redkale.util.*; + +/** + * BSON协议格式: + * 1). 基本数据类型: 直接转换成byte[] + * 2). SmallString(无特殊字符且长度小于256的字符串): length(1 byte) + byte[](utf8); 通常用于类名、字段名、枚举。 + * 3). String: length(4 bytes) + byte[](utf8); + * 4). 数组: length(4 bytes) + byte[]... + * 5). Object: + * 1. realclass (SmallString) (如果指定格式化的class与实体对象的class不一致才会有该值) + * 2. 空字符串(SmallString) + * 3. SIGN_OBJECTB 标记位,值固定为0xBB (short) + * 4. 循环字段值: + * 4.1 SIGN_HASNEXT 标记位,值固定为1 (byte) + * 4.2 字段类型; 1-9为基本类型&字符串; 101-109为基本类型&字符串的数组; 127为Object + * 4.3 字段名 (SmallString) + * 4.4 字段的值Object + * 5. SIGN_NONEXT 标记位,值固定为0 (byte) + * 6. SIGN_OBJECTE 标记位,值固定为0xEE (short) + * + * @author zhangjx + */ +public final class BsonConvert extends Convert { + + private static final ObjectPool readerPool = BsonReader.createPool(Integer.getInteger("convert.bson.pool.size", 16)); + + private static final ObjectPool writerPool = BsonWriter.createPool(Integer.getInteger("convert.bson.pool.size", 16)); + + private final boolean tiny; + + protected BsonConvert(Factory factory, boolean tiny) { + super(factory); + this.tiny = tiny; + } + + public BsonByteBufferWriter pollBsonWriter(final Supplier supplier) { + return new BsonByteBufferWriter(supplier).setTiny(tiny); + } + + public BsonWriter pollBsonWriter() { + return writerPool.get().setTiny(tiny); + } + + public void offerBsonWriter(BsonWriter out) { + if (out != null) writerPool.offer(out); + } + + public BsonReader pollBsonReader() { + return readerPool.get(); + } + + public void offerBsonReader(BsonReader in) { + if (in != null) readerPool.offer(in); + } + + public T convertFrom(final Type type, final byte[] bytes) { + if (bytes == null) return null; + return convertFrom(type, bytes, 0, bytes.length); + } + + public T convertFrom(final Type type, final byte[] bytes, int start, int len) { + if (type == null) return null; + final BsonReader in = readerPool.get(); + in.setBytes(bytes, start, len); + @SuppressWarnings("unchecked") + T rs = (T) factory.loadDecoder(type).convertFrom(in); + readerPool.offer(in); + return rs; + } + + public T convertFrom(final BsonReader in, final Type type) { + if (type == null) return null; + @SuppressWarnings("unchecked") + T rs = (T) factory.loadDecoder(type).convertFrom(in); + return rs; + } + + public byte[] convertTo(final Type type, Object value) { + if (type == null) return null; + final BsonWriter out = writerPool.get().setTiny(tiny); + factory.loadEncoder(type).convertTo(out, value); + byte[] result = out.toArray(); + writerPool.offer(out); + return result; + } + + public void convertTo(final BsonWriter out, final Type type, Object value) { + if (type == null) return; + factory.loadEncoder(type).convertTo(out, value); + } + + public ByteBuffer[] convertTo(final Supplier supplier, final Type type, Object value) { + if (supplier == null || type == null) return null; + BsonByteBufferWriter out = new BsonByteBufferWriter(supplier); + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(type).convertTo(out, value); + } + return out.toBuffers(); + } + + public ByteBuffer[] convertTo(final Supplier supplier, Object value) { + if (supplier == null) return null; + BsonByteBufferWriter out = new BsonByteBufferWriter(supplier); + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(out, value); + } + return out.toBuffers(); + } + + public void convertTo(final BsonWriter out, Object value) { + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(out, value); + } + } + + public byte[] convertTo(Object value) { + if (value == null) { + final BsonWriter out = writerPool.get().setTiny(tiny); + out.writeNull(); + byte[] result = out.toArray(); + writerPool.offer(out); + return result; + } + return convertTo(value.getClass(), value); + } + + public BsonWriter convertToWriter(final Type type, Object value) { + if (type == null) return null; + final BsonWriter out = writerPool.get().setTiny(tiny); + factory.loadEncoder(type).convertTo(out, value); + return out; + } + + public BsonWriter convertToWriter(Object value) { + if (value == null) return null; + return convertToWriter(value.getClass(), value); + } +} diff --git a/src/org/redkale/convert/bson/BsonFactory.java b/src/org/redkale/convert/bson/BsonFactory.java new file mode 100644 index 000000000..7b2dba377 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonFactory.java @@ -0,0 +1,62 @@ +/* + * 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.convert.bson; + +import java.io.Serializable; +import org.redkale.convert.*; + +/** + * + * @author zhangjx + */ +public final class BsonFactory extends Factory { + + private static final BsonFactory instance = new BsonFactory(null, Boolean.getBoolean("convert.bson.tiny")); + + static final Decodeable objectDecoder = instance.loadDecoder(Object.class); + + static final Encodeable objectEncoder = instance.loadEncoder(Object.class); + + static { + instance.register(Serializable.class, objectDecoder); + instance.register(Serializable.class, objectEncoder); + } + + private BsonFactory(BsonFactory parent, boolean tiny) { + super(parent, tiny); + } + + public static BsonFactory root() { + return instance; + } + + @Override + public final BsonConvert getConvert() { + if (convert == null) convert = new BsonConvert(this, tiny); + return (BsonConvert) convert; + } + + @Override + public BsonFactory createChild() { + return new BsonFactory(this, this.tiny); + } + + @Override + public BsonFactory createChild(boolean tiny) { + return new BsonFactory(this, tiny); + } + + @Override + public ConvertType getConvertType() { + return ConvertType.BSON; + } + + @Override + public boolean isReversible() { + return true; + } + +} diff --git a/src/org/redkale/convert/bson/BsonReader.java b/src/org/redkale/convert/bson/BsonReader.java new file mode 100644 index 000000000..5596e7677 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonReader.java @@ -0,0 +1,323 @@ +/* + * 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.convert.bson; + +import java.util.concurrent.atomic.*; +import java.util.function.*; +import org.redkale.convert.*; +import static org.redkale.convert.Reader.SIGN_NULL; +import org.redkale.convert.ext.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class BsonReader implements Reader { + + public static final short SIGN_OBJECTB = (short) 0xBB; + + public static final short SIGN_OBJECTE = (short) 0xEE; + + public static final byte SIGN_HASNEXT = 1; + + public static final byte SIGN_NONEXT = 0; + + public static final byte VERBOSE_NO = 1; + + public static final byte VERBOSE_YES = 2; + + private int position = -1; + + private byte typeval; //字段的类型值 对应 BsonWriter.writeField + + private byte[] content; + + public BsonReader() { + } + + public static ObjectPool createPool(int max) { + return new ObjectPool(max, new Creator() { + + @Override + public BsonReader create(Object... params) { + return new BsonReader(); + } + }, null, new Predicate() { + + @Override + public boolean test(BsonReader t) { + return t.recycle(); + } + }); + } + + public BsonReader(byte[] bytes) { + setBytes(bytes, 0, bytes.length); + } + + public BsonReader(byte[] bytes, int start, int len) { + setBytes(bytes, start, len); + } + + public final void setBytes(byte[] bytes) { + if (bytes == null) { + this.position = 0; + } else { + setBytes(bytes, 0, bytes.length); + } + } + + public final void setBytes(byte[] bytes, int start, int len) { + if (bytes == null) { + this.position = 0; + } else { + this.content = bytes; + this.position = start - 1; + //this.limit = start + len - 1; + } + } + + protected boolean recycle() { + this.position = -1; + this.typeval = 0; + //this.limit = -1; + this.content = null; + return true; + } + + public void close() { + this.recycle(); + } + + /** + * 跳过属性的值 + */ + @Override + public final void skipValue() { + if (typeval == 0) return; + final byte val = this.typeval; + this.typeval = 0; + switch (val) { + case 1: readBoolean(); + break; + case 2: readByte(); + break; + case 3: readShort(); + break; + case 4: readChar(); + break; + case 5: readInt(); + break; + case 6: readLong(); + break; + case 7: readFloat(); + break; + case 8: readDouble(); + break; + case 9: readString(); + break; + case 101: + BoolArraySimpledCoder.instance.convertFrom(this); + break; + case 102: + ByteArraySimpledCoder.instance.convertFrom(this); + break; + case 103: + ShortArraySimpledCoder.instance.convertFrom(this); + break; + case 104: + CharArraySimpledCoder.instance.convertFrom(this); + break; + case 105: + IntArraySimpledCoder.instance.convertFrom(this); + break; + case 106: + LongArraySimpledCoder.instance.convertFrom(this); + break; + case 107: + FloatArraySimpledCoder.instance.convertFrom(this); + break; + case 108: + DoubleArraySimpledCoder.instance.convertFrom(this); + break; + case 109: + StringArraySimpledCoder.instance.convertFrom(this); + break; + case 127: + BsonFactory.objectDecoder.convertFrom(this); + break; + } + } + + /** + * 判断下一个非空白字节是否为{ + * + */ + @Override + public int readObjectB() { + short bt = readShort(); + if (bt == Reader.SIGN_NULL) return bt; + if (bt != SIGN_OBJECTB) { + throw new ConvertException("a bson object must begin with " + (SIGN_OBJECTB) + + " (position = " + position + ") but '" + this.content[this.position] + "'"); + } + return bt; + } + + @Override + public void readObjectE() { + if (readShort() != SIGN_OBJECTE) { + throw new ConvertException("a bson object must end with " + (SIGN_OBJECTE) + + " (position = " + position + ") but '" + this.content[this.position] + "'"); + } + } + + @Override + public int readMapB() { + return readArrayB(); + } + + @Override + public void readMapE() { + } + + /** + * 判断下一个非空白字节是否为[ + * + * @return + */ + @Override + public int readArrayB() { + short bt = readShort(); + if (bt == Reader.SIGN_NULL) return bt; + return (bt & 0xffff) << 16 | ((content[++this.position] & 0xff) << 8) | (content[++this.position] & 0xff); + } + + @Override + public void readArrayE() { + } + + /** + * 判断下一个非空白字节是否: + */ + @Override + public void skipBlank() { + } + + /** + * 判断对象是否存在下一个属性或者数组是否存在下一个元素 + * + * @return + */ + @Override + public boolean hasNext() { + byte b = readByte(); + if (b == SIGN_HASNEXT) return true; + if (b != SIGN_NONEXT) throw new ConvertException("hasNext option must be (" + (SIGN_HASNEXT) + + " or " + (SIGN_NONEXT) + ") but '" + b + "' at position(" + this.position + ")"); + return false; + } + + @Override + public DeMember readField(final AtomicInteger index, final DeMember[] members) { + final String exceptedfield = readSmallString(); + this.typeval = readByte(); + final int len = members.length; + int v = index.get(); + if (v >= len) { + v = 0; + index.set(0); + } + for (int k = v; k < len; k++) { + if (exceptedfield.equals(members[k].getAttribute().field())) { + index.set(k); + return members[k]; + } + } + for (int k = 0; k < v; k++) { + if (exceptedfield.equals(members[k].getAttribute().field())) { + index.set(k); + return members[k]; + } + } + return null; + } + + //------------------------------------------------------------ + @Override + public boolean readBoolean() { + return content[++this.position] == 1; + } + + @Override + public byte readByte() { + return content[++this.position]; + } + + @Override + public char readChar() { + return (char) ((0xff00 & (content[++this.position] << 8)) | (0xff & content[++this.position])); + } + + @Override + public short readShort() { + return (short) ((0xff00 & (content[++this.position] << 8)) | (0xff & content[++this.position])); + } + + @Override + public int readInt() { + return ((content[++this.position] & 0xff) << 24) | ((content[++this.position] & 0xff) << 16) + | ((content[++this.position] & 0xff) << 8) | (content[++this.position] & 0xff); + } + + @Override + public long readLong() { + return ((((long) content[++this.position] & 0xff) << 56) + | (((long) content[++this.position] & 0xff) << 48) + | (((long) content[++this.position] & 0xff) << 40) + | (((long) content[++this.position] & 0xff) << 32) + | (((long) content[++this.position] & 0xff) << 24) + | (((long) content[++this.position] & 0xff) << 16) + | (((long) content[++this.position] & 0xff) << 8) + | (((long) content[++this.position] & 0xff))); + } + + @Override + public float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + @Override + public double readDouble() { + return Double.longBitsToDouble(readLong()); + } + + @Override + public String readClassName() { + return readSmallString(); + } + + @Override + public String readSmallString() { + int len = 0xff & readByte(); + if (len == 0) return ""; + String value = new String(content, ++this.position, len); + this.position += len - 1; + return value; + } + + @Override + public String readString() { + int len = readInt(); + if (len == SIGN_NULL) return null; + if (len == 0) return ""; + String value = new String(Utility.decodeUTF8(content, ++this.position, len)); + this.position += len - 1; + return value; + } + +} diff --git a/src/org/redkale/convert/bson/BsonSimpledCoder.java b/src/org/redkale/convert/bson/BsonSimpledCoder.java new file mode 100644 index 000000000..2a4e5950f --- /dev/null +++ b/src/org/redkale/convert/bson/BsonSimpledCoder.java @@ -0,0 +1,17 @@ +/* + * 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.convert.bson; + +import org.redkale.convert.SimpledCoder; + +/** + * + * @author zhangjx + * @param + */ +public abstract class BsonSimpledCoder extends SimpledCoder { + +} diff --git a/src/org/redkale/convert/bson/BsonWriter.java b/src/org/redkale/convert/bson/BsonWriter.java new file mode 100644 index 000000000..c10e2f204 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonWriter.java @@ -0,0 +1,297 @@ +/* + * 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.convert.bson; + +import java.nio.*; +import java.util.function.*; +import org.redkale.convert.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public class BsonWriter implements Writer { + + private static final int defaultSize = Integer.getInteger("convert.bson.writer.buffer.defsize", 1024); + + private byte[] content; + + protected int count; + + protected boolean tiny; + + public static ObjectPool createPool(int max) { + return new ObjectPool(max, new Creator() { + + @Override + public BsonWriter create(Object... params) { + return new BsonWriter(); + } + }, null, new Predicate() { + + @Override + public boolean test(BsonWriter t) { + return t.recycle(); + } + }); + } + + public byte[] toArray() { + if (count == content.length) return content; + byte[] newdata = new byte[count]; + System.arraycopy(content, 0, newdata, 0, count); + return newdata; + } + + public ByteBuffer[] toBuffers() { + return new ByteBuffer[]{ByteBuffer.wrap(content, 0, count)}; + } + + protected BsonWriter(byte[] bs) { + this.content = bs; + } + + public BsonWriter() { + this(defaultSize); + } + + public BsonWriter(int size) { + this.content = new byte[size > 128 ? size : 128]; + } + + @Override + public final boolean isTiny() { + return tiny; + } + + public BsonWriter setTiny(boolean tiny) { + this.tiny = tiny; + return this; + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + /** + * 扩充指定长度的缓冲区 + * + * @param len + * @return + */ + protected int expand(int len) { + int newcount = count + len; + if (newcount <= content.length) return 0; + byte[] newdata = new byte[Math.max(content.length * 3 / 2, newcount)]; + System.arraycopy(content, 0, newdata, 0, count); + this.content = newdata; + return 0; + } + + public void writeTo(final byte ch) { + expand(1); + content[count++] = ch; + } + + public final void writeTo(final byte... chs) { + writeTo(chs, 0, chs.length); + } + + public void writeTo(final byte[] chs, final int start, final int len) { + expand(len); + System.arraycopy(chs, start, content, count, len); + count += len; + } + + protected boolean recycle() { + this.count = 0; + if (this.content.length > defaultSize) { + this.content = new byte[defaultSize]; + } + return true; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "[count=" + this.count + "]"; + } + + //------------------------------------------------------------------------ + public final int count() { + return this.count; + } + + @Override + public final void writeBoolean(boolean value) { + writeTo(value ? (byte) 1 : (byte) 0); + } + + @Override + public final void writeByte(byte value) { + writeTo(value); + } + + @Override + public final void writeChar(final char value) { + writeTo((byte) ((value & 0xFF00) >> 8), (byte) (value & 0xFF)); + } + + @Override + public final void writeShort(short value) { + writeTo((byte) (value >> 8), (byte) value); + } + + @Override + public final void writeInt(int value) { + writeTo((byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value); + } + + @Override + public final void writeLong(long value) { + writeTo((byte) (value >> 56), (byte) (value >> 48), (byte) (value >> 40), (byte) (value >> 32), + (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value); + } + + @Override + public final void writeFloat(float value) { + writeInt(Float.floatToIntBits(value)); + } + + @Override + public final void writeDouble(double value) { + writeLong(Double.doubleToLongBits(value)); + } + + @Override + public final void wirteClassName(String clazz) { + writeSmallString(clazz == null ? "" : clazz); + } + + @Override + public final void writeObjectB(int fieldCount, Object obj) { + writeSmallString(""); + writeShort(BsonReader.SIGN_OBJECTB); + } + + @Override + public final void writeObjectE(Object obj) { + writeByte(BsonReader.SIGN_NONEXT); + writeShort(BsonReader.SIGN_OBJECTE); + } + + @Override + public final void writeField(boolean comma, Attribute attribute) { + writeByte(BsonReader.SIGN_HASNEXT); + writeSmallString(attribute.field()); + byte typeval = 127; //字段的类型值 + final Class type = attribute.type(); + if (type == boolean.class || type == Boolean.class) { + typeval = 1; + } else if (type == byte.class || type == Byte.class) { + typeval = 2; + } else if (type == short.class || type == Short.class) { + typeval = 3; + } else if (type == char.class || type == Character.class) { + typeval = 4; + } else if (type == int.class || type == Integer.class) { + typeval = 5; + } else if (type == long.class || type == Long.class) { + typeval = 6; + } else if (type == float.class || type == Float.class) { + typeval = 7; + } else if (type == double.class || type == Double.class) { + typeval = 8; + } else if (type == String.class) { + typeval = 9; + } else if (type == boolean[].class || type == Boolean[].class) { + typeval = 101; + } else if (type == byte[].class || type == Byte[].class) { + typeval = 102; + } else if (type == short[].class || type == Short[].class) { + typeval = 103; + } else if (type == char[].class || type == Character[].class) { + typeval = 104; + } else if (type == int[].class || type == Integer[].class) { + typeval = 105; + } else if (type == long[].class || type == Long[].class) { + typeval = 106; + } else if (type == float[].class || type == Float[].class) { + typeval = 107; + } else if (type == double[].class || type == Double[].class) { + typeval = 108; + } else if (type == String[].class) { + typeval = 109; + } + writeByte(typeval); + } + + /** + * 对于类的字段名、枚举值这些长度一般不超过255且不会出现双字节字符的字符串采用writeSmallString处理, readSmallString用于读取 + * + * @param value + */ + @Override + public final void writeSmallString(String value) { + if (value.isEmpty()) { + writeTo((byte) 0); + return; + } + char[] chars = Utility.charArray(value); + if (chars.length > 255) throw new ConvertException("'" + value + "' has very long length"); + byte[] bytes = new byte[chars.length + 1]; + bytes[0] = (byte) chars.length; + for (int i = 0; i < chars.length; i++) { + if (chars[i] > Byte.MAX_VALUE) throw new ConvertException("'" + value + "' has double-word"); + bytes[i + 1] = (byte) chars[i]; + } + writeTo(bytes); + } + + @Override + public final void writeString(String value) { + if (value == null) { + writeInt(Reader.SIGN_NULL); + return; + } else if (value.isEmpty()) { + writeInt(0); + return; + } + byte[] bytes = Utility.encodeUTF8(value); + writeInt(bytes.length); + writeTo(bytes); + } + + @Override + public final void writeNull() { + writeShort(Reader.SIGN_NULL); + } + + @Override + public final void writeArrayB(int size) { + writeInt(size); + } + + @Override + public final void writeArrayMark() { + } + + @Override + public final void writeArrayE() { + } + + @Override + public void writeMapB(int size) { + writeArrayB(size); + } + + @Override + public final void writeMapMark() { + } + + @Override + public final void writeMapE() { + } + +} diff --git a/src/org/redkale/convert/ext/BigIntegerSimpledCoder.java b/src/org/redkale/convert/ext/BigIntegerSimpledCoder.java new file mode 100644 index 000000000..8a6dd5284 --- /dev/null +++ b/src/org/redkale/convert/ext/BigIntegerSimpledCoder.java @@ -0,0 +1,38 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; +import org.redkale.convert.Reader; +import java.math.BigInteger; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class BigIntegerSimpledCoder extends SimpledCoder { + + public static final BigIntegerSimpledCoder instance = new BigIntegerSimpledCoder(); + + @Override + public void convertTo(W out, BigInteger value) { + if (value == null) { + out.writeNull(); + return; + } + ByteArraySimpledCoder.instance.convertTo(out, value.toByteArray()); + } + + @Override + public BigInteger convertFrom(R in) { + byte[] bytes = ByteArraySimpledCoder.instance.convertFrom(in); + return bytes == null ? null : new BigInteger(bytes); + } + +} diff --git a/src/org/redkale/convert/ext/BoolArraySimpledCoder.java b/src/org/redkale/convert/ext/BoolArraySimpledCoder.java new file mode 100644 index 000000000..bf9f2e2c2 --- /dev/null +++ b/src/org/redkale/convert/ext/BoolArraySimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class BoolArraySimpledCoder extends SimpledCoder { + + public static final BoolArraySimpledCoder instance = new BoolArraySimpledCoder(); + + @Override + public void convertTo(W out, boolean[] values) { + if (values == null) { + out.writeNull(); + return; + } + out.writeArrayB(values.length); + boolean flag = false; + for (boolean v : values) { + if (flag) out.writeArrayMark(); + out.writeBoolean(v); + flag = true; + } + out.writeArrayE(); + } + + @Override + public boolean[] convertFrom(R in) { + int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) { + return null; + } else if (len == Reader.SIGN_NOLENGTH) { + int size = 0; + boolean[] data = new boolean[8]; + while (in.hasNext()) { + if (size >= data.length) { + boolean[] newdata = new boolean[data.length + 4]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = in.readBoolean(); + } + in.readArrayE(); + boolean[] newdata = new boolean[size]; + System.arraycopy(data, 0, newdata, 0, size); + return newdata; + } else { + boolean[] values = new boolean[len]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readBoolean(); + } + in.readArrayE(); + return values; + } + } + +} diff --git a/src/org/redkale/convert/ext/BoolSimpledCoder.java b/src/org/redkale/convert/ext/BoolSimpledCoder.java new file mode 100644 index 000000000..e25feca7d --- /dev/null +++ b/src/org/redkale/convert/ext/BoolSimpledCoder.java @@ -0,0 +1,32 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class BoolSimpledCoder extends SimpledCoder { + + public static final BoolSimpledCoder instance = new BoolSimpledCoder(); + + @Override + public void convertTo(W out, Boolean value) { + out.writeBoolean(value); + } + + @Override + public Boolean convertFrom(R in) { + return in.readBoolean(); + } + +} diff --git a/src/org/redkale/convert/ext/ByteArraySimpledCoder.java b/src/org/redkale/convert/ext/ByteArraySimpledCoder.java new file mode 100644 index 000000000..d642e726c --- /dev/null +++ b/src/org/redkale/convert/ext/ByteArraySimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class ByteArraySimpledCoder extends SimpledCoder { + + public static final ByteArraySimpledCoder instance = new ByteArraySimpledCoder(); + + @Override + public void convertTo(W out, byte[] values) { + if (values == null) { + out.writeNull(); + return; + } + out.writeArrayB(values.length); + boolean flag = false; + for (byte v : values) { + if (flag) out.writeArrayMark(); + out.writeByte(v); + flag = true; + } + out.writeArrayE(); + } + + @Override + public byte[] convertFrom(R in) { + int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) { + return null; + } else if (len == Reader.SIGN_NOLENGTH) { + int size = 0; + byte[] data = new byte[8]; + while (in.hasNext()) { + if (size >= data.length) { + byte[] newdata = new byte[data.length + 4]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = in.readByte(); + } + in.readArrayE(); + byte[] newdata = new byte[size]; + System.arraycopy(data, 0, newdata, 0, size); + return newdata; + } else { + byte[] values = new byte[len]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readByte(); + } + in.readArrayE(); + return values; + } + } + +} diff --git a/src/org/redkale/convert/ext/ByteSimpledCoder.java b/src/org/redkale/convert/ext/ByteSimpledCoder.java new file mode 100644 index 000000000..74861c7a8 --- /dev/null +++ b/src/org/redkale/convert/ext/ByteSimpledCoder.java @@ -0,0 +1,32 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class ByteSimpledCoder extends SimpledCoder { + + public static final ByteSimpledCoder instance = new ByteSimpledCoder(); + + @Override + public void convertTo(W out, Byte value) { + out.writeByte(value); + } + + @Override + public Byte convertFrom(R in) { + return in.readByte(); + } + +} diff --git a/src/org/redkale/convert/ext/CharArraySimpledCoder.java b/src/org/redkale/convert/ext/CharArraySimpledCoder.java new file mode 100644 index 000000000..efac0785f --- /dev/null +++ b/src/org/redkale/convert/ext/CharArraySimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class CharArraySimpledCoder extends SimpledCoder { + + public static final CharArraySimpledCoder instance = new CharArraySimpledCoder(); + + @Override + public void convertTo(W out, char[] values) { + if (values == null) { + out.writeNull(); + return; + } + out.writeArrayB(values.length); + boolean flag = false; + for (char v : values) { + if (flag) out.writeArrayMark(); + out.writeChar(v); + flag = true; + } + out.writeArrayE(); + } + + @Override + public char[] convertFrom(R in) { + int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) { + return null; + } else if (len == Reader.SIGN_NOLENGTH) { + int size = 0; + char[] data = new char[8]; + while (in.hasNext()) { + if (size >= data.length) { + char[] newdata = new char[data.length + 4]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = in.readChar(); + } + in.readArrayE(); + char[] newdata = new char[size]; + System.arraycopy(data, 0, newdata, 0, size); + return newdata; + } else { + char[] values = new char[len]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readChar(); + } + in.readArrayE(); + return values; + } + } + +} diff --git a/src/org/redkale/convert/ext/CharSimpledCoder.java b/src/org/redkale/convert/ext/CharSimpledCoder.java new file mode 100644 index 000000000..8e3803945 --- /dev/null +++ b/src/org/redkale/convert/ext/CharSimpledCoder.java @@ -0,0 +1,33 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class CharSimpledCoder extends SimpledCoder { + + public static final CharSimpledCoder instance = new CharSimpledCoder(); + + @Override + public void convertTo(W out, Character value) { + out.writeChar(value); + } + + @Override + public Character convertFrom(R in) { + return in.readChar(); + } + +} diff --git a/src/org/redkale/convert/ext/DLongSimpledCoder.java b/src/org/redkale/convert/ext/DLongSimpledCoder.java new file mode 100644 index 000000000..9df4bb726 --- /dev/null +++ b/src/org/redkale/convert/ext/DLongSimpledCoder.java @@ -0,0 +1,40 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.Writer; +import org.redkale.convert.SimpledCoder; +import org.redkale.util.DLong; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class DLongSimpledCoder extends SimpledCoder { + + public static final DLongSimpledCoder instance = new DLongSimpledCoder(); + + @Override + public void convertTo(final W out, final DLong value) { + if (value == null) { + out.writeNull(); + } else { + out.writeSmallString(value.getFirst() + "_" + value.getSecond()); + } + } + + @Override + public DLong convertFrom(R in) { + String str = in.readString(); + if (str == null) return null; + int pos = str.indexOf('_'); + return new DLong(Long.parseLong(str.substring(0, pos)), Long.parseLong(str.substring(pos + 1))); + } + +} diff --git a/src/org/redkale/convert/ext/DateSimpledCoder.java b/src/org/redkale/convert/ext/DateSimpledCoder.java new file mode 100644 index 000000000..d884167e4 --- /dev/null +++ b/src/org/redkale/convert/ext/DateSimpledCoder.java @@ -0,0 +1,33 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; +import java.util.Date; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class DateSimpledCoder extends SimpledCoder { + + public static final DateSimpledCoder instance = new DateSimpledCoder(); + + @Override + public void convertTo(W out, Date value) { + out.writeLong(value.getTime()); + } + + @Override + public Date convertFrom(R in) { + return new Date(in.readLong()); + } + +} diff --git a/src/org/redkale/convert/ext/DoubleArraySimpledCoder.java b/src/org/redkale/convert/ext/DoubleArraySimpledCoder.java new file mode 100644 index 000000000..9005ab25d --- /dev/null +++ b/src/org/redkale/convert/ext/DoubleArraySimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class DoubleArraySimpledCoder extends SimpledCoder { + + public static final DoubleArraySimpledCoder instance = new DoubleArraySimpledCoder(); + + @Override + public void convertTo(W out, double[] values) { + if (values == null) { + out.writeNull(); + return; + } + out.writeArrayB(values.length); + boolean flag = false; + for (double v : values) { + if (flag) out.writeArrayMark(); + out.writeDouble(v); + flag = true; + } + out.writeArrayE(); + } + + @Override + public double[] convertFrom(R in) { + int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) { + return null; + } else if (len == Reader.SIGN_NOLENGTH) { + int size = 0; + double[] data = new double[8]; + while (in.hasNext()) { + if (size >= data.length) { + double[] newdata = new double[data.length + 4]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = in.readDouble(); + } + in.readArrayE(); + double[] newdata = new double[size]; + System.arraycopy(data, 0, newdata, 0, size); + return newdata; + } else { + double[] values = new double[len]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readDouble(); + } + in.readArrayE(); + return values; + } + } + +} diff --git a/src/org/redkale/convert/ext/DoubleSimpledCoder.java b/src/org/redkale/convert/ext/DoubleSimpledCoder.java new file mode 100644 index 000000000..613877115 --- /dev/null +++ b/src/org/redkale/convert/ext/DoubleSimpledCoder.java @@ -0,0 +1,32 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class DoubleSimpledCoder extends SimpledCoder { + + public static final DoubleSimpledCoder instance = new DoubleSimpledCoder(); + + @Override + public void convertTo(W out, Double value) { + out.writeDouble(value); + } + + @Override + public Double convertFrom(R in) { + return in.readDouble(); + } + +} diff --git a/src/org/redkale/convert/ext/EnumSimpledCoder.java b/src/org/redkale/convert/ext/EnumSimpledCoder.java new file mode 100644 index 000000000..aa6b0ee10 --- /dev/null +++ b/src/org/redkale/convert/ext/EnumSimpledCoder.java @@ -0,0 +1,44 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + * @param + */ +public final class EnumSimpledCoder extends SimpledCoder { + + private final Class type; + + public EnumSimpledCoder(Class type) { + this.type = type; + } + + @Override + public void convertTo(final W out, final E value) { + if (value == null) { + out.writeNull(); + } else { + out.writeSmallString(value.toString()); + } + } + + @Override + @SuppressWarnings("unchecked") + public E convertFrom(final R in) { + String value = in.readSmallString(); + if (value == null) return null; + return (E) Enum.valueOf(type, value); + } + +} diff --git a/src/org/redkale/convert/ext/FloatArraySimpledCoder.java b/src/org/redkale/convert/ext/FloatArraySimpledCoder.java new file mode 100644 index 000000000..84eaf45f8 --- /dev/null +++ b/src/org/redkale/convert/ext/FloatArraySimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class FloatArraySimpledCoder extends SimpledCoder { + + public static final FloatArraySimpledCoder instance = new FloatArraySimpledCoder(); + + @Override + public void convertTo(W out, float[] values) { + if (values == null) { + out.writeNull(); + return; + } + out.writeArrayB(values.length); + boolean flag = false; + for (float v : values) { + if (flag) out.writeArrayMark(); + out.writeFloat(v); + flag = true; + } + out.writeArrayE(); + } + + @Override + public float[] convertFrom(R in) { + int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) { + return null; + } else if (len == Reader.SIGN_NOLENGTH) { + int size = 0; + float[] data = new float[8]; + while (in.hasNext()) { + if (size >= data.length) { + float[] newdata = new float[data.length + 4]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = in.readFloat(); + } + in.readArrayE(); + float[] newdata = new float[size]; + System.arraycopy(data, 0, newdata, 0, size); + return newdata; + } else { + float[] values = new float[len]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readFloat(); + } + in.readArrayE(); + return values; + } + } + +} diff --git a/src/org/redkale/convert/ext/FloatSimpledCoder.java b/src/org/redkale/convert/ext/FloatSimpledCoder.java new file mode 100644 index 000000000..265eddbeb --- /dev/null +++ b/src/org/redkale/convert/ext/FloatSimpledCoder.java @@ -0,0 +1,32 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class FloatSimpledCoder extends SimpledCoder { + + public static final FloatSimpledCoder instance = new FloatSimpledCoder(); + + @Override + public void convertTo(W out, Float value) { + out.writeFloat(value); + } + + @Override + public Float convertFrom(R in) { + return in.readFloat(); + } + +} diff --git a/src/org/redkale/convert/ext/InetAddressSimpledCoder.java b/src/org/redkale/convert/ext/InetAddressSimpledCoder.java new file mode 100644 index 000000000..f2eee328a --- /dev/null +++ b/src/org/redkale/convert/ext/InetAddressSimpledCoder.java @@ -0,0 +1,70 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; +import org.redkale.convert.Reader; +import java.net.*; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class InetAddressSimpledCoder extends SimpledCoder { + + public static final InetAddressSimpledCoder instance = new InetAddressSimpledCoder(); + + @Override + public void convertTo(W out, InetAddress value) { + if (value == null) { + out.writeNull(); + return; + } + ByteArraySimpledCoder.instance.convertTo(out, value.getAddress()); + } + + @Override + public InetAddress convertFrom(R in) { + byte[] bytes = ByteArraySimpledCoder.instance.convertFrom(in); + if (bytes == null) return null; + try { + return InetAddress.getByAddress(bytes); + } catch (Exception ex) { + return null; + } + } + + public final static class InetSocketAddressSimpledCoder extends SimpledCoder { + + public static final InetSocketAddressSimpledCoder instance = new InetSocketAddressSimpledCoder(); + + @Override + public void convertTo(W out, InetSocketAddress value) { + if (value == null) { + out.writeNull(); + return; + } + ByteArraySimpledCoder.instance.convertTo(out, value.getAddress().getAddress()); + out.writeInt(value.getPort()); + } + + @Override + public InetSocketAddress convertFrom(R in) { + byte[] bytes = ByteArraySimpledCoder.instance.convertFrom(in); + if (bytes == null) return null; + int port = in.readInt(); + try { + return new InetSocketAddress(InetAddress.getByAddress(bytes), port); + } catch (Exception ex) { + return null; + } + } + + } +} diff --git a/src/org/redkale/convert/ext/IntArraySimpledCoder.java b/src/org/redkale/convert/ext/IntArraySimpledCoder.java new file mode 100644 index 000000000..ca9b60000 --- /dev/null +++ b/src/org/redkale/convert/ext/IntArraySimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class IntArraySimpledCoder extends SimpledCoder { + + public static final IntArraySimpledCoder instance = new IntArraySimpledCoder(); + + @Override + public void convertTo(W out, int[] values) { + if (values == null) { + out.writeNull(); + return; + } + out.writeArrayB(values.length); + boolean flag = false; + for (int v : values) { + if (flag) out.writeArrayMark(); + out.writeInt(v); + flag = true; + } + out.writeArrayE(); + } + + @Override + public int[] convertFrom(R in) { + int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) { + return null; + } else if (len == Reader.SIGN_NOLENGTH) { + int size = 0; + int[] data = new int[8]; + while (in.hasNext()) { + if (size >= data.length) { + int[] newdata = new int[data.length + 4]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = in.readInt(); + } + in.readArrayE(); + int[] newdata = new int[size]; + System.arraycopy(data, 0, newdata, 0, size); + return newdata; + } else { + int[] values = new int[len]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readInt(); + } + in.readArrayE(); + return values; + } + } + +} diff --git a/src/org/redkale/convert/ext/IntSimpledCoder.java b/src/org/redkale/convert/ext/IntSimpledCoder.java new file mode 100644 index 000000000..13815f424 --- /dev/null +++ b/src/org/redkale/convert/ext/IntSimpledCoder.java @@ -0,0 +1,32 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class IntSimpledCoder extends SimpledCoder { + + public static final IntSimpledCoder instance = new IntSimpledCoder(); + + @Override + public void convertTo(W out, Integer value) { + out.writeInt(value); + } + + @Override + public Integer convertFrom(R in) { + return in.readInt(); + } + +} diff --git a/src/org/redkale/convert/ext/LongArraySimpledCoder.java b/src/org/redkale/convert/ext/LongArraySimpledCoder.java new file mode 100644 index 000000000..b9f21a145 --- /dev/null +++ b/src/org/redkale/convert/ext/LongArraySimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class LongArraySimpledCoder extends SimpledCoder { + + public static final LongArraySimpledCoder instance = new LongArraySimpledCoder(); + + @Override + public void convertTo(W out, long[] values) { + if (values == null) { + out.writeNull(); + return; + } + out.writeArrayB(values.length); + boolean flag = false; + for (long v : values) { + if (flag) out.writeArrayMark(); + out.writeLong(v); + flag = true; + } + out.writeArrayE(); + } + + @Override + public long[] convertFrom(R in) { + int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) { + return null; + } else if (len == Reader.SIGN_NOLENGTH) { + int size = 0; + long[] data = new long[8]; + while (in.hasNext()) { + if (size >= data.length) { + long[] newdata = new long[data.length + 4]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = in.readLong(); + } + in.readArrayE(); + long[] newdata = new long[size]; + System.arraycopy(data, 0, newdata, 0, size); + return newdata; + } else { + long[] values = new long[len]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readLong(); + } + in.readArrayE(); + return values; + } + } + +} diff --git a/src/org/redkale/convert/ext/LongSimpledCoder.java b/src/org/redkale/convert/ext/LongSimpledCoder.java new file mode 100644 index 000000000..0c578ceea --- /dev/null +++ b/src/org/redkale/convert/ext/LongSimpledCoder.java @@ -0,0 +1,33 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class LongSimpledCoder extends SimpledCoder { + + public static final LongSimpledCoder instance = new LongSimpledCoder(); + + @Override + public void convertTo(W out, Long value) { + out.writeLong(value); + } + + @Override + public Long convertFrom(R in) { + return in.readLong(); + } + +} diff --git a/src/org/redkale/convert/ext/NumberSimpledCoder.java b/src/org/redkale/convert/ext/NumberSimpledCoder.java new file mode 100644 index 000000000..28c809fa8 --- /dev/null +++ b/src/org/redkale/convert/ext/NumberSimpledCoder.java @@ -0,0 +1,32 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class NumberSimpledCoder extends SimpledCoder { + + public static final NumberSimpledCoder instance = new NumberSimpledCoder(); + + @Override + public void convertTo(W out, Number value) { + out.writeLong(value == null ? 0L : value.longValue()); + } + + @Override + public Number convertFrom(R in) { + return in.readLong(); + } + +} diff --git a/src/org/redkale/convert/ext/PatternSimpledCoder.java b/src/org/redkale/convert/ext/PatternSimpledCoder.java new file mode 100644 index 000000000..fd652a3ac --- /dev/null +++ b/src/org/redkale/convert/ext/PatternSimpledCoder.java @@ -0,0 +1,38 @@ +/* + * 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.convert.ext; + +import java.util.regex.*; +import org.redkale.convert.*; + +/** + * + * @author zhangjx + * @param + * @param + */ +public class PatternSimpledCoder extends SimpledCoder { + + public static final PatternSimpledCoder instance = new PatternSimpledCoder(); + + @Override + public void convertTo(W out, Pattern value) { + if (value == null) { + out.writeNull(); + } else { + out.writeString(value.flags() + "," + value.pattern()); + } + } + + @Override + public Pattern convertFrom(R in) { + String value = in.readString(); + if (value == null) return null; + int pos = value.indexOf(','); + return Pattern.compile(value.substring(pos + 1), Integer.parseInt(value.substring(0, pos))); + } + +} diff --git a/src/org/redkale/convert/ext/ShortArraySimpledCoder.java b/src/org/redkale/convert/ext/ShortArraySimpledCoder.java new file mode 100644 index 000000000..af726f8bc --- /dev/null +++ b/src/org/redkale/convert/ext/ShortArraySimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class ShortArraySimpledCoder extends SimpledCoder { + + public static final ShortArraySimpledCoder instance = new ShortArraySimpledCoder(); + + @Override + public void convertTo(W out, short[] values) { + if (values == null) { + out.writeNull(); + return; + } + out.writeArrayB(values.length); + boolean flag = false; + for (short v : values) { + if (flag) out.writeArrayMark(); + out.writeShort(v); + flag = true; + } + out.writeArrayE(); + } + + @Override + public short[] convertFrom(R in) { + int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) { + return null; + } else if (len == Reader.SIGN_NOLENGTH) { + int size = 0; + short[] data = new short[8]; + while (in.hasNext()) { + if (size >= data.length) { + short[] newdata = new short[data.length + 4]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = in.readShort(); + } + in.readArrayE(); + short[] newdata = new short[size]; + System.arraycopy(data, 0, newdata, 0, size); + return newdata; + } else { + short[] values = new short[len]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readShort(); + } + in.readArrayE(); + return values; + } + } + +} diff --git a/src/org/redkale/convert/ext/ShortSimpledCoder.java b/src/org/redkale/convert/ext/ShortSimpledCoder.java new file mode 100644 index 000000000..1b6f08221 --- /dev/null +++ b/src/org/redkale/convert/ext/ShortSimpledCoder.java @@ -0,0 +1,32 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class ShortSimpledCoder extends SimpledCoder { + + public static final ShortSimpledCoder instance = new ShortSimpledCoder(); + + @Override + public void convertTo(W out, Short value) { + out.writeShort(value); + } + + @Override + public Short convertFrom(R in) { + return in.readShort(); + } + +} diff --git a/src/org/redkale/convert/ext/StringArraySimpledCoder.java b/src/org/redkale/convert/ext/StringArraySimpledCoder.java new file mode 100644 index 000000000..c07cbc979 --- /dev/null +++ b/src/org/redkale/convert/ext/StringArraySimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class StringArraySimpledCoder extends SimpledCoder { + + public static final StringArraySimpledCoder instance = new StringArraySimpledCoder(); + + @Override + public void convertTo(W out, String[] values) { + if (values == null) { + out.writeNull(); + return; + } + out.writeArrayB(values.length); + boolean flag = false; + for (String v : values) { + if (flag) out.writeArrayMark(); + out.writeString(v); + flag = true; + } + out.writeArrayE(); + } + + @Override + public String[] convertFrom(R in) { + int len = in.readArrayB(); + if (len == Reader.SIGN_NULL) { + return null; + } else if (len == Reader.SIGN_NOLENGTH) { + int size = 0; + String[] data = new String[8]; + while (in.hasNext()) { + if (size >= data.length) { + String[] newdata = new String[data.length + 4]; + System.arraycopy(data, 0, newdata, 0, size); + data = newdata; + } + data[size++] = in.readString(); + } + in.readArrayE(); + String[] newdata = new String[size]; + System.arraycopy(data, 0, newdata, 0, size); + return newdata; + } else { + String[] values = new String[len]; + for (int i = 0; i < values.length; i++) { + values[i] = in.readString(); + } + in.readArrayE(); + return values; + } + } + +} diff --git a/src/org/redkale/convert/ext/StringSimpledCoder.java b/src/org/redkale/convert/ext/StringSimpledCoder.java new file mode 100644 index 000000000..189e557ec --- /dev/null +++ b/src/org/redkale/convert/ext/StringSimpledCoder.java @@ -0,0 +1,32 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class StringSimpledCoder extends SimpledCoder { + + public static final StringSimpledCoder instance = new StringSimpledCoder(); + + @Override + public void convertTo(W out, String value) { + out.writeString(value); + } + + @Override + public String convertFrom(R in) { + return in.readString(); + } + +} diff --git a/src/org/redkale/convert/ext/TypeSimpledCoder.java b/src/org/redkale/convert/ext/TypeSimpledCoder.java new file mode 100644 index 000000000..f1a93b4e4 --- /dev/null +++ b/src/org/redkale/convert/ext/TypeSimpledCoder.java @@ -0,0 +1,42 @@ +/* + * 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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.Writer; +import org.redkale.convert.SimpledCoder; + +/** + * + * @author zhangjx + * @param + * @param + */ +public class TypeSimpledCoder extends SimpledCoder { + + public static final TypeSimpledCoder instance = new TypeSimpledCoder(); + + @Override + public void convertTo(final W out, final Class value) { + if (value == null) { + out.writeNull(); + } else { + out.writeSmallString(value.getName()); + } + } + + @Override + public Class convertFrom(R in) { + String str = in.readSmallString(); + if (str == null) return null; + try { + return Class.forName(str); + } catch (Exception e) { + return null; + } + } + +} diff --git a/src/org/redkale/convert/json/InetAddressJsonSimpledCoder.java b/src/org/redkale/convert/json/InetAddressJsonSimpledCoder.java new file mode 100644 index 000000000..2720284fd --- /dev/null +++ b/src/org/redkale/convert/json/InetAddressJsonSimpledCoder.java @@ -0,0 +1,68 @@ +/* + * 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.convert.json; + +import java.net.*; +import org.redkale.convert.*; +import org.redkale.convert.ext.*; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class InetAddressJsonSimpledCoder extends SimpledCoder { + + public static final InetAddressJsonSimpledCoder instance = new InetAddressJsonSimpledCoder(); + + @Override + public void convertTo(JsonWriter out, InetAddress value) { + if (value == null) { + out.writeNull(); + return; + } + StringSimpledCoder.instance.convertTo(out, value.getHostAddress()); + } + + @Override + public InetAddress convertFrom(JsonReader in) { + String str = StringSimpledCoder.instance.convertFrom(in); + if (str == null) return null; + try { + return InetAddress.getByName(str); + } catch (Exception ex) { + return null; + } + } + + public final static class InetSocketAddressJsonSimpledCoder extends SimpledCoder { + + public static final InetSocketAddressJsonSimpledCoder instance = new InetSocketAddressJsonSimpledCoder(); + + @Override + public void convertTo(JsonWriter out, InetSocketAddress value) { + if (value == null) { + out.writeNull(); + return; + } + StringSimpledCoder.instance.convertTo(out, value.getHostString() + ":" + value.getPort()); + } + + @Override + public InetSocketAddress convertFrom(JsonReader in) { + String str = StringSimpledCoder.instance.convertFrom(in); + if (str == null) return null; + try { + int pos = str.indexOf(':'); + return new InetSocketAddress(str.substring(0, pos), Integer.parseInt(str.substring(pos + 1))); + } catch (Exception ex) { + return null; + } + } + + } +} diff --git a/src/org/redkale/convert/json/JsonByteBufferWriter.java b/src/org/redkale/convert/json/JsonByteBufferWriter.java new file mode 100644 index 000000000..20835b1a9 --- /dev/null +++ b/src/org/redkale/convert/json/JsonByteBufferWriter.java @@ -0,0 +1,348 @@ +/* + * 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.convert.json; + +import java.nio.*; +import java.nio.charset.*; +import java.util.*; +import java.util.function.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class JsonByteBufferWriter extends JsonWriter { + + private static final Charset UTF8 = Charset.forName("UTF-8"); + + private final Charset charset; + + private final Supplier supplier; + + private ByteBuffer[] buffers; + + private int index; + + protected JsonByteBufferWriter(Supplier supplier) { + this(null, supplier); + } + + protected JsonByteBufferWriter(Charset charset, Supplier supplier) { + this.charset = UTF8.equals(charset) ? null : charset; + this.supplier = supplier; + } + + @Override + public JsonByteBufferWriter setTiny(boolean tiny) { + this.tiny = tiny; + return this; + } + + @Override + protected boolean recycle() { + this.index = 0; + this.buffers = null; + return false; + } + + @Override + public ByteBuffer[] toBuffers() { + if (buffers == null) return new ByteBuffer[0]; + for (int i = index; i < this.buffers.length; i++) { + ByteBuffer buf = this.buffers[i]; + if (buf.position() != 0) buf.flip(); + } + return this.buffers; + } + + @Override + public int count() { + if (this.buffers == null) return 0; + int len = 0; + for (ByteBuffer buffer : buffers) { + len += buffer.remaining(); + } + return len; + } + + private int expand(final int byteLength) { + if (this.buffers == null) { + this.index = 0; + this.buffers = new ByteBuffer[]{supplier.get()}; + } + ByteBuffer buffer = this.buffers[index]; + if (!buffer.hasRemaining()) { + buffer.flip(); + buffer = supplier.get(); + ByteBuffer[] bufs = new ByteBuffer[this.buffers.length + 1]; + System.arraycopy(this.buffers, 0, bufs, 0, this.buffers.length); + bufs[this.buffers.length] = buffer; + this.buffers = bufs; + this.index++; + } + int len = buffer.remaining(); + int size = 0; + while (len < byteLength) { + buffer = supplier.get(); + ByteBuffer[] bufs = new ByteBuffer[this.buffers.length + 1]; + System.arraycopy(this.buffers, 0, bufs, 0, this.buffers.length); + bufs[this.buffers.length] = buffer; + this.buffers = bufs; + len += buffer.remaining(); + size++; + } + return size; + } + + @Override + public void writeTo(final char ch) { + if (ch > Byte.MAX_VALUE) throw new RuntimeException("writeTo char(int.value = " + (int) ch + ") must be less 127"); + expand(1); + this.buffers[index].put((byte) ch); + } + + @Override + public void writeTo(final char[] chs, final int start, final int len) { + writeTo(-1, false, chs, start, len); + } + + private void writeTo(int expandsize, final boolean quote, final char[] chs, final int start, final int len) { + int byteLength = quote ? 2 : 0; + ByteBuffer bb = null; + if (charset == null) { + byteLength += encodeUTF8Length(chs, start, len); + } else { + bb = charset.encode(CharBuffer.wrap(chs, start, len)); + byteLength += bb.remaining(); + } + if (expandsize < 0) expandsize = expand(byteLength); + if (expandsize == 0) { // 只需要一个buffer + final ByteBuffer buffer = this.buffers[index]; + if (quote) buffer.put((byte) '"'); + + if (charset == null) { //UTF-8 + final int limit = start + len; + for (int i = start; i < limit; i++) { + char c = chs[i]; + if (c < 0x80) { + buffer.put((byte) c); + } else if (c < 0x800) { + buffer.put((byte) (0xc0 | (c >> 6))); + buffer.put((byte) (0x80 | (c & 0x3f))); + } else { + buffer.put((byte) (0xe0 | ((c >> 12)))); + buffer.put((byte) (0x80 | ((c >> 6) & 0x3f))); + buffer.put((byte) (0x80 | (c & 0x3f))); + } + } + } else { + buffer.put(bb); + } + + if (quote) buffer.put((byte) '"'); + return; + } + ByteBuffer buffer = this.buffers[index]; + if (quote) { + if (!buffer.hasRemaining()) buffer = nextByteBuffer(); + buffer.put((byte) '"'); + } + if (charset == null) { //UTF-8 + final int limit = start + len; + for (int i = start; i < limit; i++) { + buffer = putChar(buffer, chs[i]); + } + } else { + while (bb.hasRemaining()) { + if (!buffer.hasRemaining()) buffer = nextByteBuffer(); + buffer.put(bb.get()); + } + } + if (quote) { + if (!buffer.hasRemaining()) buffer = nextByteBuffer(); + buffer.put((byte) '"'); + } + } + + private ByteBuffer putChar(ByteBuffer buffer, char c) { + if (c < 0x80) { + if (!buffer.hasRemaining()) buffer = nextByteBuffer(); + buffer.put((byte) c); + } else if (c < 0x800) { + if (!buffer.hasRemaining()) buffer = nextByteBuffer(); + buffer.put((byte) (0xc0 | (c >> 6))); + if (!buffer.hasRemaining()) buffer = nextByteBuffer(); + buffer.put((byte) (0x80 | (c & 0x3f))); + } else { + if (!buffer.hasRemaining()) buffer = nextByteBuffer(); + buffer.put((byte) (0xe0 | ((c >> 12)))); + if (!buffer.hasRemaining()) buffer = nextByteBuffer(); + buffer.put((byte) (0x80 | ((c >> 6) & 0x3f))); + if (!buffer.hasRemaining()) buffer = nextByteBuffer(); + buffer.put((byte) (0x80 | (c & 0x3f))); + } + return buffer; + } + + private ByteBuffer nextByteBuffer() { + this.buffers[this.index].flip(); + return this.buffers[++this.index]; + } + + private static int encodeUTF8Length(final char[] text, final int start, final int len) { + char c; + int size = 0; + final char[] chars = text; + final int limit = start + len; + for (int i = start; i < limit; i++) { + c = chars[i]; + size += (c < 0x80 ? 1 : (c < 0x800 ? 2 : 3)); + } + return size; + } + + private static int encodeEscapeUTF8Length(final char[] text, final int start, final int len) { + char c; + int size = 0; + final char[] chars = text; + final int limit = start + len; + for (int i = start; i < limit; i++) { + c = chars[i]; + switch (c) { + case '\n': size += 2; + break; + case '\r': size += 2; + break; + case '\t': size += 2; + break; + case '\\': size += 2; + break; + case '"': size += 2; + break; + default: + size += (c < 0x80 ? 1 : (c < 0x800 ? 2 : 3)); + break; + } + } + return size; + } + + /** + * 注意: 该String值不能为null且不会进行转义, 只用于不含需要转义字符的字符串,例如enum、double、BigInteger转换的String + * + * @param quote + * @param value + */ + @Override + public void writeTo(final boolean quote, final String value) { + char[] chs = Utility.charArray(value); + writeTo(-1, quote, chs, 0, chs.length); + } + + @Override + public void writeString(String value) { + if (value == null) { + writeNull(); + return; + } + final char[] chs = Utility.charArray(value); + int len = 0; + for (char ch : chs) { + switch (ch) { + case '\n': len += 2; + break; + case '\r': len += 2; + break; + case '\t': len += 2; + break; + case '\\': len += 2; + break; + case '"': len += 2; + break; + default: len++; + break; + } + } + if (len == chs.length) { + writeTo(-1, true, chs, 0, len); + return; + } + int expandsize = -1; + if (this.charset == null) { //UTF-8 + final int byteLength = 2 + encodeEscapeUTF8Length(chs, 0, chs.length); + expandsize = expand(byteLength); + if (expandsize == 0) { // 只需要一个buffer + final ByteBuffer buffer = this.buffers[index]; + buffer.put((byte) '"'); + for (char c : chs) { + switch (c) { + case '\n': buffer.put((byte) '\\').put((byte) 'n'); + break; + case '\r': buffer.put((byte) '\\').put((byte) 'r'); + break; + case '\t': buffer.put((byte) '\\').put((byte) 't'); + break; + case '\\': buffer.put((byte) '\\').put((byte) '\\'); + break; + case '"': buffer.put((byte) '\\').put((byte) '"'); + break; + default: + if (c < 0x80) { + buffer.put((byte) c); + } else if (c < 0x800) { + buffer.put((byte) (0xc0 | (c >> 6))); + buffer.put((byte) (0x80 | (c & 0x3f))); + } else { + buffer.put((byte) (0xe0 | ((c >> 12)))); + buffer.put((byte) (0x80 | ((c >> 6) & 0x3f))); + buffer.put((byte) (0x80 | (c & 0x3f))); + } + break; + } + } + buffer.put((byte) '"'); + return; + } + } + StringBuilder sb = new StringBuilder(len); + for (char ch : chs) { + switch (ch) { + case '\n': sb.append("\\n"); + break; + case '\r': sb.append("\\r"); + break; + case '\t': sb.append("\\t"); + break; + case '\\': sb.append("\\\\"); + break; + case '"': sb.append("\\\""); + break; + default: sb.append(ch); + break; + } + } + char[] cs = Utility.charArray(sb); + writeTo(expandsize, true, cs, 0, sb.length()); + } + + @Override + public void writeField(boolean comma, Attribute attribute) { + if (comma) writeTo(','); + writeTo(true, attribute.field()); + writeTo(':'); + } + + @Override + public void writeSmallString(String value) { + writeTo(false, value); + } + + @Override + public String toString() { + return Objects.toString(this); + } +} diff --git a/src/org/redkale/convert/json/JsonConvert.java b/src/org/redkale/convert/json/JsonConvert.java new file mode 100644 index 000000000..d4d1786c8 --- /dev/null +++ b/src/org/redkale/convert/json/JsonConvert.java @@ -0,0 +1,137 @@ +/* + * 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.convert.json; + +import java.lang.reflect.*; +import java.nio.*; +import java.nio.charset.*; +import java.util.function.*; +import org.redkale.convert.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public final class JsonConvert extends Convert { + + public static final Type TYPE_MAP_STRING_STRING = new TypeToken>() { + }.getType(); + + private static final ObjectPool readerPool = JsonReader.createPool(Integer.getInteger("convert.json.pool.size", 16)); + + private static final ObjectPool writerPool = JsonWriter.createPool(Integer.getInteger("convert.json.pool.size", 16)); + + private final boolean tiny; + + protected JsonConvert(JsonFactory factory, boolean tiny) { + super(factory); + this.tiny = tiny; + } + + public JsonByteBufferWriter pollJsonWriter(final Supplier supplier) { + return new JsonByteBufferWriter(supplier).setTiny(tiny); + } + + public JsonByteBufferWriter pollJsonWriter(final Charset charset, final Supplier supplier) { + return new JsonByteBufferWriter(charset, supplier).setTiny(tiny); + } + + public JsonWriter pollJsonWriter() { + return writerPool.get().setTiny(tiny); + } + + public void offerJsonWriter(JsonWriter out) { + if (out != null) writerPool.offer(out); + } + + @Override + public JsonFactory getFactory() { + return (JsonFactory) factory; + } + + public T convertFrom(final Type type, final String text) { + if (text == null) return null; + return convertFrom(type, Utility.charArray(text)); + } + + public T convertFrom(final Type type, final char[] text) { + if (text == null) return null; + return convertFrom(type, text, 0, text.length); + } + + public T convertFrom(final Type type, final char[] text, int start, int len) { + if (text == null || type == null) return null; + final JsonReader in = readerPool.get(); + in.setText(text, start, len); + T rs = (T) factory.loadDecoder(type).convertFrom(in); + readerPool.offer(in); + return rs; + } + + public String convertTo(final Type type, Object value) { + if (type == null) return null; + if (value == null) return "null"; + final JsonWriter out = writerPool.get().setTiny(tiny); + factory.loadEncoder(type).convertTo(out, value); + String result = out.toString(); + writerPool.offer(out); + return result; + } + + public String convertTo(Object value) { + if (value == null) return "null"; + return convertTo(value.getClass(), value); + } + + public void convertTo(final JsonWriter out, final Type type, Object value) { + if (type == null) return; + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(type).convertTo(out, value); + } + } + + public void convertTo(final JsonWriter out, Object value) { + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(out, value); + } + } + + public ByteBuffer[] convertTo(final Supplier supplier, final Type type, Object value) { + return convertTo(null, supplier, type, value); + } + + public ByteBuffer[] convertTo(final Charset charset, final Supplier supplier, final Type type, Object value) { + if (supplier == null || type == null) return null; + JsonByteBufferWriter out = new JsonByteBufferWriter(charset, supplier); + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(type).convertTo(out, value); + } + return out.toBuffers(); + } + + public ByteBuffer[] convertTo(final Supplier supplier, Object value) { + return convertTo(null, supplier, value); + } + + public ByteBuffer[] convertTo(final Charset charset, final Supplier supplier, Object value) { + if (supplier == null) return null; + JsonByteBufferWriter out = new JsonByteBufferWriter(charset, supplier); + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(out, value); + } + return out.toBuffers(); + } +} diff --git a/src/org/redkale/convert/json/JsonFactory.java b/src/org/redkale/convert/json/JsonFactory.java new file mode 100644 index 000000000..435ab3496 --- /dev/null +++ b/src/org/redkale/convert/json/JsonFactory.java @@ -0,0 +1,60 @@ +/* + * 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.convert.json; + +import org.redkale.convert.ConvertType; +import org.redkale.convert.Factory; +import java.io.Serializable; +import java.net.*; + +/** + * + * @author zhangjx + */ +public final class JsonFactory extends Factory { + + private static final JsonFactory instance = new JsonFactory(null, Boolean.getBoolean("convert.json.tiny")); + + static { + instance.register(InetAddress.class, InetAddressJsonSimpledCoder.instance); + instance.register(InetSocketAddress.class, InetAddressJsonSimpledCoder.InetSocketAddressJsonSimpledCoder.instance); + instance.register(Serializable.class, instance.loadEncoder(Object.class)); + } + + private JsonFactory(JsonFactory parent, boolean tiny) { + super(parent, tiny); + } + + public static JsonFactory root() { + return instance; + } + + @Override + public final JsonConvert getConvert() { + if (convert == null) convert = new JsonConvert(this, tiny); + return (JsonConvert) convert; + } + + @Override + public JsonFactory createChild() { + return new JsonFactory(this, this.tiny); + } + + @Override + public JsonFactory createChild(boolean tiny) { + return new JsonFactory(this, tiny); + } + + @Override + public ConvertType getConvertType() { + return ConvertType.JSON; + } + + @Override + public boolean isReversible() { + return false; + } +} diff --git a/src/org/redkale/convert/json/JsonReader.java b/src/org/redkale/convert/json/JsonReader.java new file mode 100644 index 000000000..4a61d6c85 --- /dev/null +++ b/src/org/redkale/convert/json/JsonReader.java @@ -0,0 +1,577 @@ +/* + * 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.convert.json; + +import java.util.concurrent.atomic.*; +import java.util.function.*; +import org.redkale.convert.*; +import static org.redkale.convert.Reader.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class JsonReader implements Reader { + + private int position = -1; + + private char[] text; + + private int limit; + + public static ObjectPool createPool(int max) { + return new ObjectPool(max, new Creator() { //为了兼容 JDK 6 + + @Override + public JsonReader create(Object... params) { + return new JsonReader(); + } + }, null, new Predicate() { + + @Override + public boolean test(JsonReader t) { + return t.recycle(); + } + }); + } + + public JsonReader() { + } + + public JsonReader(String json) { + setText(Utility.charArray(json)); + } + + public JsonReader(char[] text) { + setText(text, 0, text.length); + } + + public JsonReader(char[] text, int start, int len) { + setText(text, start, len); + } + + public final void setText(String text) { + JsonReader.this.setText(Utility.charArray(text)); + } + + public final void setText(char[] text) { + setText(text, 0, text.length); + } + + public final void setText(char[] text, int start, int len) { + this.text = text; + this.position = start - 1; + this.limit = start + len - 1; + } + + protected boolean recycle() { + this.position = -1; + this.limit = -1; + this.text = null; + return true; + } + + public void close() { + this.recycle(); + } + + /** + * 找到指定的属性值 例如: {id : 1, data : { name : 'a', items : [1,2,3]}} seek('data.items') 直接跳转到 [1,2,3]; + * + * @param key + */ + public final void seek(String key) { + if (key == null || key.length() < 1) return; + final String[] keys = key.split("\\."); + nextGoodChar(); //读掉 { [ + for (String key1 : keys) { + while (this.hasNext()) { + String field = this.readSmallString(); + skipBlank(); + if (key1.equals(field)) break; + skipValue(); + } + } + + } + + /** + * 跳过属性的值 + */ + @Override + public final void skipValue() { + final char ch = nextGoodChar(); + if (ch == '"' || ch == '\'') { + backChar(ch); + readString(); + } else if (ch == '{') { + while (hasNext()) { + this.readSmallString(); //读掉field + this.skipBlank(); + this.skipValue(); + } + } else if (ch == '[') { + while (hasNext()) { + this.skipValue(); + } + } else { + char c; + for (;;) { + c = nextChar(); + if (c <= ' ') return; + if (c == '}' || c == ']' || c == ',' || c == ':') { + backChar(c); + return; + } + } + } + } + + /** + * 读取下一个字符, 不跳过空白字符 + * + * @return + */ + protected char nextChar() { + return this.text[++this.position]; + } + + /** + * 跳过空白字符, 返回一个非空白字符 + * + * @return + */ + protected char nextGoodChar() { + char c = nextChar(); + if (c > ' ') return c; + for (;;) { + c = nextChar(); + if (c > ' ') return c; + } + } + + /** + * 回退最后读取的字符 + * + * @param ch + */ + protected void backChar(char ch) { + this.position--; + } + + /** + * 判断下一个非空白字符是否为{ + * + */ + @Override + public int readObjectB() { + char ch = this.text[++this.position]; + if (ch == '{') return SIGN_NOLENGTH; + if (ch <= ' ') { + for (;;) { + ch = this.text[++this.position]; + if (ch > ' ') break; + } + if (ch == '{') return SIGN_NOLENGTH; + } + if (ch == 'n' && text[++position] == 'u' && text[++position] == 'l' && text[++position] == 'l') return SIGN_NULL; + if (ch == 'N' && text[++position] == 'U' && text[++position] == 'L' && text[++position] == 'L') return SIGN_NULL; + throw new ConvertException("a json object text must begin with '{' (position = " + position + ") but '" + ch + "' in (" + new String(this.text) + ")"); + } + + @Override + public void readObjectE() { + } + + /** + * 判断下一个非空白字符是否为{ + * + */ + @Override + public int readMapB() { + return readArrayB(); + } + + @Override + public void readMapE() { + } + + /** + * 判断下一个非空白字符是否为[ + * + * @return + */ + @Override + public int readArrayB() { + char ch = this.text[++this.position]; + if (ch == '[') return SIGN_NOLENGTH; + if (ch == '{') return SIGN_NOLENGTH; + if (ch <= ' ') { + for (;;) { + ch = this.text[++this.position]; + if (ch > ' ') break; + } + if (ch == '[') return SIGN_NOLENGTH; + if (ch == '{') return SIGN_NOLENGTH; + } + if (ch == 'n' && text[++position] == 'u' && text[++position] == 'l' && text[++position] == 'l') return SIGN_NULL; + if (ch == 'N' && text[++position] == 'U' && text[++position] == 'L' && text[++position] == 'L') return SIGN_NULL; + throw new ConvertException("a json array text must begin with '[' (position = " + position + ") but '" + ch + "' in (" + new String(this.text) + ")"); + } + + @Override + public void readArrayE() { + } + + /** + * 判断下一个非空白字符是否: + */ + @Override + public void skipBlank() { + char ch = this.text[++this.position]; + if (ch == ':') return; + if (ch <= ' ') { + for (;;) { + ch = this.text[++this.position]; + if (ch > ' ') break; + } + if (ch == ':') return; + } + throw new ConvertException("'" + new String(text) + "'expected a ':' but '" + ch + "'(position = " + position + ") in (" + new String(this.text) + ")"); + } + + /** + * 判断对象是否存在下一个属性或者数组是否存在下一个元素 + * + * @return + */ + @Override + public boolean hasNext() { + char ch = this.text[++this.position]; + if (ch == ',') return true; + if (ch == '}' || ch == ']') return false; + if (ch <= ' ') { + for (;;) { + ch = this.text[++this.position]; + if (ch > ' ') break; + } + if (ch == ',') return true; + if (ch == '}' || ch == ']') return false; + } + this.position--; + return true; + } + + @Override + public String readClassName() { + return null; + } + + @Override + public String readSmallString() { + final int eof = this.limit; + if (this.position == eof) return null; + final char[] text0 = this.text; + int currpos = this.position; + char ch = text0[++currpos]; + if (ch <= ' ') { + for (;;) { + ch = text0[++currpos]; + if (ch > ' ') break; + } + } + if (ch == '"' || ch == '\'') { + final char quote = ch; + final int start = currpos + 1; + for (;;) { + ch = text0[++currpos]; + if (ch == '\\') { + this.position = currpos - 1; + return readEscapeValue(quote, start); + } else if (ch == quote) { + break; + } + } + this.position = currpos; + char[] chs = new char[currpos - start]; + System.arraycopy(text0, start, chs, 0, chs.length); + return new String(chs); + } else { + int start = currpos; + for (;;) { + if (currpos == eof) break; + ch = text0[++currpos]; + if (ch == ',' || ch == ']' || ch == '}' || ch <= ' ' || ch == ':') break; + } + int len = currpos - start; + if (len < 1) { + this.position = currpos; + return String.valueOf(ch); + } + this.position = currpos - 1; + if (len == 4 && text0[start] == 'n' && text0[start + 1] == 'u' && text0[start + 2] == 'l' && text0[start + 3] == 'l') return null; + return new String(text0, start, len); + } + } + + /** + * 读取一个int + * + * @return + */ + @Override + public final int readInt() { + final char[] text0 = this.text; + final int eof = this.limit; + int currpos = this.position; + char firstchar = text0[++currpos]; + if (firstchar <= ' ') { + for (;;) { + firstchar = text0[++currpos]; + if (firstchar > ' ') break; + } + } + if (firstchar == '"' || firstchar == '\'') { + firstchar = text0[++currpos]; + if (firstchar == '"' || firstchar == '\'') { + this.position = currpos; + return 0; + } + } + int value = 0; + final boolean negative = firstchar == '-'; + if (!negative) { + if (firstchar < '0' || firstchar > '9') throw new NumberFormatException("illegal escape(" + firstchar + ") (position = " + currpos + ") in (" + new String(this.text) + ")"); + value = firstchar - '0'; + } + for (;;) { + if (currpos == eof) break; + char ch = text0[++currpos]; + if (ch >= '0' && ch <= '9') { + value = (value << 3) + (value << 1) + (ch - '0'); + } else if (ch == '"' || ch == '\'') { + } else if (ch == ',' || ch == '}' || ch == ']' || ch <= ' ' || ch == ':') { + break; + } else { + throw new NumberFormatException("illegal escape(" + ch + ") (position = " + currpos + ") in (" + new String(this.text) + ")"); + } + } + this.position = currpos - 1; + return negative ? -value : value; + } + + /** + * 读取一个long + * + * @return + */ + @Override + public final long readLong() { + final char[] text0 = this.text; + final int eof = this.limit; + int currpos = this.position; + char firstchar = text0[++currpos]; + if (firstchar <= ' ') { + for (;;) { + firstchar = text0[++currpos]; + if (firstchar > ' ') break; + } + } + if (firstchar == '"' || firstchar == '\'') { + firstchar = text0[++currpos]; + if (firstchar == '"' || firstchar == '\'') { + this.position = currpos; + return 0L; + } + } + long value = 0; + final boolean negative = firstchar == '-'; + if (!negative) { + if (firstchar < '0' || firstchar > '9') throw new NumberFormatException("illegal escape(" + firstchar + ") (position = " + currpos + ") in (" + new String(this.text) + ")"); + value = firstchar - '0'; + } + for (;;) { + if (currpos == eof) break; + char ch = text0[++currpos]; + if (ch >= '0' && ch <= '9') { + value = (value << 3) + (value << 1) + (ch - '0'); + } else if (ch == '"' || ch == '\'') { + } else if (ch == ',' || ch == '}' || ch == ']' || ch <= ' ' || ch == ':') { + break; + } else { + throw new NumberFormatException("illegal escape(" + ch + ") (position = " + currpos + ") but '" + ch + "' in (" + new String(this.text) + ")"); + } + } + this.position = currpos - 1; + return negative ? -value : value; + } + + @Override + public DeMember readField(final AtomicInteger index, final DeMember[] members) { + final String exceptedfield = this.readSmallString(); + final int len = members.length; + int v = index.get(); + if (v >= len) { + v = 0; + index.set(0); + } + for (int k = v; k < len; k++) { + if (exceptedfield.equals(members[k].getAttribute().field())) { + index.set(k); + return members[k]; + } + } + for (int k = 0; k < v; k++) { + if (exceptedfield.equals(members[k].getAttribute().field())) { + index.set(k); + return members[k]; + } + } + return null; + //if (result == null && len == 1 && text0[start] == '@') return REFER; + } +//------------------------------------------------------------ + + @Override + public boolean readBoolean() { + return "true".equalsIgnoreCase(this.readSmallString()); + } + + @Override + public byte readByte() { + return (byte) readInt(); + } + + @Override + public char readChar() { + return (char) readInt(); + } + + @Override + public short readShort() { + return (short) readInt(); + } + + @Override + public float readFloat() { + String chars = readSmallString(); + if (chars == null || chars.isEmpty()) return 0.f; + return Float.parseFloat(chars); + } + + @Override + public double readDouble() { + String chars = readSmallString(); + if (chars == null || chars.isEmpty()) return 0.0; + return Double.parseDouble(chars); + } + + /** + * 读取字符串, 必须是"或者'包围的字符串值 + * + * @return + */ + @Override + public String readString() { + final char[] text0 = this.text; + int currpos = this.position; + char expected = text0[++currpos]; + if (expected <= ' ') { + for (;;) { + expected = text0[++currpos]; + if (expected > ' ') break; + } + } + if (expected != '"' && expected != '\'') { + if (expected == 'n' && text0.length > currpos + 3) { + if (text0[++currpos] == 'u' && text0[++currpos] == 'l' && text0[++currpos] == 'l') { + this.position = currpos; + if (text0.length > currpos + 4) { + char ch = text0[currpos + 1]; + if (ch == ',' || ch <= ' ' || ch == '}' || ch == ']' || ch == ':') return null; + } else { + return null; + } + } + } else { + final int start = currpos; + for (;;) { + char ch = text0[currpos]; + if (ch == ',' || ch <= ' ' || ch == '}' || ch == ']' || ch == ':') break; + currpos++; + } + if (currpos == start) throw new ConvertException("expected a string after a key but '" + text0[position] + "' (position = " + position + ") in (" + new String(this.text) + ")"); + this.position = currpos - 1; + return new String(text0, start, currpos - start); + } + this.position = currpos; + throw new ConvertException("expected a ':' after a key but '" + text0[position] + "' (position = " + position + ") in (" + new String(this.text) + ")"); + } + final int start = ++currpos; + for (;;) { + char ch = text0[currpos]; + if (ch == expected) { + break; + } else if (ch == '\\') { + this.position = currpos - 1; + return readEscapeValue(expected, start); + } + currpos++; + } + this.position = currpos; + return new String(text0, start, currpos - start); + } + + private String readEscapeValue(final char expected, int start) { + StringBuilder array = new StringBuilder(); + final char[] text0 = this.text; + int pos = this.position; + array.append(text0, start, pos + 1 - start); + char c; + for (;;) { + c = text0[++pos]; + if (c == expected) { + this.position = pos; + return array.toString(); + } else if (c == '\\') { + c = text0[++pos]; + switch (c) { + case '"': + case '\'': + case '\\': + case '/': + array.append(c); + break; + case 'n': + array.append('\n'); + break; + case 'r': + array.append('\r'); + break; + case 'u': + array.append((char) Integer.parseInt(new String(new char[]{text0[++pos], text0[++pos], text0[++pos], text0[++pos]}), 16)); + break; + case 't': + array.append('\t'); + break; + case 'b': + array.append('\b'); + break; + case 'f': + array.append('\f'); + break; + default: + this.position = pos; + throw new ConvertException("illegal escape(" + c + ") (position = " + this.position + ") in (" + new String(this.text) + ")"); + } + } else { + array.append(c); + } + } + } + +} diff --git a/src/org/redkale/convert/json/JsonSimpledCoder.java b/src/org/redkale/convert/json/JsonSimpledCoder.java new file mode 100644 index 000000000..396cba71a --- /dev/null +++ b/src/org/redkale/convert/json/JsonSimpledCoder.java @@ -0,0 +1,17 @@ +/* + * 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.convert.json; + +import org.redkale.convert.SimpledCoder; + +/** + * + * @author zhangjx + * @param + */ +public abstract class JsonSimpledCoder extends SimpledCoder { + +} diff --git a/src/org/redkale/convert/json/JsonWriter.java b/src/org/redkale/convert/json/JsonWriter.java new file mode 100644 index 000000000..6766406af --- /dev/null +++ b/src/org/redkale/convert/json/JsonWriter.java @@ -0,0 +1,268 @@ +/* + * 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.convert.json; + +import java.nio.*; +import java.util.function.*; +import org.redkale.convert.*; +import org.redkale.util.*; + +/** + * + * writeTo系列的方法输出的字符不能含特殊字符 + * + * @author zhangjx + */ +public class JsonWriter implements Writer { + + private static final char[] CHARS_TUREVALUE = "true".toCharArray(); + + private static final char[] CHARS_FALSEVALUE = "false".toCharArray(); + + private static final int defaultSize = Integer.getInteger("convert.json.writer.buffer.defsize", 1024); + + private int count; + + private char[] content; + + protected boolean tiny; + + public static ObjectPool createPool(int max) { + return new ObjectPool(max, new Creator() { + + @Override + public JsonWriter create(Object... params) { + return new JsonWriter(); + } + }, null, new Predicate() { + + @Override + public boolean test(JsonWriter t) { + return t.recycle(); + } + }); + } + + public JsonWriter() { + this(defaultSize); + } + + public JsonWriter(int size) { + this.content = new char[size > 128 ? size : 128]; + } + + @Override + public boolean isTiny() { + return tiny; + } + + public JsonWriter setTiny(boolean tiny) { + this.tiny = tiny; + return this; + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + /** + * 返回指定至少指定长度的缓冲区 + * + * @param len + * @return + */ + private char[] expand(int len) { + int newcount = count + len; + if (newcount <= content.length) return content; + char[] newdata = new char[Math.max(content.length * 3 / 2, newcount)]; + System.arraycopy(content, 0, newdata, 0, count); + this.content = newdata; + return newdata; + } + + public void writeTo(final char ch) { //只能是 0 - 127 的字符 + expand(1); + content[count++] = ch; + } + + public void writeTo(final char[] chs, final int start, final int len) { //只能是 0 - 127 的字符 + expand(len); + System.arraycopy(chs, start, content, count, len); + count += len; + } + + /** + * 注意: 该String值不能为null且不会进行转义, 只用于不含需要转义字符的字符串,例如enum、double、BigInteger转换的String + * + * @param quote + * @param value + */ + public void writeTo(final boolean quote, final String value) { + int len = value.length(); + expand(len + (quote ? 2 : 0)); + if (quote) content[count++] = '"'; + value.getChars(0, len, content, count); + count += len; + if (quote) content[count++] = '"'; + } + + protected boolean recycle() { + this.count = 0; + if (this.content.length > defaultSize) { + this.content = new char[defaultSize]; + } + return true; + } + + public ByteBuffer[] toBuffers() { + return new ByteBuffer[]{ByteBuffer.wrap(Utility.encodeUTF8(content, 0, count))}; + } + + public int count() { + return this.count; + } + + @Override + public void writeString(String value) { + if (value == null) { + writeNull(); + return; + } + expand(value.length() * 2 + 2); + content[count++] = '"'; + for (char ch : Utility.charArray(value)) { + switch (ch) { + case '\n': content[count++] = '\\'; + content[count++] = 'n'; + break; + case '\r': content[count++] = '\\'; + content[count++] = 'r'; + break; + case '\t': content[count++] = '\\'; + content[count++] = 't'; + break; + case '\\': content[count++] = '\\'; + content[count++] = ch; + break; + case '"': content[count++] = '\\'; + content[count++] = ch; + break; + default: content[count++] = ch; + break; + } + } + content[count++] = '"'; + } + + @Override + public void writeField(boolean comma, Attribute attribute) { + if (comma) writeTo(','); + writeTo(true, attribute.field()); + writeTo(':'); + } + + @Override + public void writeSmallString(String value) { + writeTo(false, value); + } + + @Override + public String toString() { + return new String(content, 0, count); + } + + //---------------------------------------------------------------------------------------------- + public final void writeTo(final char... chs) { //只能是 0 - 127 的字符 + writeTo(chs, 0, chs.length); + } + + @Override + public final void writeBoolean(boolean value) { + writeTo(value ? CHARS_TUREVALUE : CHARS_FALSEVALUE); + } + + @Override + public final void writeByte(byte value) { + writeInt(value); + } + + @Override + public final void writeChar(char value) { + writeInt(value); + } + + @Override + public final void writeShort(short value) { + writeInt(value); + } + + @Override + public final void writeInt(int value) { + writeSmallString(String.valueOf(value)); + } + + @Override + public final void writeLong(long value) { + writeSmallString(String.valueOf(value)); + } + + @Override + public final void writeFloat(float value) { + writeSmallString(String.valueOf(value)); + } + + @Override + public final void writeDouble(double value) { + writeSmallString(String.valueOf(value)); + } + + @Override + public final void wirteClassName(String clazz) { + } + + @Override + public final void writeObjectB(int fieldCount, Object obj) { + writeTo('{'); + } + + @Override + public final void writeObjectE(Object obj) { + writeTo('}'); + } + + @Override + public final void writeNull() { + writeTo('n', 'u', 'l', 'l'); + } + + @Override + public final void writeArrayB(int size) { + writeTo('['); + } + + @Override + public final void writeArrayMark() { + writeTo(','); + } + + @Override + public final void writeArrayE() { + writeTo(']'); + } + + @Override + public final void writeMapB(int size) { + writeTo('{'); + } + + @Override + public final void writeMapMark() { + writeTo(':'); + } + + @Override + public final void writeMapE() { + writeTo('}'); + } +} diff --git a/src/org/redkale/net/Async.java b/src/org/redkale/net/Async.java new file mode 100644 index 000000000..89fbaa223 --- /dev/null +++ b/src/org/redkale/net/Async.java @@ -0,0 +1,25 @@ +/* + * 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.net; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 当Service是Remote模式时, 用该注解标注在方法上可使数据变成异步传输, 该注解只能标注在返回类型为void的public方法上 + * 不再起作用, 屏蔽掉 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +@Deprecated +public @interface Async { + +} diff --git a/src/org/redkale/net/AsyncConnection.java b/src/org/redkale/net/AsyncConnection.java new file mode 100644 index 000000000..b9ec916de --- /dev/null +++ b/src/org/redkale/net/AsyncConnection.java @@ -0,0 +1,570 @@ +/* + * 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.net; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.*; + +/** + * + * @author zhangjx + */ +public abstract class AsyncConnection implements AsynchronousByteChannel, AutoCloseable { + + protected Map attributes; + + public abstract boolean isTCP(); + + public abstract SocketAddress getRemoteAddress(); + + public abstract SocketAddress getLocalAddress(); + + public abstract int getReadTimeoutSecond(); + + public abstract int getWriteTimeoutSecond(); + + public abstract void setReadTimeoutSecond(int readTimeoutSecond); + + public abstract void setWriteTimeoutSecond(int writeTimeoutSecond); + + public final void write(ByteBuffer[] srcs, A attachment, CompletionHandler handler) { + write(srcs, 0, srcs.length, attachment, handler); + } + + protected abstract void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler handler); + + public void dispose() {//同close, 只是去掉throws IOException + try { + this.close(); + } catch (IOException io) { + } + } + + @Override + public void close() throws IOException { + if (attributes == null) return; + try { + for (Object obj : attributes.values()) { + if (obj instanceof AutoCloseable) ((AutoCloseable) obj).close(); + } + } catch (Exception io) { + } + } + + public void setAttribute(String name, Object value) { + if (attributes == null) attributes = new HashMap<>(); + attributes.put(name, value); + } + + @SuppressWarnings("unchecked") + public final T getAttribute(String name) { + return (T) (attributes == null ? null : attributes.get(name)); + } + + public final void removeAttribute(String name) { + if (attributes != null) attributes.remove(name); + } + + public final Map getAttributes() { + return attributes; + } + + public final void clearAttribute() { + if (attributes != null) attributes.clear(); + } + + //------------------------------------------------------------------------------------------------------------------------------ + public static AsyncConnection create(final String protocol, final SocketAddress address) throws IOException { + return create(protocol, address, 0, 0); + } + + /** + * 创建客户端连接 + * + * @param protocol + * @param address + * @param readTimeoutSecond0 + * @param writeTimeoutSecond0 + * @return + * @throws java.io.IOException + */ + public static AsyncConnection create(final String protocol, final SocketAddress address, + final int readTimeoutSecond0, final int writeTimeoutSecond0) throws IOException { + if ("TCP".equalsIgnoreCase(protocol)) { + AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(); + try { + channel.connect(address).get(3, TimeUnit.SECONDS); + } catch (Exception e) { + throw new IOException("AsyncConnection connect " + address, e); + } + return create(channel, address, readTimeoutSecond0, writeTimeoutSecond0); + } else if ("UDP".equalsIgnoreCase(protocol)) { + DatagramChannel channel = DatagramChannel.open(); + channel.configureBlocking(true); + channel.connect(address); + return create(channel, address, true, readTimeoutSecond0, writeTimeoutSecond0); + } else { + throw new RuntimeException("AsyncConnection not support protocol " + protocol); + } + } + + private static class BIOUDPAsyncConnection extends AsyncConnection { + + private int readTimeoutSecond; + + private int writeTimeoutSecond; + + private final DatagramChannel channel; + + private final SocketAddress remoteAddress; + + private final boolean client; + + public BIOUDPAsyncConnection(final DatagramChannel ch, SocketAddress addr, + final boolean client0, final int readTimeoutSecond0, final int writeTimeoutSecond0) { + this.channel = ch; + this.client = client0; + this.readTimeoutSecond = readTimeoutSecond0; + this.writeTimeoutSecond = writeTimeoutSecond0; + this.remoteAddress = addr; + } + + @Override + public void setReadTimeoutSecond(int readTimeoutSecond) { + this.readTimeoutSecond = readTimeoutSecond; + } + + @Override + public void setWriteTimeoutSecond(int writeTimeoutSecond) { + this.writeTimeoutSecond = writeTimeoutSecond; + } + + @Override + public int getReadTimeoutSecond() { + return this.readTimeoutSecond; + } + + @Override + public int getWriteTimeoutSecond() { + return this.writeTimeoutSecond; + } + + @Override + public final SocketAddress getRemoteAddress() { + return remoteAddress; + } + + @Override + public SocketAddress getLocalAddress() { + try { + return channel.getLocalAddress(); + } catch (IOException e) { + return null; + } + } + + @Override + protected void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler handler) { + try { + int rs = 0; + for (int i = offset; i < offset + length; i++) { + rs += channel.send(srcs[i], remoteAddress); + if(i != offset) Thread.sleep(10); + } + if (handler != null) handler.completed(rs, attachment); + } catch (Exception e) { + if (handler != null) handler.failed(e, attachment); + } + } + + @Override + public void read(ByteBuffer dst, A attachment, CompletionHandler handler) { + try { + int rs = channel.read(dst); + if (handler != null) handler.completed(rs, attachment); + } catch (IOException e) { + if (handler != null) handler.failed(e, attachment); + } + } + + @Override + public Future read(ByteBuffer dst) { + try { + int rs = channel.read(dst); + return new SimpleFuture(rs); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void write(ByteBuffer src, A attachment, CompletionHandler handler) { + try { + int rs = channel.send(src, remoteAddress); + if (handler != null) handler.completed(rs, attachment); + } catch (IOException e) { + if (handler != null) handler.failed(e, attachment); + } + } + + @Override + public Future write(ByteBuffer src) { + try { + int rs = channel.send(src, remoteAddress); + return new SimpleFuture(rs); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public final void close() throws IOException { + super.close(); + if (client) { + channel.close(); + } + } + + @Override + public final boolean isOpen() { + return channel.isOpen(); + } + + @Override + public final boolean isTCP() { + return false; + } + } + + public static AsyncConnection create(final DatagramChannel ch, SocketAddress addr, + final boolean client0, final int readTimeoutSecond0, final int writeTimeoutSecond0) { + return new BIOUDPAsyncConnection(ch, addr, client0, readTimeoutSecond0, writeTimeoutSecond0); + } + + private static class SimpleFuture implements Future { + + private final int rs; + + public SimpleFuture(int rs) { + this.rs = rs; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return true; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public Integer get() throws InterruptedException, ExecutionException { + return rs; + } + + @Override + public Integer get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return rs; + } + + } + + private static class BIOTCPAsyncConnection extends AsyncConnection { + + private int readTimeoutSecond; + + private int writeTimeoutSecond; + + private final Socket socket; + + private final ReadableByteChannel readChannel; + + private final WritableByteChannel writeChannel; + + private final SocketAddress remoteAddress; + + public BIOTCPAsyncConnection(final Socket socket, final SocketAddress addr0, final int readTimeoutSecond0, final int writeTimeoutSecond0) { + this.socket = socket; + ReadableByteChannel rc = null; + WritableByteChannel wc = null; + try { + socket.setSoTimeout(Math.max(readTimeoutSecond0, writeTimeoutSecond0)); + rc = Channels.newChannel(socket.getInputStream()); + wc = Channels.newChannel(socket.getOutputStream()); + } catch (IOException e) { + e.printStackTrace(); + } + this.readChannel = rc; + this.writeChannel = wc; + this.readTimeoutSecond = readTimeoutSecond0; + this.writeTimeoutSecond = writeTimeoutSecond0; + SocketAddress addr = addr0; + if (addr == null) { + try { + addr = socket.getRemoteSocketAddress(); + } catch (Exception e) { + //do nothing + } + } + this.remoteAddress = addr; + } + + @Override + public boolean isTCP() { + return true; + } + + @Override + public SocketAddress getRemoteAddress() { + return remoteAddress; + } + + @Override + public SocketAddress getLocalAddress() { + return socket.getLocalSocketAddress(); + } + + @Override + public int getReadTimeoutSecond() { + return readTimeoutSecond; + } + + @Override + public int getWriteTimeoutSecond() { + return writeTimeoutSecond; + } + + @Override + public void setReadTimeoutSecond(int readTimeoutSecond) { + this.readTimeoutSecond = readTimeoutSecond; + } + + @Override + public void setWriteTimeoutSecond(int writeTimeoutSecond) { + this.writeTimeoutSecond = writeTimeoutSecond; + } + + @Override + protected void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler handler) { + try { + int rs = 0; + for (int i = offset; i < offset + length; i++) { + rs += writeChannel.write(srcs[i]); + } + if (handler != null) handler.completed(rs, attachment); + } catch (IOException e) { + if (handler != null) handler.failed(e, attachment); + } + } + + @Override + public void read(ByteBuffer dst, A attachment, CompletionHandler handler) { + try { + int rs = readChannel.read(dst); + if (handler != null) handler.completed(rs, attachment); + } catch (IOException e) { + if (handler != null) handler.failed(e, attachment); + } + } + + @Override + public Future read(ByteBuffer dst) { + try { + int rs = readChannel.read(dst); + return new SimpleFuture(rs); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void write(ByteBuffer src, A attachment, CompletionHandler handler) { + try { + int rs = writeChannel.write(src); + if (handler != null) handler.completed(rs, attachment); + } catch (IOException e) { + if (handler != null) handler.failed(e, attachment); + } + } + + @Override + public Future write(ByteBuffer src) { + try { + int rs = writeChannel.write(src); + return new SimpleFuture(rs); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() throws IOException { + super.close(); + this.socket.close(); + } + + @Override + public boolean isOpen() { + return !socket.isClosed(); + } + } + + /** + * 通常用于 ssl socket + * @param socket + * @return + */ + public static AsyncConnection create(final Socket socket) { + return create(socket, null, 0, 0); + } + + public static AsyncConnection create(final Socket socket, final SocketAddress addr0, final int readTimeoutSecond0, final int writeTimeoutSecond0) { + return new BIOTCPAsyncConnection(socket, addr0, readTimeoutSecond0, writeTimeoutSecond0); + } + + private static class AIOTCPAsyncConnection extends AsyncConnection { + + private int readTimeoutSecond; + + private int writeTimeoutSecond; + + private final AsynchronousSocketChannel channel; + + private final SocketAddress remoteAddress; + + public AIOTCPAsyncConnection(final AsynchronousSocketChannel ch, final SocketAddress addr0, final int readTimeoutSecond0, final int writeTimeoutSecond0) { + this.channel = ch; + this.readTimeoutSecond = readTimeoutSecond0; + this.writeTimeoutSecond = writeTimeoutSecond0; + SocketAddress addr = addr0; + if (addr == null) { + try { + addr = ch.getRemoteAddress(); + } catch (Exception e) { + //do nothing + } + } + this.remoteAddress = addr; + } + + @Override + public void read(ByteBuffer dst, A attachment, CompletionHandler handler) { + if (readTimeoutSecond > 0) { + channel.read(dst, readTimeoutSecond, TimeUnit.SECONDS, attachment, handler); + } else { + channel.read(dst, attachment, handler); + } + } + + @Override + public void write(ByteBuffer src, A attachment, CompletionHandler handler) { + if (writeTimeoutSecond > 0) { + channel.write(src, writeTimeoutSecond, TimeUnit.SECONDS, attachment, handler); + } else { + channel.write(src, attachment, handler); + } + } + + @Override + public void write(ByteBuffer[] srcs, int offset, int length, A attachment, CompletionHandler handler) { + channel.write(srcs, offset, length, writeTimeoutSecond > 0 ? writeTimeoutSecond : 60, TimeUnit.SECONDS, + attachment, new CompletionHandler() { + + @Override + public void completed(Long result, A attachment) { + handler.completed(result.intValue(), attachment); + } + + @Override + public void failed(Throwable exc, A attachment) { + handler.failed(exc, attachment); + } + + }); + } + + @Override + public void setReadTimeoutSecond(int readTimeoutSecond) { + this.readTimeoutSecond = readTimeoutSecond; + } + + @Override + public void setWriteTimeoutSecond(int writeTimeoutSecond) { + this.writeTimeoutSecond = writeTimeoutSecond; + } + + @Override + public int getReadTimeoutSecond() { + return this.readTimeoutSecond; + } + + @Override + public int getWriteTimeoutSecond() { + return this.writeTimeoutSecond; + } + + @Override + public final SocketAddress getRemoteAddress() { + return remoteAddress; + } + + @Override + public SocketAddress getLocalAddress() { + try { + return channel.getLocalAddress(); + } catch (IOException e) { + return null; + } + } + + @Override + public final Future read(ByteBuffer dst) { + return channel.read(dst); + } + + @Override + public final Future write(ByteBuffer src) { + return channel.write(src); + } + + @Override + public final void close() throws IOException { + super.close(); + channel.close(); + } + + @Override + public final boolean isOpen() { + return channel.isOpen(); + } + + @Override + public final boolean isTCP() { + return true; + } + + } + + public static AsyncConnection create(final AsynchronousSocketChannel ch) { + return create(ch, null, 0, 0); + } + + public static AsyncConnection create(final AsynchronousSocketChannel ch, final SocketAddress addr0, final int readTimeoutSecond0, final int writeTimeoutSecond0) { + return new AIOTCPAsyncConnection(ch, addr0, readTimeoutSecond0, writeTimeoutSecond0); + } + +} diff --git a/src/org/redkale/net/AsyncDatagramChannel.java b/src/org/redkale/net/AsyncDatagramChannel.java new file mode 100644 index 000000000..fc0a4532f --- /dev/null +++ b/src/org/redkale/net/AsyncDatagramChannel.java @@ -0,0 +1,1327 @@ +/* + * 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.net; + +import java.io.*; +import java.lang.invoke.*; +import java.lang.ref.*; +import java.lang.reflect.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.security.*; +import java.util.*; +import java.util.concurrent.*; +import sun.misc.*; +import sun.security.action.*; + +/** + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public final class AsyncDatagramChannel implements AsynchronousByteChannel, MulticastChannel { + + private final DatagramChannel dc; + + private final AsynchronousChannelGroupProxy group; + + private final Object attachKey; + + private boolean closed; + + // used to coordinate timed and blocking reads + private final Object readLock = new Object(); + + // channel blocking mode (requires readLock) + private boolean isBlocking = true; + + // number of blocking readers (requires readLock) + private int blockingReaderCount; + + // true if timed read attempted while blocking read in progress (requires readLock) + private boolean transitionToNonBlocking; + + // true if a blocking read is cancelled (requires readLock) + private boolean blockingReadKilledByCancel; + + // temporary Selectors used by timed reads (requires readLock) + private Selector firstReader; + + private Set otherReaders; + + private static final sun.misc.Unsafe UNSAFE; + + private static final long fdoffset; + + static { + sun.misc.Unsafe usafe = null; + long fd = 0L; + try { + Field safeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + safeField.setAccessible(true); + usafe = (sun.misc.Unsafe) safeField.get(null); + fd = usafe.objectFieldOffset(DatagramChannel.open().getClass().getDeclaredField("fd")); + } catch (Exception e) { + throw new RuntimeException(e); + } + UNSAFE = usafe; + fdoffset = fd; + } + + private AsyncDatagramChannel(ProtocolFamily family, AsynchronousChannelGroup group0) + throws IOException { + this.dc = (family == null) ? DatagramChannel.open() : DatagramChannel.open(family); + if (group0 == null) group0 = AsynchronousChannelGroup.withThreadPool(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2)); + this.group = new AsynchronousChannelGroupProxy(group0); + + // attach this channel to the group as foreign channel + boolean registered = false; + try { + attachKey = group.attachForeignChannel(this, (FileDescriptor) UNSAFE.getObject(dc, fdoffset)); + registered = true; + } finally { + if (!registered) + dc.close(); + } + } + + public static AsyncDatagramChannel open(AsynchronousChannelGroup group) throws IOException { + return open(null, group); + } + + public static AsyncDatagramChannel open(ProtocolFamily family, AsynchronousChannelGroup group) + throws IOException { + return new AsyncDatagramChannel(family, group); + } + + @Override + public final void read(ByteBuffer dst, A attachment, CompletionHandler handler) { + read(dst, 0L, TimeUnit.MILLISECONDS, attachment, handler); + } + + // throws RuntimeException if blocking read has been cancelled + private void ensureBlockingReadNotKilled() { + assert Thread.holdsLock(readLock); + if (blockingReadKilledByCancel) throw new RuntimeException("Reading not allowed due to cancellation"); + } + + // invoke prior to non-timed read/receive + private void beginNoTimeoutRead() { + synchronized (readLock) { + ensureBlockingReadNotKilled(); + if (isBlocking) blockingReaderCount++; + } + } + + // invoke after non-timed read/receive has completed + private void endNoTimeoutRead() { + synchronized (readLock) { + if (isBlocking) { + if (--blockingReaderCount == 0 && transitionToNonBlocking) { + // notify any threads waiting to make channel non-blocking + readLock.notifyAll(); + } + } + } + } + + // invoke prior to timed read + // returns the timeout remaining + private long prepareForTimedRead(PendingFuture result, long timeout) throws IOException { + synchronized (readLock) { + ensureBlockingReadNotKilled(); + if (isBlocking) { + transitionToNonBlocking = true; + while (blockingReaderCount > 0 && timeout > 0L && !result.isCancelled()) { + long st = System.currentTimeMillis(); + try { + readLock.wait(timeout); + } catch (InterruptedException e) { + } + timeout -= System.currentTimeMillis() - st; + } + if (blockingReaderCount == 0) { + // re-check that blocked read wasn't cancelled + ensureBlockingReadNotKilled(); + // no blocking reads so change channel to non-blocking + dc.configureBlocking(false); + isBlocking = false; + } + } + return timeout; + } + } + + // returns a temporary Selector + private Selector getSelector() throws IOException { + Selector sel = getTemporarySelector(dc); + synchronized (readLock) { + if (firstReader == null) { + firstReader = sel; + } else { + if (otherReaders == null) otherReaders = new HashSet<>(); + otherReaders.add(sel); + } + } + return sel; + } + + // releases a temporary Selector + private void releaseSelector(Selector sel) throws IOException { + synchronized (readLock) { + if (firstReader == sel) { + firstReader = null; + } else { + otherReaders.remove(sel); + } + } + releaseTemporarySelector(sel); + } + + // wakeup all Selectors currently in use + private void wakeupSelectors() { + synchronized (readLock) { + if (firstReader != null) + firstReader.wakeup(); + if (otherReaders != null) { + for (Selector sel : otherReaders) { + sel.wakeup(); + } + } + } + } + + public AsynchronousChannelGroupProxy group() { + return group; + } + + @Override + public boolean isOpen() { + return dc.isOpen(); + } + + public void onCancel(PendingFuture task) { + synchronized (readLock) { + if (blockingReaderCount > 0) { + blockingReadKilledByCancel = true; + readLock.notifyAll(); + return; + } + } + wakeupSelectors(); + } + + @Override + public void close() throws IOException { + synchronized (dc) { + if (closed) return; + closed = true; + } + // detach from group and close underlying channel + group.detachForeignChannel(attachKey); + dc.close(); + + // wakeup any threads blocked in timed read/receives + wakeupSelectors(); + } + + public AsyncDatagramChannel connect(SocketAddress remote) + throws IOException { + dc.connect(remote); + return this; + } + + public AsyncDatagramChannel disconnect() throws IOException { + dc.disconnect(); + return this; + } + + private static class WrappedMembershipKey extends MembershipKey { + + private final MulticastChannel channel; + + private final MembershipKey key; + + WrappedMembershipKey(MulticastChannel channel, MembershipKey key) { + this.channel = channel; + this.key = key; + } + + @Override + public boolean isValid() { + return key.isValid(); + } + + @Override + public void drop() { + key.drop(); + } + + @Override + public MulticastChannel channel() { + return channel; + } + + @Override + public InetAddress group() { + return key.group(); + } + + @Override + public NetworkInterface networkInterface() { + return key.networkInterface(); + } + + @Override + public InetAddress sourceAddress() { + return key.sourceAddress(); + } + + @Override + public MembershipKey block(InetAddress toBlock) throws IOException { + key.block(toBlock); + return this; + } + + @Override + public MembershipKey unblock(InetAddress toUnblock) { + key.unblock(toUnblock); + return this; + } + + @Override + public String toString() { + return key.toString(); + } + } + + @Override + public MembershipKey join(InetAddress group, + NetworkInterface interf) + throws IOException { + MembershipKey key = ((MulticastChannel) dc).join(group, interf); + return new WrappedMembershipKey(this, key); + } + + @Override + public MembershipKey join(InetAddress group, NetworkInterface interf, InetAddress source) throws IOException { + MembershipKey key = ((MulticastChannel) dc).join(group, interf, source); + return new WrappedMembershipKey(this, key); + } + + private Future implSend(ByteBuffer src, SocketAddress target, A attachment, CompletionHandler handler) { + int n = 0; + Throwable exc = null; + try { + n = dc.send(src, target); + } catch (IOException ioe) { + exc = ioe; + } + if (handler == null) + return CompletedFuture.withResult(n, exc); + Invoker.invoke(this, handler, attachment, n, exc); + return null; + } + + public Future send(ByteBuffer src, SocketAddress target) { + return implSend(src, target, null, null); + } + + public void send(ByteBuffer src, SocketAddress target, A attachment, CompletionHandler handler) { + if (handler == null) throw new NullPointerException("'handler' is null"); + implSend(src, target, attachment, handler); + } + + public void send(ByteBuffer[] srcs, final int offset, final int length, SocketAddress target, A attachment, final CompletionHandler handler) { + if (handler == null) throw new NullPointerException("'handler' is null"); + final ByteBuffer[] buffers = srcs; + implSend(buffers[offset], target, attachment, new CompletionHandler() { + private int index = offset; + + private int resultSum; + + private final int max = length - 1; + + @Override + public void completed(Integer result, A attachment) { + resultSum += result; + if (buffers[index].hasRemaining()) { + implSend(buffers[index], target, attachment, this); + } else if (index == max) { + if (handler != null) handler.completed(resultSum, attachment); + } else { + implSend(buffers[++index], target, attachment, this); + } + } + + @Override + public void failed(Throwable exc, A attachment) { + if (handler != null) handler.failed(exc, attachment); + } + }); + } + + private Future implWrite(ByteBuffer src, A attachment, CompletionHandler handler) { + int n = 0; + Throwable exc = null; + try { + n = dc.write(src); + } catch (IOException ioe) { + exc = ioe; + } + if (handler == null) return CompletedFuture.withResult(n, exc); + Invoker.invoke(this, handler, attachment, n, exc); + return null; + + } + + @Override + public Future write(ByteBuffer src) { + return implWrite(src, null, null); + } + + @Override + public void write(ByteBuffer src, A attachment, CompletionHandler handler) { + if (handler == null) throw new NullPointerException("'handler' is null"); + implWrite(src, attachment, handler); + } + + /** + * Receive into the given buffer with privileges enabled and restricted by the given AccessControlContext (can be null). + */ + private SocketAddress doRestrictedReceive(final ByteBuffer dst, + AccessControlContext acc) + throws IOException { + if (acc == null) { + return dc.receive(dst); + } else { + try { + return AccessController.doPrivileged( + new PrivilegedExceptionAction() { + public SocketAddress run() throws IOException { + return dc.receive(dst); + } + }, acc); + } catch (PrivilegedActionException pae) { + Exception cause = pae.getException(); + if (cause instanceof SecurityException) + throw (SecurityException) cause; + throw (IOException) cause; + } + } + } + + private Future implReceive(final ByteBuffer dst, final long timeout, + final TimeUnit unit, A attachment, final CompletionHandler handler) { + if (dst.isReadOnly()) throw new IllegalArgumentException("Read-only buffer"); + if (timeout < 0L) throw new IllegalArgumentException("Negative timeout"); + if (unit == null) throw new NullPointerException(); + + // complete immediately if channel closed + if (!isOpen()) { + Throwable exc = new ClosedChannelException(); + if (handler == null) return CompletedFuture.withFailure(exc); + Invoker.invoke(this, handler, attachment, null, exc); + return null; + } + + final AccessControlContext acc = (System.getSecurityManager() == null) ? null : AccessController.getContext(); + final PendingFuture result = new PendingFuture(this, handler, attachment); + Runnable task = new Runnable() { + public void run() { + try { + SocketAddress remote = null; + long to; + if (timeout == 0L) { + beginNoTimeoutRead(); + try { + remote = doRestrictedReceive(dst, acc); + } finally { + endNoTimeoutRead(); + } + to = 0L; + } else { + to = prepareForTimedRead(result, unit.toMillis(timeout)); + if (to <= 0L) throw new InterruptedByTimeoutException(); + remote = doRestrictedReceive(dst, acc); + } + if (remote == null) { + Selector sel = getSelector(); + SelectionKey sk = null; + try { + sk = dc.register(sel, SelectionKey.OP_READ); + for (;;) { + if (!dc.isOpen()) throw new AsynchronousCloseException(); + if (result.isCancelled()) break; + long st = System.currentTimeMillis(); + int ns = sel.select(to); + if (ns > 0) { + remote = doRestrictedReceive(dst, acc); + if (remote != null) break; + } + sel.selectedKeys().remove(sk); + if (timeout != 0L) { + to -= System.currentTimeMillis() - st; + if (to <= 0) throw new InterruptedByTimeoutException(); + } + } + } finally { + if (sk != null) + sk.cancel(); + releaseSelector(sel); + } + } + result.setResult(remote); + } catch (Exception x) { + if (x instanceof ClosedChannelException) + x = new AsynchronousCloseException(); + result.setFailure(x); + } + Invoker.invokeUnchecked(result); + } + }; + try { + group.executeOnPooledThread(task); + } catch (RejectedExecutionException ree) { + throw new ShutdownChannelGroupException(); + } + return result; + } + + public Future receive(ByteBuffer dst) { + return implReceive(dst, 0L, TimeUnit.MILLISECONDS, null, null); + } + + public void receive(ByteBuffer dst, A attachment, CompletionHandler handler) { + receive(dst, 0L, TimeUnit.MILLISECONDS, attachment, handler); + } + + public void receive(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) { + if (handler == null) throw new NullPointerException("'handler' is null"); + implReceive(dst, timeout, unit, attachment, handler); + } + + private Future implRead(final ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) { + if (dst.isReadOnly()) throw new IllegalArgumentException("Read-only buffer"); + if (timeout < 0L) throw new IllegalArgumentException("Negative timeout"); + if (unit == null) throw new NullPointerException(); + + // complete immediately if channel closed + if (!isOpen()) { + Throwable exc = new ClosedChannelException(); + if (handler == null) return CompletedFuture.withFailure(exc); + Invoker.invoke(this, handler, attachment, null, exc); + return null; + } + + // another thread may disconnect before read is initiated + if (!dc.isConnected()) throw new NotYetConnectedException(); + + final PendingFuture result = new PendingFuture(this, handler, attachment); + Runnable task = new Runnable() { + public void run() { + try { + int n = 0; + long to; + if (timeout == 0L) { + beginNoTimeoutRead(); + try { + n = dc.read(dst); + } finally { + endNoTimeoutRead(); + } + to = 0L; + } else { + to = prepareForTimedRead(result, unit.toMillis(timeout)); + if (to <= 0L) throw new InterruptedByTimeoutException(); + n = dc.read(dst); + } + if (n == 0) { + Selector sel = getSelector(); + SelectionKey sk = null; + try { + sk = dc.register(sel, SelectionKey.OP_READ); + for (;;) { + if (!dc.isOpen()) throw new AsynchronousCloseException(); + if (result.isCancelled()) break; + long st = System.currentTimeMillis(); + int ns = sel.select(to); + if (ns > 0) { + if ((n = dc.read(dst)) != 0) break; + } + sel.selectedKeys().remove(sk); + if (timeout != 0L) { + to -= System.currentTimeMillis() - st; + if (to <= 0) throw new InterruptedByTimeoutException(); + } + } + } finally { + if (sk != null) + sk.cancel(); + releaseSelector(sel); + } + } + result.setResult(n); + } catch (Exception x) { + if (x instanceof ClosedChannelException) x = new AsynchronousCloseException(); + result.setFailure(x); + } + Invoker.invokeUnchecked(result); + } + }; + try { + group.executeOnPooledThread(task); + } catch (RejectedExecutionException ree) { + throw new ShutdownChannelGroupException(); + } + return result; + } + + @Override + public Future read(ByteBuffer dst) { + return implRead(dst, 0L, TimeUnit.MILLISECONDS, null, null); + } + + public void read(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler handler) { + if (handler == null) throw new NullPointerException("'handler' is null"); + implRead(dst, timeout, unit, attachment, handler); + } + + @Override + public AsyncDatagramChannel bind(SocketAddress local) throws IOException { + dc.bind(local); + return this; + } + + @Override + public SocketAddress getLocalAddress() throws IOException { + return dc.getLocalAddress(); + } + + @Override + public AsyncDatagramChannel setOption(SocketOption name, T value) throws IOException { + dc.setOption(name, value); + return this; + } + + @Override + public T getOption(SocketOption name) throws IOException { + return dc.getOption(name); + } + + @Override + public Set> supportedOptions() { + return dc.supportedOptions(); + } + + public SocketAddress getRemoteAddress() throws IOException { + return dc.getRemoteAddress(); + } + + private static class SelectorWrapper { + + private Selector sel; + + private SelectorWrapper(Selector sel) { + this.sel = sel; + Cleaner.create(this, new Closer(sel)); + } + + private static class Closer implements Runnable { + + private Selector sel; + + private Closer(Selector sel) { + this.sel = sel; + } + + public void run() { + try { + sel.close(); + } catch (Exception th) { + //throw new Error(th); + } + } + } + + public Selector get() { + return sel; + } + } + + private static ThreadLocal> localSelector + = new ThreadLocal>(); + + // Hold a reference to the selWrapper object to prevent it from + // being cleaned when the temporary selector wrapped is on lease. + private static ThreadLocal localSelectorWrapper + = new ThreadLocal(); + + static Selector getTemporarySelector(SelectableChannel sc) + throws IOException { + SoftReference ref = localSelector.get(); + SelectorWrapper selWrapper = null; + Selector sel = null; + if (ref == null + || ((selWrapper = ref.get()) == null) + || ((sel = selWrapper.get()) == null) + || (sel.provider() != sc.provider())) { + sel = sc.provider().openSelector(); + selWrapper = new SelectorWrapper(sel); + localSelector.set(new SoftReference(selWrapper)); + } + localSelectorWrapper.set(selWrapper); + return sel; + } + + static void releaseTemporarySelector(Selector sel) + throws IOException { + // Selector should be empty + sel.selectNow(); // Flush cancelled keys + assert sel.keys().isEmpty() : "Temporary selector not empty"; + localSelectorWrapper.set(null); + } + +} + +final class AsynchronousChannelGroupProxy extends AsynchronousChannelGroup { + + private final AsynchronousChannelGroup group; + + private final MethodHandle executeOnPooledThread; + + private final MethodHandle attachForeignChannel; + + private final MethodHandle detachForeignChannel; + + public AsynchronousChannelGroupProxy(AsynchronousChannelGroup group) { + super(group.provider()); + this.group = group; + MethodHandle method1 = null, method2 = null, method3 = null; + try { + Method m = findGroupMethod(group.getClass(), "executeOnPooledThread", Runnable.class); + m.setAccessible(true); + method1 = MethodHandles.lookup().unreflect(m); + + m = findGroupMethod(group.getClass(), "attachForeignChannel", Channel.class, FileDescriptor.class); + m.setAccessible(true); + method2 = MethodHandles.lookup().unreflect(m); + + m = findGroupMethod(group.getClass(), "detachForeignChannel", Object.class); + m.setAccessible(true); + method3 = MethodHandles.lookup().unreflect(m); + } catch (Exception e) { + e.printStackTrace(); + } + this.executeOnPooledThread = method1; + this.attachForeignChannel = method2; + this.detachForeignChannel = method3; + } + + private static Method findGroupMethod(Class clazz, String methodname, Class... params) throws Exception { + if (clazz == Object.class) return null; + try { + return clazz.getDeclaredMethod(methodname, params); + } catch (NoSuchMethodException e) { + return findGroupMethod(clazz.getSuperclass(), methodname, params); + } + } + + @Override + public boolean isShutdown() { + return group.isShutdown(); + } + + @Override + public boolean isTerminated() { + return group.isTerminated(); + } + + @Override + public void shutdown() { + group.shutdown(); + } + + @Override + public void shutdownNow() throws IOException { + group.shutdownNow(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return group.awaitTermination(timeout, unit); + } + + Object attachForeignChannel(Channel channel, FileDescriptor fdo) throws IOException { + try { + return attachForeignChannel.invoke(group, channel, fdo); + } catch (Throwable e) { + throw new IOException(e); + } + } + + void detachForeignChannel(Object key) { + try { + detachForeignChannel.invoke(group, key); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + final void executeOnPooledThread(Runnable task) { + try { + executeOnPooledThread.invoke(group, task); + } catch (Throwable e) { + e.printStackTrace(); + } + } + +} + +final class PendingFuture implements Future { + + private static final CancellationException CANCELLED = new CancellationException(); + + private final AsynchronousChannel channel; + + private final CompletionHandler handler; + + private final A attachment; + + // true if result (or exception) is available + private volatile boolean haveResult; + + private volatile V result; + + private volatile Throwable exc; + + // latch for waiting (created lazily if needed) + private CountDownLatch latch; + + // optional timer task that is cancelled when result becomes available + private Future timeoutTask; + + // optional context object + private volatile Object context; + + PendingFuture(AsynchronousChannel channel, CompletionHandler handler, A attachment, Object context) { + this.channel = channel; + this.handler = handler; + this.attachment = attachment; + this.context = context; + } + + PendingFuture(AsynchronousChannel channel, CompletionHandler handler, A attachment) { + this.channel = channel; + this.handler = handler; + this.attachment = attachment; + } + + PendingFuture(AsynchronousChannel channel) { + this(channel, null, null); + } + + PendingFuture(AsynchronousChannel channel, Object context) { + this(channel, null, null, context); + } + + AsynchronousChannel channel() { + return channel; + } + + CompletionHandler handler() { + return handler; + } + + A attachment() { + return attachment; + } + + void setContext(Object context) { + this.context = context; + } + + Object getContext() { + return context; + } + + void setTimeoutTask(Future task) { + synchronized (this) { + if (haveResult) { + task.cancel(false); + } else { + this.timeoutTask = task; + } + } + } + + // creates latch if required; return true if caller needs to wait + private boolean prepareForWait() { + synchronized (this) { + if (haveResult) { + return false; + } else { + if (latch == null) + latch = new CountDownLatch(1); + return true; + } + } + } + + /** + * Sets the result, or a no-op if the result or exception is already set. + */ + void setResult(V res) { + synchronized (this) { + if (haveResult) + return; + result = res; + haveResult = true; + if (timeoutTask != null) + timeoutTask.cancel(false); + if (latch != null) + latch.countDown(); + } + } + + /** + * Sets the result, or a no-op if the result or exception is already set. + */ + void setFailure(Throwable x) { + if (!(x instanceof IOException) && !(x instanceof SecurityException)) + x = new IOException(x); + synchronized (this) { + if (haveResult) + return; + exc = x; + haveResult = true; + if (timeoutTask != null) + timeoutTask.cancel(false); + if (latch != null) + latch.countDown(); + } + } + + /** + * Sets the result + */ + void setResult(V res, Throwable x) { + if (x == null) { + setResult(res); + } else { + setFailure(x); + } + } + + @Override + public V get() throws ExecutionException, InterruptedException { + if (!haveResult) { + boolean needToWait = prepareForWait(); + if (needToWait) + latch.await(); + } + if (exc != null) { + if (exc == CANCELLED) + throw new CancellationException(); + throw new ExecutionException(exc); + } + return result; + } + + @Override + public V get(long timeout, TimeUnit unit) + throws ExecutionException, InterruptedException, TimeoutException { + if (!haveResult) { + boolean needToWait = prepareForWait(); + if (needToWait) + if (!latch.await(timeout, unit)) throw new TimeoutException(); + } + if (exc != null) { + if (exc == CANCELLED) + throw new CancellationException(); + throw new ExecutionException(exc); + } + return result; + } + + Throwable exception() { + return (exc != CANCELLED) ? exc : null; + } + + V value() { + return result; + } + + @Override + public boolean isCancelled() { + return (exc == CANCELLED); + } + + @Override + public boolean isDone() { + return haveResult; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + synchronized (this) { + if (haveResult) + return false; // already completed + + // notify channel + if (channel() instanceof AsyncDatagramChannel) + ((AsyncDatagramChannel) channel()).onCancel(this); + + // set result and cancel timer + exc = CANCELLED; + haveResult = true; + if (timeoutTask != null) + timeoutTask.cancel(false); + } + + // close channel if forceful cancel + if (mayInterruptIfRunning) { + try { + channel().close(); + } catch (IOException ignore) { + } + } + + // release waiters + if (latch != null) + latch.countDown(); + return true; + } +} + +final class CompletedFuture implements Future { + + private final V result; + + private final Throwable exc; + + private CompletedFuture(V result, Throwable exc) { + this.result = result; + this.exc = exc; + } + + static CompletedFuture withResult(V result) { + return new CompletedFuture(result, null); + } + + static CompletedFuture withFailure(Throwable exc) { + // exception must be IOException or SecurityException + if (!(exc instanceof IOException) && !(exc instanceof SecurityException)) + exc = new IOException(exc); + return new CompletedFuture(null, exc); + } + + static CompletedFuture withResult(V result, Throwable exc) { + if (exc == null) { + return withResult(result); + } else { + return withFailure(exc); + } + } + + @Override + public V get() throws ExecutionException { + if (exc != null) + throw new ExecutionException(exc); + return result; + } + + @Override + public V get(long timeout, TimeUnit unit) throws ExecutionException { + if (unit == null) + throw new NullPointerException(); + if (exc != null) + throw new ExecutionException(exc); + return result; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } +} + +class Invoker { + + private Invoker() { + } + + // maximum number of completion handlers that may be invoked on the current + // thread before it re-directs invocations to the thread pool. This helps + // avoid stack overflow and lessens the risk of starvation. + private static final int maxHandlerInvokeCount = AccessController.doPrivileged( + new GetIntegerAction("sun.nio.ch.maxCompletionHandlersOnStack", 16)); + + // Per-thread object with reference to channel group and a counter for + // the number of completion handlers invoked. This should be reset to 0 + // when all completion handlers have completed. + static class GroupAndInvokeCount { + + private final AsynchronousChannelGroup group; + + private int handlerInvokeCount; + + GroupAndInvokeCount(AsynchronousChannelGroup group) { + this.group = group; + } + + AsynchronousChannelGroup group() { + return group; + } + + int invokeCount() { + return handlerInvokeCount; + } + + void setInvokeCount(int value) { + handlerInvokeCount = value; + } + + void resetInvokeCount() { + handlerInvokeCount = 0; + } + + void incrementInvokeCount() { + handlerInvokeCount++; + } + } + + private static final ThreadLocal myGroupAndInvokeCount + = new ThreadLocal() { + @Override + protected GroupAndInvokeCount initialValue() { + return null; + } + }; + + /** + * Binds this thread to the given group + */ + static void bindToGroup(AsynchronousChannelGroup group) { + myGroupAndInvokeCount.set(new GroupAndInvokeCount(group)); + } + + /** + * Returns the GroupAndInvokeCount object for this thread. + */ + static GroupAndInvokeCount getGroupAndInvokeCount() { + return myGroupAndInvokeCount.get(); + } + + /** + * Returns true if the current thread is in a channel group's thread pool + */ + static boolean isBoundToAnyGroup() { + return myGroupAndInvokeCount.get() != null; + } + + /* + * Returns true if the current thread is in the given channel's thread pool + * and we haven't exceeded the maximum number of handler frames on the stack. + */ + static boolean mayInvokeDirect(GroupAndInvokeCount myGroupAndInvokeCount, + AsynchronousChannelGroup group) { + if ((myGroupAndInvokeCount != null) + && (myGroupAndInvokeCount.group() == group) + && (myGroupAndInvokeCount.invokeCount() < maxHandlerInvokeCount)) { + return true; + } + return false; + } + + /** + * Invoke handler without checking the thread identity or number of handlers on the thread stack. + */ + static void invokeUnchecked(CompletionHandler handler, + A attachment, + V value, + Throwable exc) { + if (exc == null) { + handler.completed(value, attachment); + } else { + handler.failed(exc, attachment); + } + + // clear interrupt + Thread.interrupted(); + } + + /** + * Invoke handler assuming thread identity already checked + */ + static void invokeDirect(GroupAndInvokeCount myGroupAndInvokeCount, + CompletionHandler handler, + A attachment, + V result, + Throwable exc) { + myGroupAndInvokeCount.incrementInvokeCount(); + Invoker.invokeUnchecked(handler, attachment, result, exc); + } + + /** + * Invokes the handler. If the current thread is in the channel group's thread pool then the handler is invoked directly, otherwise it is invoked indirectly. + */ + static void invoke(AsynchronousChannel channel, + CompletionHandler handler, + A attachment, + V result, + Throwable exc) { + boolean invokeDirect = false; + boolean identityOkay = false; + GroupAndInvokeCount thisGroupAndInvokeCount = myGroupAndInvokeCount.get(); + if (thisGroupAndInvokeCount != null) { + if ((thisGroupAndInvokeCount.group() == ((AsyncDatagramChannel) channel).group())) + identityOkay = true; + if (identityOkay + && (thisGroupAndInvokeCount.invokeCount() < maxHandlerInvokeCount)) { + // group match + invokeDirect = true; + } + } + if (invokeDirect) { + invokeDirect(thisGroupAndInvokeCount, handler, attachment, result, exc); + } else { + try { + invokeIndirectly(channel, handler, attachment, result, exc); + } catch (RejectedExecutionException ree) { + // channel group shutdown; fallback to invoking directly + // if the current thread has the right identity. + if (identityOkay) { + invokeDirect(thisGroupAndInvokeCount, + handler, attachment, result, exc); + } else { + throw new ShutdownChannelGroupException(); + } + } + } + } + + /** + * Invokes the handler indirectly via the channel group's thread pool. + */ + static void invokeIndirectly(AsynchronousChannel channel, + final CompletionHandler handler, + final A attachment, + final V result, + final Throwable exc) { + try { + ((AsyncDatagramChannel) channel).group().executeOnPooledThread(new Runnable() { + public void run() { + GroupAndInvokeCount thisGroupAndInvokeCount + = myGroupAndInvokeCount.get(); + if (thisGroupAndInvokeCount != null) + thisGroupAndInvokeCount.setInvokeCount(1); + invokeUnchecked(handler, attachment, result, exc); + } + }); + } catch (RejectedExecutionException ree) { + throw new ShutdownChannelGroupException(); + } + } + + /** + * Invokes the handler "indirectly" in the given Executor + */ + static void invokeIndirectly(final CompletionHandler handler, + final A attachment, + final V value, + final Throwable exc, + Executor executor) { + try { + executor.execute(new Runnable() { + public void run() { + invokeUnchecked(handler, attachment, value, exc); + } + }); + } catch (RejectedExecutionException ree) { + throw new ShutdownChannelGroupException(); + } + } + + /** + * Invokes the given task on the thread pool associated with the given channel. If the current thread is in the thread pool then the task is invoked directly. + */ + static void invokeOnThreadInThreadPool(AsyncDatagramChannel channel, + Runnable task) { + boolean invokeDirect; + GroupAndInvokeCount thisGroupAndInvokeCount = myGroupAndInvokeCount.get(); + AsynchronousChannelGroupProxy targetGroup = channel.group(); + if (thisGroupAndInvokeCount == null) { + invokeDirect = false; + } else { + invokeDirect = (thisGroupAndInvokeCount.group == targetGroup); + } + try { + if (invokeDirect) { + task.run(); + } else { + targetGroup.executeOnPooledThread(task); + } + } catch (RejectedExecutionException ree) { + throw new ShutdownChannelGroupException(); + } + } + + /** + * Invoke handler with completed result. This method does not check the thread identity or the number of handlers on the thread stack. + */ + static void invokeUnchecked(PendingFuture future) { + assert future.isDone(); + CompletionHandler handler = future.handler(); + if (handler != null) { + invokeUnchecked(handler, + future.attachment(), + future.value(), + future.exception()); + } + } + + /** + * Invoke handler with completed result. If the current thread is in the channel group's thread pool then the handler is invoked directly, otherwise it is invoked indirectly. + */ + static void invoke(PendingFuture future) { + assert future.isDone(); + CompletionHandler handler = future.handler(); + if (handler != null) { + invoke(future.channel(), + handler, + future.attachment(), + future.value(), + future.exception()); + } + } + + /** + * Invoke handler with completed result. The handler is invoked indirectly, via the channel group's thread pool. + */ + static void invokeIndirectly(PendingFuture future) { + assert future.isDone(); + CompletionHandler handler = future.handler(); + if (handler != null) { + invokeIndirectly(future.channel(), + handler, + future.attachment(), + future.value(), + future.exception()); + } + } +} diff --git a/src/org/redkale/net/Context.java b/src/org/redkale/net/Context.java new file mode 100644 index 000000000..fdf9cc2df --- /dev/null +++ b/src/org/redkale/net/Context.java @@ -0,0 +1,132 @@ +/* + * 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.net; + +import java.net.*; +import java.nio.*; +import java.nio.charset.*; +import java.util.concurrent.*; +import java.util.function.*; +import java.util.logging.*; +import org.redkale.convert.bson.*; +import org.redkale.convert.json.*; +import org.redkale.util.*; +import org.redkale.watch.*; + +/** + * + * @author zhangjx + */ +public class Context { + + private static final Charset UTF8 = Charset.forName("UTF-8"); + + protected final long serverStartTime; + + protected final ExecutorService executor; + + protected final int bufferCapacity; + + protected final ObjectPool bufferPool; + + protected final ObjectPool responsePool; + + protected final PrepareServlet prepare; + + private final InetSocketAddress address; + + protected final Charset charset; + + protected final int maxbody; + + protected final int readTimeoutSecond; + + protected final int writeTimeoutSecond; + + protected final Logger logger; + + protected final BsonFactory bsonFactory; + + protected final JsonFactory jsonFactory; + + protected final WatchFactory watch; + + public Context(long serverStartTime, Logger logger, ExecutorService executor, int bufferCapacity, ObjectPool bufferPool, ObjectPool responsePool, + final int maxbody, Charset charset, InetSocketAddress address, final PrepareServlet prepare, final WatchFactory watch, + final int readTimeoutSecond, final int writeTimeoutSecond) { + this.serverStartTime = serverStartTime; + this.logger = logger; + this.executor = executor; + this.bufferCapacity = bufferCapacity; + this.bufferPool = bufferPool; + this.responsePool = responsePool; + this.maxbody = maxbody; + this.charset = UTF8.equals(charset) ? null : charset; + this.address = address; + this.prepare = prepare; + this.watch = watch; + this.readTimeoutSecond = readTimeoutSecond; + this.writeTimeoutSecond = writeTimeoutSecond; + this.jsonFactory = JsonFactory.root(); + this.bsonFactory = BsonFactory.root(); + } + + public int getMaxbody() { + return maxbody; + } + + public InetSocketAddress getServerAddress() { + return address; + } + + public long getServerStartTime() { + return serverStartTime; + } + + public Charset getCharset() { + return charset; + } + + public void submit(Runnable r) { + executor.submit(r); + } + + public int getBufferCapacity() { + return bufferCapacity; + } + + public Supplier getBufferSupplier() { + return bufferPool; + } + + public ByteBuffer pollBuffer() { + return bufferPool.get(); + } + + public void offerBuffer(ByteBuffer buffer) { + bufferPool.offer(buffer); + } + + public Logger getLogger() { + return logger; + } + + public int getReadTimeoutSecond() { + return readTimeoutSecond; + } + + public int getWriteTimeoutSecond() { + return writeTimeoutSecond; + } + + public JsonConvert getJsonConvert() { + return jsonFactory.getConvert(); + } + + public BsonConvert getBsonConvert() { + return bsonFactory.getConvert(); + } +} diff --git a/src/org/redkale/net/PrepareRunner.java b/src/org/redkale/net/PrepareRunner.java new file mode 100644 index 000000000..95f87870b --- /dev/null +++ b/src/org/redkale/net/PrepareRunner.java @@ -0,0 +1,98 @@ +/* + * 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.net; + +import java.nio.*; +import java.nio.channels.*; +import java.util.logging.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public final class PrepareRunner implements Runnable { + + private final AsyncConnection channel; + + private final Context context; + + private ByteBuffer data; + + public PrepareRunner(Context context, AsyncConnection channel, ByteBuffer data) { + this.context = context; + this.channel = channel; + this.data = data; + } + + @Override + public void run() { + final PrepareServlet prepare = context.prepare; + final ObjectPool responsePool = context.responsePool; + if (data != null) { + final Response response = responsePool.get(); + response.init(channel); + try { + prepare.prepare(data, response.request, response); + } catch (Throwable t) { + context.logger.log(Level.WARNING, "prepare servlet abort, forece to close channel ", t); + response.finish(true); + } + return; + } + final ByteBuffer buffer = context.pollBuffer(); + try { + channel.read(buffer, null, new CompletionHandler() { + @Override + public void completed(Integer count, Void attachment1) { + if (count < 1 && buffer.remaining() == buffer.limit()) { + try { + context.offerBuffer(buffer); + channel.close(); + } catch (Exception e) { + context.logger.log(Level.FINEST, "PrepareRunner close channel erroneous on no read bytes", e); + } + return; + } +// { //测试 +// buffer.flip(); +// byte[] bs = new byte[buffer.remaining()]; +// buffer.get(bs); +// System.println(new String(bs)); +// } + buffer.flip(); + final Response response = responsePool.get(); + response.init(channel); + try { + prepare.prepare(buffer, response.request, response); + } catch (Throwable t) { //此处不可 context.offerBuffer(buffer); 以免prepare.prepare内部异常导致重复 offerBuffer + context.logger.log(Level.WARNING, "prepare servlet abort, forece to close channel ", t); + response.finish(true); + } + } + + @Override + public void failed(Throwable exc, Void attachment2) { + context.offerBuffer(buffer); + try { + channel.close(); + } catch (Exception e) { + } + if (exc != null) context.logger.log(Level.FINEST, "Servlet Handler read channel erroneous, forece to close channel ", exc); + } + }); + } catch (Exception te) { + context.offerBuffer(buffer); + try { + channel.close(); + } catch (Exception e) { + } + if (te != null) context.logger.log(Level.FINEST, "Servlet read channel erroneous, forece to close channel ", te); + } + } + +} diff --git a/src/org/redkale/net/PrepareServlet.java b/src/org/redkale/net/PrepareServlet.java new file mode 100644 index 000000000..63cd0b037 --- /dev/null +++ b/src/org/redkale/net/PrepareServlet.java @@ -0,0 +1,73 @@ +/* + * 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.net; + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.concurrent.atomic.*; +import java.util.logging.*; + +/** + * + * @author zhangjx + * @param + * @param

+ */ +public abstract class PrepareServlet> implements Servlet { + + protected final AtomicLong executeCounter = new AtomicLong(); //执行请求次数 + + protected final AtomicLong illRequestCounter = new AtomicLong(); //错误请求次数 + + public final void prepare(final ByteBuffer buffer, final R request, final P response) throws IOException { + executeCounter.incrementAndGet(); + final int rs = request.readHeader(buffer); + if (rs < 0) { + response.context.offerBuffer(buffer); + if (rs != Integer.MIN_VALUE) illRequestCounter.incrementAndGet(); + response.finish(true); + } else if (rs == 0) { + response.context.offerBuffer(buffer); + request.prepare(); + this.execute(request, response); + } else { + buffer.clear(); + final AtomicInteger ai = new AtomicInteger(rs); + request.channel.read(buffer, buffer, new CompletionHandler() { + + @Override + public void completed(Integer result, ByteBuffer attachment) { + buffer.flip(); + ai.addAndGet(-request.readBody(buffer)); + if (ai.get() > 0) { + buffer.clear(); + request.channel.read(buffer, buffer, this); + } else { + response.context.offerBuffer(buffer); + request.prepare(); + try { + execute(request, response); + } catch (Exception e) { + illRequestCounter.incrementAndGet(); + response.finish(true); + request.context.logger.log(Level.WARNING, "prepare servlet abort, forece to close channel ", e); + } + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + illRequestCounter.incrementAndGet(); + response.context.offerBuffer(buffer); + response.finish(true); + if (exc != null) request.context.logger.log(Level.FINER, "Servlet read channel erroneous, forece to close channel ", exc); + } + }); + } + } + +} diff --git a/src/org/redkale/net/ProtocolServer.java b/src/org/redkale/net/ProtocolServer.java new file mode 100644 index 000000000..2dcc9f54e --- /dev/null +++ b/src/org/redkale/net/ProtocolServer.java @@ -0,0 +1,185 @@ +/* + * 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.net; + +import java.io.IOException; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.*; + +/** + * + * @author zhangjx + */ +public abstract class ProtocolServer { + + protected static final boolean winos = System.getProperty("os.name").contains("Window"); + + public abstract void open() throws IOException; + + public abstract void bind(SocketAddress local, int backlog) throws IOException; + + public abstract Set> supportedOptions(); + + public abstract void setOption(SocketOption name, T value) throws IOException; + + public abstract void accept(); + + public abstract void close() throws IOException; + + public abstract AsynchronousChannelGroup getChannelGroup(); + + //--------------------------------------------------------------------- + public static ProtocolServer create(String protocol, Context context) { + if ("TCP".equalsIgnoreCase(protocol)) return new ProtocolTCPServer(context); + if ("UDP".equalsIgnoreCase(protocol)) return new ProtocolUDPServer(context); + throw new RuntimeException("ProtocolServer not support protocol " + protocol); + } + + private static final class ProtocolUDPServer extends ProtocolServer { + + private boolean running; + + private final Context context; + + private DatagramChannel serverChannel; + + public ProtocolUDPServer(Context context) { + this.context = context; + } + + @Override + public void open() throws IOException { + DatagramChannel ch = DatagramChannel.open(); + ch.configureBlocking(true); + this.serverChannel = ch; + } + + @Override + public void bind(SocketAddress local, int backlog) throws IOException { + this.serverChannel.bind(local); + } + + @Override + public void setOption(SocketOption name, T value) throws IOException { + this.serverChannel.setOption(name, value); + } + + @Override + public Set> supportedOptions() { + return this.serverChannel.supportedOptions(); + } + + @Override + public void accept() { + final DatagramChannel serchannel = this.serverChannel; + final int readTimeoutSecond = this.context.readTimeoutSecond; + final int writeTimeoutSecond = this.context.writeTimeoutSecond; + final CountDownLatch cdl = new CountDownLatch(1); + this.running = true; + new Thread() { + @Override + public void run() { + cdl.countDown(); + while (running) { + final ByteBuffer buffer = context.pollBuffer(); + try { + SocketAddress address = serchannel.receive(buffer); + buffer.flip(); + AsyncConnection conn = AsyncConnection.create(serchannel, address, false, readTimeoutSecond, writeTimeoutSecond); + context.submit(new PrepareRunner(context, conn, buffer)); + } catch (Exception e) { + context.offerBuffer(buffer); + } + } + } + }.start(); + try { + cdl.await(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void close() throws IOException { + this.running = false; + this.serverChannel.close(); + } + + @Override + public AsynchronousChannelGroup getChannelGroup() { + return null; + } + + } + + private static final class ProtocolTCPServer extends ProtocolServer { + + private final Context context; + + private AsynchronousChannelGroup group; + + private AsynchronousServerSocketChannel serverChannel; + + public ProtocolTCPServer(Context context) { + this.context = context; + } + + @Override + public void open() throws IOException { + group = AsynchronousChannelGroup.withCachedThreadPool(context.executor, 1); + this.serverChannel = AsynchronousServerSocketChannel.open(group); + } + + @Override + public void bind(SocketAddress local, int backlog) throws IOException { + this.serverChannel.bind(local, backlog); + } + + @Override + public void setOption(SocketOption name, T value) throws IOException { + this.serverChannel.setOption(name, value); + } + + @Override + public Set> supportedOptions() { + return this.serverChannel.supportedOptions(); + } + + @Override + public void accept() { + final AsynchronousServerSocketChannel serchannel = this.serverChannel; + serchannel.accept(null, new CompletionHandler() { + + @Override + public void completed(final AsynchronousSocketChannel channel, Void attachment) { + serchannel.accept(null, this); + context.submit(new PrepareRunner(context, AsyncConnection.create(channel, null, context.readTimeoutSecond, context.writeTimeoutSecond), null)); + } + + @Override + public void failed(Throwable exc, Void attachment) { + serchannel.accept(null, this); + //if (exc != null) context.logger.log(Level.FINEST, AsynchronousServerSocketChannel.class.getSimpleName() + " accept erroneous", exc); + } + }); + } + + @Override + public void close() throws IOException { + this.serverChannel.close(); + } + + @Override + public AsynchronousChannelGroup getChannelGroup() { + return this.group; + } + } + +} diff --git a/src/org/redkale/net/Request.java b/src/org/redkale/net/Request.java new file mode 100644 index 000000000..5f829d746 --- /dev/null +++ b/src/org/redkale/net/Request.java @@ -0,0 +1,112 @@ +/* + * 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.net; + +import java.nio.*; +import java.util.*; +import org.redkale.convert.bson.*; +import org.redkale.convert.json.*; + +/** + * + * @author zhangjx + */ +public abstract class Request { + + protected final Context context; + + protected final BsonConvert bsonConvert; + + protected final JsonConvert jsonConvert; + + protected long createtime; + + protected boolean keepAlive; + + protected AsyncConnection channel; + + /** + * properties 与 attributes 的区别在于:调用recycle时, attributes会被清空而properties会保留; + * properties 通常存放需要永久绑定在request里的一些对象 + */ + private final Map properties = new HashMap<>(); + + protected final Map attributes = new HashMap<>(); + + protected Request(Context context) { + this.context = context; + this.bsonConvert = context.getBsonConvert(); + this.jsonConvert = context.getJsonConvert(); + } + + /** + * 返回值:Integer.MIN_VALUE: 帧数据; -1:数据不合法; 0:解析完毕; >0: 需再读取的字节数。 + * + * @param buffer + * @return + */ + protected abstract int readHeader(ByteBuffer buffer); + + /** + * 读取buffer,并返回读取的有效数据长度 + * + * @param buffer + * @return + */ + protected abstract int readBody(ByteBuffer buffer); + + protected abstract void prepare(); + + protected void recycle() { + createtime = 0; + keepAlive = false; + attributes.clear(); + channel = null; // close it by response + } + + protected void setProperty(String name, Object value) { + properties.put(name, value); + } + + @SuppressWarnings("unchecked") + protected T getProperty(String name) { + return (T) properties.get(name); + } + + protected void removeProperty(String name) { + properties.remove(name); + } + + protected Map getProperties() { + return properties; + } + + public void setAttribute(String name, Object value) { + attributes.put(name, value); + } + + @SuppressWarnings("unchecked") + public T getAttribute(String name) { + return (T) attributes.get(name); + } + + public void removeAttribute(String name) { + attributes.remove(name); + } + + public Map getAttributes() { + return attributes; + } + + public Context getContext() { + return this.context; + } + + public long getCreatetime() { + return createtime; + } + +} diff --git a/src/org/redkale/net/Response.java b/src/org/redkale/net/Response.java new file mode 100644 index 000000000..3a4a499c9 --- /dev/null +++ b/src/org/redkale/net/Response.java @@ -0,0 +1,228 @@ +/* + * 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.net; + +import java.nio.*; +import java.nio.channels.*; + +/** + * + * @author zhangjx + * @param + */ +@SuppressWarnings("unchecked") +public abstract class Response { + + protected final Context context; + + protected final R request; + + protected AsyncConnection channel; + + private boolean inited = true; + + protected Runnable recycleListener; + + private final CompletionHandler finishHandler = new CompletionHandler() { + + @Override + public void completed(Integer result, ByteBuffer attachment) { + if (attachment.hasRemaining()) { + channel.write(attachment, attachment, this); + } else { + context.offerBuffer(attachment); + finish(); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + context.offerBuffer(attachment); + finish(true); + } + + }; + + private final CompletionHandler finishHandler2 = new CompletionHandler() { + + @Override + public void completed(Integer result, ByteBuffer[] attachments) { + int index = -1; + for (int i = 0; i < attachments.length; i++) { + if (attachments[i].hasRemaining()) { + index = i; + break; + } else { + context.offerBuffer(attachments[i]); + } + } + if (index == 0) { + channel.write(attachments, attachments, this); + } else if (index > 0) { + ByteBuffer[] newattachs = new ByteBuffer[attachments.length - index]; + System.arraycopy(attachments, index, newattachs, 0, newattachs.length); + channel.write(newattachs, newattachs, this); + } else { + finish(); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer[] attachments) { + for (ByteBuffer attachment : attachments) { + context.offerBuffer(attachment); + } + finish(true); + } + + }; + + protected Response(Context context, final R request) { + this.context = context; + this.request = request; + } + + protected AsyncConnection removeChannel() { + AsyncConnection ch = this.channel; + this.channel = null; + return ch; + } + + protected void prepare() { + inited = true; + } + + protected boolean recycle() { + if (!inited) return false; + boolean keepAlive = request.keepAlive; + if (recycleListener != null) { + try { + recycleListener.run(); + } catch (Exception e) { + System.err.println(request); + e.printStackTrace(); + } + recycleListener = null; + } + request.recycle(); + if (channel != null) { + if (keepAlive) { + this.context.submit(new PrepareRunner(context, channel, null)); + } else { + try { + if (channel.isOpen()) channel.close(); + } catch (Exception e) { + } + } + channel = null; + } + this.inited = false; + return true; + } + + protected void refuseAlive() { + this.request.keepAlive = false; + } + + protected void init(AsyncConnection channel) { + this.channel = channel; + this.request.channel = channel; + this.request.createtime = System.currentTimeMillis(); + } + + public void setRecycleListener(Runnable recycleListener) { + this.recycleListener = recycleListener; + } + + public void finish() { + this.finish(false); + } + + public void finish(boolean kill) { + //System.println("耗时: " + (System.currentTimeMillis() - request.createtime)); + if (kill) refuseAlive(); + this.context.responsePool.offer(this); + } + + public void finish(ByteBuffer buffer) { + this.channel.write(buffer, buffer, finishHandler); + } + + public void finish(boolean kill, ByteBuffer buffer) { + if (kill) refuseAlive(); + this.channel.write(buffer, buffer, finishHandler); + } + + public void finish(ByteBuffer... buffers) { + this.channel.write(buffers, buffers, finishHandler2); + } + + public void finish(boolean kill, ByteBuffer... buffers) { + if (kill) refuseAlive(); + this.channel.write(buffers, buffers, finishHandler2); + } + + protected void send(final ByteBuffer buffer, final A attachment, final CompletionHandler handler) { + this.channel.write(buffer, attachment, new CompletionHandler() { + + @Override + public void completed(Integer result, A attachment) { + if (buffer.hasRemaining()) { + channel.write(buffer, attachment, this); + } else { + context.offerBuffer(buffer); + if (handler != null) handler.completed(result, attachment); + } + } + + @Override + public void failed(Throwable exc, A attachment) { + context.offerBuffer(buffer); + if (handler != null) handler.failed(exc, attachment); + } + + }); + } + + protected void send(final ByteBuffer[] buffers, A attachment, CompletionHandler handler) { + this.channel.write(buffers, attachment, new CompletionHandler() { + + @Override + public void completed(Integer result, A attachment) { + int index = -1; + for (int i = 0; i < buffers.length; i++) { + if (buffers[i].hasRemaining()) { + index = i; + break; + } + context.offerBuffer(buffers[i]); + } + if (index == 0) { + channel.write(buffers, attachment, this); + } else if (index > 0) { + ByteBuffer[] newattachs = new ByteBuffer[buffers.length - index]; + System.arraycopy(buffers, index, newattachs, 0, newattachs.length); + channel.write(newattachs, attachment, this); + } else { + if (handler != null) handler.completed(result, attachment); + } + } + + @Override + public void failed(Throwable exc, A attachment) { + for (ByteBuffer buffer : buffers) { + context.offerBuffer(buffer); + } + if (handler != null) handler.failed(exc, attachment); + } + + }); + } + + public Context getContext() { + return context; + } +} diff --git a/src/org/redkale/net/SSLBuilder.java b/src/org/redkale/net/SSLBuilder.java new file mode 100644 index 000000000..2680a8164 --- /dev/null +++ b/src/org/redkale/net/SSLBuilder.java @@ -0,0 +1,160 @@ +/* + * 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.net; + +import java.nio.*; +import java.security.*; +import javax.net.ssl.*; + +/** + * + * @author zhangjx + */ +public class SSLBuilder { + + private static SSLContext sslContext; + + static { + try { + char[] keypasswd = new char[32]; + final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null, keypasswd); + final String algorithm = System.getProperty("ssl.algorithm", KeyManagerFactory.getDefaultAlgorithm()); + final KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm); + kmf.init(keyStore, keypasswd); + SSLContext sslContext0 = SSLContext.getInstance("TLS"); + sslContext0.init(kmf.getKeyManagers(), null, new SecureRandom()); + sslContext = sslContext0; + } catch (Exception e) { + throw new Error(e); + } + } + + private final SSLEngine sslEngine; + + private int appBufferSize; + + private int netBufferSize; + + public SSLBuilder() { + sslEngine = sslContext.createSSLEngine(); + //sslEngine.setEnabledCipherSuites(null); + //sslEngine.setEnabledProtocols(null); + + sslEngine.setUseClientMode(false); + sslEngine.setWantClientAuth(false); + sslEngine.setNeedClientAuth(false); + //--------------------------- + updateBufferSizes(); + } + + private void updateBufferSizes() { + final SSLSession session = sslEngine.getSession(); + appBufferSize = session.getApplicationBufferSize(); + netBufferSize = session.getPacketBufferSize(); + } + + public static void main(String[] args) throws Exception { + + } + + private static int getSSLPacketSize(final ByteBuffer buf) throws SSLException { + + /* + * SSLv2 length field is in bytes 0/1 + * SSLv3/TLS length field is in bytes 3/4 + */ + if (buf.remaining() < 5) return -1; + + final byte byte0; + final byte byte1; + final byte byte2; + final byte byte3; + final byte byte4; + + if (buf.hasArray()) { + final byte[] array = buf.array(); + int pos = buf.arrayOffset() + buf.position(); + byte0 = array[pos++]; + byte1 = array[pos++]; + byte2 = array[pos++]; + byte3 = array[pos++]; + byte4 = array[pos]; + } else { + int pos = buf.position(); + byte0 = buf.get(pos++); + byte1 = buf.get(pos++); + byte2 = buf.get(pos++); + byte3 = buf.get(pos++); + byte4 = buf.get(pos); + } + + int len; + + /* + * If we have already verified previous packets, we can + * ignore the verifications steps, and jump right to the + * determination. Otherwise, try one last hueristic to + * see if it's SSL/TLS. + */ + if (byte0 >= 20 && byte0 <= 23) { + /* + * Last sanity check that it's not a wild record + */ + final byte major = byte1; + final byte minor = byte2; + final int v = (major << 8) | minor & 0xff; + + // Check if too old (currently not possible) + // or if the major version does not match. + // The actual version negotiation is in the handshaker classes + if ((v < 0x0300) || (major > 0x03)) { + throw new SSLException("Unsupported record version major=" + major + " minor=" + minor); + } + + /* + * One of the SSLv3/TLS message types. + */ + len = ((byte3 & 0xff) << 8) + (byte4 & 0xff) + 5; // SSLv3 record header + + } else { + /* + * Must be SSLv2 or something unknown. + * Check if it's short (2 bytes) or + * long (3) header. + * + * Internals can warn about unsupported SSLv2 + */ + boolean isShort = ((byte0 & 0x80) != 0); + + if (isShort && ((byte2 == 1) || byte2 == 4)) { + + final byte major = byte3; + final byte minor = byte4; + final int v = (major << 8) | minor & 0xff; + + // Check if too old (currently not possible) + // or if the major version does not match. + // The actual version negotiation is in the handshaker classes + if ((v < 0x0300) || (major > 0x03)) { + // if it's not SSLv2, we're out of here. + if (v != 0x0002) throw new SSLException("Unsupported record version major=" + major + " minor=" + minor); + } + + /* + * Client or Server Hello + */ + int mask = 0x7f; + len = ((byte0 & mask) << 8) + (byte1 & 0xff) + (2); + } else { + // Gobblygook! + throw new SSLException("Unrecognized SSL message, plaintext connection?"); + } + } + + return len; + } +} diff --git a/src/org/redkale/net/Server.java b/src/org/redkale/net/Server.java new file mode 100644 index 000000000..9584a3a19 --- /dev/null +++ b/src/org/redkale/net/Server.java @@ -0,0 +1,188 @@ +/* + * 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.net; + +import java.io.*; +import java.lang.reflect.*; +import java.net.*; +import java.nio.charset.*; +import java.text.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.logging.*; +import org.redkale.util.*; +import org.redkale.watch.*; + +/** + * + * @author zhangjx + */ +public abstract class Server { + + public static final String RESNAME_ROOT = "SER_ROOT"; + + protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName()); + + //------------------------------------------------------------- + protected final long serverStartTime; + + protected final WatchFactory watch; + + protected final String protocol; + + protected final PrepareServlet prepare; + + protected AnyValue config; + + protected Charset charset; + + protected InetSocketAddress address; + + protected Context context; + + protected int backlog; + + protected ProtocolServer serverChannel; + + protected int capacity; + + protected int threads; + + protected ExecutorService executor; + + protected int bufferPoolSize; + + protected int responsePoolSize; + + protected int maxbody; + + protected int readTimeoutSecond; + + protected int writeTimeoutSecond; + + private ScheduledThreadPoolExecutor scheduler; + + protected Server(long serverStartTime, String protocol, PrepareServlet servlet, final WatchFactory watch) { + this.serverStartTime = serverStartTime; + this.protocol = protocol; + this.prepare = servlet; + this.watch = watch; + } + + public void init(final AnyValue config) throws Exception { + Objects.requireNonNull(config); + this.config = config; + this.address = new InetSocketAddress(config.getValue("host", "0.0.0.0"), config.getIntValue("port", 80)); + this.charset = Charset.forName(config.getValue("charset", "UTF-8")); + this.backlog = config.getIntValue("backlog", 8 * 1024); + this.readTimeoutSecond = config.getIntValue("readTimeoutSecond", 0); + this.writeTimeoutSecond = config.getIntValue("writeTimeoutSecond", 0); + this.capacity = config.getIntValue("capacity", 8 * 1024); + this.maxbody = config.getIntValue("maxbody", 64 * 1024); + this.threads = config.getIntValue("threads", Runtime.getRuntime().availableProcessors() * 16); + this.bufferPoolSize = config.getIntValue("bufferPoolSize", Runtime.getRuntime().availableProcessors() * 512); + this.responsePoolSize = config.getIntValue("responsePoolSize", Runtime.getRuntime().availableProcessors() * 256); + final int port = this.address.getPort(); + final AtomicInteger counter = new AtomicInteger(); + final Format f = createFormat(); + this.executor = Executors.newFixedThreadPool(threads, (Runnable r) -> { + Thread t = new WorkThread(executor, r); + t.setName("Servlet-" + protocol + "-" + port + "-Thread-" + f.format(counter.incrementAndGet())); + return t; + }); + } + + public void destroy(final AnyValue config) throws Exception { + this.prepare.destroy(context, config); + if (scheduler != null) scheduler.shutdownNow(); + } + + public InetSocketAddress getSocketAddress() { + return address; + } + + public String getProtocol() { + return protocol; + } + + public Logger getLogger() { + return this.logger; + } + + public void start() throws IOException { + this.context = this.createContext(); + this.prepare.init(this.context, config); + if (this.watch != null) this.watch.inject(this.prepare); + this.serverChannel = ProtocolServer.create(this.protocol, context); + this.serverChannel.open(); + if (this.serverChannel.supportedOptions().contains(StandardSocketOptions.TCP_NODELAY)) { + this.serverChannel.setOption(StandardSocketOptions.TCP_NODELAY, true); + } + serverChannel.bind(address, backlog); + serverChannel.accept(); + final String threadName = "[" + Thread.currentThread().getName() + "] "; + logger.info(threadName + this.getClass().getSimpleName() + "." + protocol + " listen: " + address + + ", threads: " + threads + ", bufferCapacity: " + capacity + ", bufferPoolSize: " + bufferPoolSize + ", responsePoolSize: " + responsePoolSize + + ", started in " + (System.currentTimeMillis() - context.getServerStartTime()) + " ms"); + } + + protected abstract Context createContext(); + + public void shutdown() throws IOException { + long s = System.currentTimeMillis(); + logger.info(this.getClass().getSimpleName() + "-" + this.protocol + " shutdowning"); + try { + this.serverChannel.close(); + } catch (Exception e) { + } + logger.info(this.getClass().getSimpleName() + "-" + this.protocol + " shutdow prepare servlet"); + this.prepare.destroy(this.context, config); + long e = System.currentTimeMillis() - s; + logger.info(this.getClass().getSimpleName() + " shutdown in " + e + " ms"); + } + + protected Format createFormat() { + String sf = "0"; + if (this.threads > 10) sf = "00"; + if (this.threads > 100) sf = "000"; + if (this.threads > 1000) sf = "0000"; + return new DecimalFormat(sf); + } + + public static void loadLib(final Logger logger, final String lib) throws Exception { + if (lib == null || lib.isEmpty()) return; + final Set set = new HashSet<>(); + for (String s : lib.split(";")) { + if (s.isEmpty()) continue; + if (s.endsWith("*")) { + File root = new File(s.substring(0, s.length() - 1)); + if (root.isDirectory()) { + for (File f : root.listFiles()) { + set.add(f.toURI().toURL()); + } + } + } else { + File f = new File(s); + if (f.canRead()) set.add(f.toURI().toURL()); + } + } + if (set.isEmpty()) return; + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl instanceof URLClassLoader) { + URLClassLoader loader = (URLClassLoader) cl; + final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + method.setAccessible(true); + for (URL url : set) { + method.invoke(loader, url); + //if (logger != null) logger.log(Level.INFO, "Server found ClassPath({0})", url); + } + } else { + Thread.currentThread().setContextClassLoader(new URLClassLoader(set.toArray(new URL[set.size()]), cl)); + } + } + +} diff --git a/src/org/redkale/net/Servlet.java b/src/org/redkale/net/Servlet.java new file mode 100644 index 000000000..4b6575092 --- /dev/null +++ b/src/org/redkale/net/Servlet.java @@ -0,0 +1,27 @@ +/* + * 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.net; + +import org.redkale.util.AnyValue; +import java.io.IOException; + +/** + * + * @author zhangjx + * @param + * @param

+ */ +public interface Servlet> { + + default void init(Context context, AnyValue config) { + } + + public void execute(R request, P response) throws IOException; + + default void destroy(Context context, AnyValue config) { + } + +} diff --git a/src/org/redkale/net/Transport.java b/src/org/redkale/net/Transport.java new file mode 100644 index 000000000..cb77d2692 --- /dev/null +++ b/src/org/redkale/net/Transport.java @@ -0,0 +1,254 @@ +/* + * 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.net; + +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.function.*; +import org.redkale.util.*; +import org.redkale.watch.*; + +/** + * 传输客户端 + * + * @author zhangjx + */ +public final class Transport { + + public static final String DEFAULT_PROTOCOL = "TCP"; + + protected static final int MAX_POOL_LIMIT = Runtime.getRuntime().availableProcessors() * 16; + + protected static final boolean supportTcpNoDelay; + + static { + boolean tcpNoDelay = false; + try { + AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(); + tcpNoDelay = channel.supportedOptions().contains(StandardSocketOptions.TCP_NODELAY); + channel.close(); + } catch (Exception e) { + } + supportTcpNoDelay = tcpNoDelay; + } + + protected final String name; + + protected final int bufferPoolSize; + + protected final int bufferCapacity; + + protected final boolean tcp; + + protected final String protocol; + + protected final AsynchronousChannelGroup group; + + protected final InetSocketAddress[] remoteAddres; + + protected final ObjectPool bufferPool; + + protected final ConcurrentHashMap> connPool = new ConcurrentHashMap<>(); + + public Transport(Transport transport, InetSocketAddress localAddress, Collection transports) { + this(transport.name, transport.protocol, null, transport.bufferPoolSize, parse(localAddress, transports)); + } + + public Transport(String name, WatchFactory watch, int bufferPoolSize, Collection addresses) { + this(name, DEFAULT_PROTOCOL, watch, bufferPoolSize, addresses); + } + + public Transport(String name, String protocol, WatchFactory watch, int bufferPoolSize, Collection addresses) { + this.name = name; + this.protocol = protocol; + this.tcp = "TCP".equalsIgnoreCase(protocol); + this.bufferPoolSize = bufferPoolSize; + this.bufferCapacity = 8192; + AsynchronousChannelGroup g = null; + try { + final AtomicInteger counter = new AtomicInteger(); + ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 8, (Runnable r) -> { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("Transport-" + name + "-Thread-" + counter.incrementAndGet()); + return t; + }); + g = AsynchronousChannelGroup.withCachedThreadPool(executor, 1); + } catch (Exception e) { + throw new RuntimeException(e); + } + this.group = g; + AtomicLong createBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber(Transport.class.getSimpleName() + "-" + name + "-" + protocol + ".Buffer.creatCounter"); + AtomicLong cycleBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber(Transport.class.getSimpleName() + "-" + name + "-" + protocol + ".Buffer.cycleCounter"); + final int rcapacity = bufferCapacity; + this.bufferPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, bufferPoolSize, + (Object... params) -> ByteBuffer.allocateDirect(rcapacity), null, (e) -> { + if (e == null || e.isReadOnly() || e.capacity() != rcapacity) return false; + e.clear(); + return true; + }); + this.remoteAddres = addresses.toArray(new InetSocketAddress[addresses.size()]); + } + + private static Collection parse(InetSocketAddress addr, Collection transports) { + final Set set = new LinkedHashSet<>(); + for (Transport t : transports) { + set.addAll(Arrays.asList(t.remoteAddres)); + } + set.remove(addr); + return set; + } + + public void close() { + connPool.forEach((k, v) -> v.forEach(c -> c.dispose())); + } + + public boolean match(Collection addrs) { + if (addrs == null) return false; + if (addrs.size() != this.remoteAddres.length) return false; + for (InetSocketAddress addr : this.remoteAddres) { + if (!addrs.contains(addr)) return false; + } + return true; + } + + public InetSocketAddress[] getRemoteAddress() { + return remoteAddres; + } + + @Override + public String toString() { + return Transport.class.getSimpleName() + "{name=" + name + ",protocol=" + protocol + ",remoteAddres=" + Arrays.toString(remoteAddres) + "}"; + } + + public int getBufferCapacity() { + return bufferCapacity; + } + + public ByteBuffer pollBuffer() { + return bufferPool.get(); + } + + public Supplier getBufferSupplier() { + return bufferPool; + } + + public void offerBuffer(ByteBuffer buffer) { + bufferPool.offer(buffer); + } + + public void offerBuffer(ByteBuffer... buffers) { + for (ByteBuffer buffer : buffers) offerBuffer(buffer); + } + + public boolean isTCP() { + return tcp; + } + + public AsyncConnection pollConnection(SocketAddress addr) { + if (addr == null && remoteAddres.length == 1) addr = remoteAddres[0]; + final boolean rand = addr == null; + if (rand && remoteAddres.length < 1) throw new RuntimeException("Transport (" + this.name + ") has no remoteAddress list"); + try { + if (tcp) { + AsynchronousSocketChannel channel = null; + if (rand) { //取地址 + for (int i = 0; i < remoteAddres.length; i++) { + addr = remoteAddres[i]; + BlockingQueue queue = connPool.get(addr); + if (queue != null && !queue.isEmpty()) { + AsyncConnection conn; + while ((conn = queue.poll()) != null) { + if (conn.isOpen()) return conn; + } + } + if (channel == null) { + channel = AsynchronousSocketChannel.open(group); + if (supportTcpNoDelay) channel.setOption(StandardSocketOptions.TCP_NODELAY, true); + } + try { + channel.connect(addr).get(2, TimeUnit.SECONDS); + break; + } catch (Exception iex) { + iex.printStackTrace(); + if (i == remoteAddres.length - 1) channel = null; + } + } + } else { + channel = AsynchronousSocketChannel.open(group); + if (supportTcpNoDelay) channel.setOption(StandardSocketOptions.TCP_NODELAY, true); + channel.connect(addr).get(2, TimeUnit.SECONDS); + } + if (channel == null) return null; + return AsyncConnection.create(channel, addr, 3000, 3000); + } else { // UDP + if (rand) addr = remoteAddres[0]; + DatagramChannel channel = DatagramChannel.open(); + channel.configureBlocking(true); + channel.connect(addr); + return AsyncConnection.create(channel, addr, true, 3000, 3000); +// AsyncDatagramChannel channel = AsyncDatagramChannel.open(group); +// channel.connect(addr); +// return AsyncConnection.create(channel, addr, true, 3000, 3000); + } + } catch (Exception ex) { + throw new RuntimeException("transport address = " + addr, ex); + } + } + + public void offerConnection(final boolean forceClose, AsyncConnection conn) { + if (!forceClose && conn.isTCP()) { //暂时每次都关闭 + if (conn.isOpen()) { + BlockingQueue queue = connPool.get(conn.getRemoteAddress()); + if (queue == null) { + queue = new ArrayBlockingQueue<>(MAX_POOL_LIMIT); + connPool.put(conn.getRemoteAddress(), queue); + } + if (!queue.offer(conn)) conn.dispose(); + } + } else { + conn.dispose(); + } + } + + public void async(SocketAddress addr, final ByteBuffer buffer, A att, final CompletionHandler handler) { + final AsyncConnection conn = pollConnection(addr); + conn.write(buffer, buffer, new CompletionHandler() { + + @Override + public void completed(Integer result, ByteBuffer attachment) { + buffer.clear(); + conn.read(buffer, buffer, new CompletionHandler() { + + @Override + public void completed(Integer result, ByteBuffer attachment) { + if (handler != null) handler.completed(result, att); + offerBuffer(buffer); + offerConnection(false, conn); + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + offerBuffer(buffer); + offerConnection(true, conn); + } + }); + + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + offerBuffer(buffer); + offerConnection(true, conn); + } + }); + } + +} diff --git a/src/org/redkale/net/WorkThread.java b/src/org/redkale/net/WorkThread.java new file mode 100644 index 000000000..c0a7f2e23 --- /dev/null +++ b/src/org/redkale/net/WorkThread.java @@ -0,0 +1,31 @@ +/* + * 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.net; + +import java.util.concurrent.*; + +/** + * + * @author zhangjx + */ +public class WorkThread extends Thread { + + private final ExecutorService executor; + + public WorkThread(ExecutorService executor, Runnable runner) { + super(runner); + this.executor = executor; + this.setDaemon(true); + } + + public void submit(Runnable runner) { + executor.submit(runner); + } + + public ExecutorService getExecutor() { + return executor; + } +} diff --git a/src/org/redkale/net/http/AuthIgnore.java b/src/org/redkale/net/http/AuthIgnore.java new file mode 100644 index 000000000..972cfdc28 --- /dev/null +++ b/src/org/redkale/net/http/AuthIgnore.java @@ -0,0 +1,22 @@ +/* + * 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.net.http; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({METHOD, TYPE}) +@Retention(RUNTIME) +public @interface AuthIgnore { + +} diff --git a/src/org/redkale/net/http/BasedHttpServlet.java b/src/org/redkale/net/http/BasedHttpServlet.java new file mode 100644 index 000000000..6ce094eaf --- /dev/null +++ b/src/org/redkale/net/http/BasedHttpServlet.java @@ -0,0 +1,260 @@ +/* + * 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.net.http; + +import org.redkale.net.Response; +import org.redkale.net.Request; +import org.redkale.net.Context; +import org.redkale.util.AnyValue; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.*; +import java.util.*; +import java.util.concurrent.*; +import jdk.internal.org.objectweb.asm.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +/** + * + * @author zhangjx + */ +public abstract class BasedHttpServlet extends HttpServlet { + + private Map.Entry[] actions; + + public boolean preExecute(HttpRequest request, HttpResponse response) throws IOException { + return true; + } + + @Override + public final void execute(HttpRequest request, HttpResponse response) throws IOException { + if (!preExecute(request, response)) return; + for (Map.Entry en : actions) { + if (request.getRequestURI().startsWith(en.getKey())) { + Entry entry = en.getValue(); + if (entry.ignore || authenticate(entry.moduleid, entry.actionid, request, response)) { + if (entry.cachetimeout > 0) {//有缓存设置 + CacheEntry ce = entry.cache.get(request.getRequestURI()); + if (ce != null && ce.time + entry.cachetimeout > System.currentTimeMillis()) { //缓存有效 + response.setStatus(ce.status); + response.setContentType(ce.contentType); + response.finish(ce.getBuffers()); + return; + } + response.setInterceptor(entry.cacheInterceptor); + } + entry.servlet.execute(request, response); + } + return; + } + } + throw new IOException(this.getClass().getName() + " not found method for URI(" + request.getRequestURI() + ")"); + } + + @Override + public void init(Context context, AnyValue config) { + String path = ((HttpContext) context).getContextPath(); + WebServlet ws = this.getClass().getAnnotation(WebServlet.class); + if (ws != null && !ws.fillurl()) path = ""; + HashMap map = load(); + this.actions = new Map.Entry[map.size()]; + int i = -1; + for (Map.Entry en : map.entrySet()) { + actions[++i] = new AbstractMap.SimpleEntry<>(path + en.getKey(), en.getValue()); + } + } + + public abstract boolean authenticate(int module, int actionid, HttpRequest request, HttpResponse response) throws IOException; + + private HashMap load() { + final boolean typeIgnore = this.getClass().getAnnotation(AuthIgnore.class) != null; + WebServlet module = this.getClass().getAnnotation(WebServlet.class); + final int serviceid = module == null ? 0 : module.moduleid(); + final HashMap map = new HashMap<>(); + Set nameset = new HashSet<>(); + for (final Method method : this.getClass().getMethods()) { + //----------------------------------------------- + String methodname = method.getName(); + if ("service".equals(methodname) || "preExecute".equals(methodname) || "execute".equals(methodname) || "authenticate".equals(methodname)) continue; + //----------------------------------------------- + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 2 || paramTypes[0] != HttpRequest.class + || paramTypes[1] != HttpResponse.class) continue; + //----------------------------------------------- + Class[] exps = method.getExceptionTypes(); + if (exps.length > 0 && (exps.length != 1 || exps[0] != IOException.class)) continue; + //----------------------------------------------- + String name = methodname; + int actionid = 0; + WebAction action = method.getAnnotation(WebAction.class); + if (action != null) { + actionid = action.actionid(); + name = action.url().trim(); + } + if (nameset.contains(name)) throw new RuntimeException(this.getClass().getSimpleName() + " has two same " + WebAction.class.getSimpleName() + "(" + name + ")"); + for (String n : nameset) { + if (n.contains(name) || name.contains(n)) { + throw new RuntimeException(this.getClass().getSimpleName() + " has two overlap " + WebAction.class.getSimpleName() + "(" + name + ", " + n + ")"); + } + } + nameset.add(name); + map.put(name, new Entry(typeIgnore, serviceid, actionid, name, method, createHttpServlet(method))); + } + return map; + } + + private HttpServlet createHttpServlet(final Method method) { + //------------------------------------------------------------------------------ + final String supDynName = HttpServlet.class.getName().replace('.', '/'); + final String interName = this.getClass().getName().replace('.', '/'); + final String interDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(this.getClass()); + final String requestSupDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(Request.class); + final String responseSupDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(Response.class); + final String requestDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(HttpRequest.class); + final String responseDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(HttpResponse.class); + String newDynName = interName + "_Dyn_" + method.getName(); + int i = 0; + for (;;) { + try { + Class.forName(newDynName.replace('/', '.')); + newDynName += "_" + (++i); + } catch (Exception ex) { + break; + } + } + //------------------------------------------------------------------------------ + ClassWriter cw = new ClassWriter(0); + FieldVisitor fv; + MethodVisitor mv; + AnnotationVisitor av0; + final String factfield = "factServlet"; + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, supDynName, null); + { + fv = cw.visitField(ACC_PUBLIC, factfield, interDesc, null, null); + fv.visitEnd(); + } + { //构造函数 + mv = (cw.visitMethod(ACC_PUBLIC, "", "()V", null, null)); + //mv.setDebug(true); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, supDynName, "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { + mv = (cw.visitMethod(ACC_PUBLIC, "execute", "(" + requestDesc + responseDesc + ")V", null, new String[]{"java/io/IOException"})); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, factfield, interDesc); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, interName, method.getName(), "(" + requestDesc + responseDesc + ")V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + } + { + mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "execute", "(" + requestSupDesc + responseSupDesc + ")V", null, new String[]{"java/io/IOException"}); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, HttpRequest.class.getName().replace('.', '/')); + mv.visitVarInsn(ALOAD, 2); + mv.visitTypeInsn(CHECKCAST, HttpResponse.class.getName().replace('.', '/')); + mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "execute", "(" + requestDesc + responseDesc + ")V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + } + cw.visitEnd(); + //------------------------------------------------------------------------------ + byte[] bytes = cw.toByteArray(); + Class newClazz = new ClassLoader(this.getClass().getClassLoader()) { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass(newDynName.replace('/', '.'), bytes); + try { + HttpServlet instance = (HttpServlet) newClazz.newInstance(); + instance.getClass().getField(factfield).set(instance, this); + return instance; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private static final class Entry { + + public Entry(boolean typeIgnore, int moduleid, int actionid, String name, Method method, HttpServlet servlet) { + this.moduleid = moduleid; + this.actionid = actionid; + this.name = name; + this.method = method; + this.servlet = servlet; + this.ignore = typeIgnore || method.getAnnotation(AuthIgnore.class) != null; + HttpCacheable hc = method.getAnnotation(HttpCacheable.class); + this.cachetimeout = hc == null ? 0 : hc.timeout() * 1000; + this.cache = cachetimeout > 0 ? new ConcurrentHashMap() : null; + this.cacheInterceptor = cachetimeout > 0 ? (HttpResponse response, ByteBuffer[] buffers) -> { + int status = response.getStatus(); + if (status != 200) return null; + CacheEntry ce = new CacheEntry(response.getStatus(), response.getContentType(), buffers); + cache.put(response.getRequest().getRequestURI(), ce); + return ce.getBuffers(); + } : null; + } + + public boolean isNeedCheck() { + return this.moduleid != 0 || this.actionid != 0; + } + + public final HttpResponse.Interceptor cacheInterceptor; + + public final ConcurrentHashMap cache; + + public final int cachetimeout; + + public final boolean ignore; + + public final int moduleid; + + public final int actionid; + + public final String name; + + public final Method method; + + public final HttpServlet servlet; + } + + private static final class CacheEntry { + + public final long time = System.currentTimeMillis(); + + private final ByteBuffer[] buffers; + + private final int status; + + private final String contentType; + + public CacheEntry(int status, String contentType, ByteBuffer[] bufs) { + this.status = status; + this.contentType = contentType; + final ByteBuffer[] newBuffers = new ByteBuffer[bufs.length]; + for (int i = 0; i < newBuffers.length; i++) { + newBuffers[i] = bufs[i].duplicate().asReadOnlyBuffer(); + } + this.buffers = newBuffers; + } + + public ByteBuffer[] getBuffers() { + final ByteBuffer[] newBuffers = new ByteBuffer[buffers.length]; + for (int i = 0; i < newBuffers.length; i++) { + newBuffers[i] = buffers[i].duplicate(); + } + return newBuffers; + } + } +} diff --git a/src/org/redkale/net/http/HttpCacheable.java b/src/org/redkale/net/http/HttpCacheable.java new file mode 100644 index 000000000..83f1d04db --- /dev/null +++ b/src/org/redkale/net/http/HttpCacheable.java @@ -0,0 +1,25 @@ +/* + * 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.net.http; + +import java.lang.annotation.*; + +/** + * + * @author zhangjx + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface HttpCacheable { + + /** + * 超时的秒数 + * + * @return + */ + int timeout() default 15; +} diff --git a/src/org/redkale/net/http/HttpContext.java b/src/org/redkale/net/http/HttpContext.java new file mode 100644 index 000000000..648a51ad0 --- /dev/null +++ b/src/org/redkale/net/http/HttpContext.java @@ -0,0 +1,59 @@ +/* + * 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.net.http; + +import java.net.*; +import java.nio.*; +import java.nio.charset.*; +import java.security.*; +import java.util.concurrent.*; +import java.util.logging.*; +import org.redkale.net.*; +import org.redkale.util.*; +import org.redkale.watch.*; + +/** + * + * @author zhangjx + */ +public final class HttpContext extends Context { + + protected final String contextPath; + + protected final SecureRandom random = new SecureRandom(); + + public HttpContext(long serverStartTime, Logger logger, ExecutorService executor, int bufferCapacity, ObjectPool bufferPool, + ObjectPool responsePool, int maxbody, Charset charset, InetSocketAddress address, PrepareServlet prepare, + WatchFactory watch, int readTimeoutSecond, int writeTimeoutSecond, String contextPath) { + super(serverStartTime, logger, executor, bufferCapacity, bufferPool, responsePool, maxbody, charset, + address, prepare, watch, readTimeoutSecond, writeTimeoutSecond); + this.contextPath = contextPath; + random.setSeed(Math.abs(System.nanoTime())); + } + + public String getContextPath() { + return this.contextPath; + } + + protected String createSessionid() { + byte[] bytes = new byte[16]; + random.nextBytes(bytes); + return new String(Utility.binToHex(bytes)); + } + + protected WatchFactory getWatchFactory() { + return watch; + } + + protected ExecutorService getExecutor() { + return executor; + } + + protected ObjectPool getResponsePool() { + return responsePool; + } + +} diff --git a/src/org/redkale/net/http/HttpPrepareServlet.java b/src/org/redkale/net/http/HttpPrepareServlet.java new file mode 100644 index 000000000..0a78b850a --- /dev/null +++ b/src/org/redkale/net/http/HttpPrepareServlet.java @@ -0,0 +1,152 @@ +/* + * 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.net.http; + +import org.redkale.util.AnyValue.DefaultAnyValue; +import java.io.*; +import java.nio.*; +import java.util.*; +import java.util.AbstractMap.SimpleEntry; +import java.util.function.*; +import java.util.logging.*; +import java.util.regex.*; +import org.redkale.net.*; +import org.redkale.util.*; +import org.redkale.watch.*; + +/** + * + * @author zhangjx + */ +public final class HttpPrepareServlet extends PrepareServlet> { + + private ByteBuffer flashPolicyBuffer; + + private final List servlets = new ArrayList<>(); + + private final Map strmaps = new HashMap<>(); + + private SimpleEntry, HttpServlet>[] regArray = new SimpleEntry[0]; + + private HttpServlet resourceHttpServlet = new HttpResourceServlet(); + + private String flashdomain = "*"; + + private String flashports = "80,443,$"; + + @Override + public void init(Context context, AnyValue config) { + this.servlets.stream().forEach(s -> { + s.init(context, s.conf); + }); + final WatchFactory watch = ((HttpContext) context).getWatchFactory(); + if (watch != null) { + this.servlets.stream().forEach(s -> { + watch.inject(s); + }); + } + if (config != null) { + AnyValue ssConfig = config.getAnyValue("servlets"); + if (ssConfig != null) { + AnyValue resConfig = ssConfig.getAnyValue("resource-servlet"); + if (resConfig instanceof DefaultAnyValue) { + if (resConfig.getValue("webroot") == null) + ((DefaultAnyValue) resConfig).addValue("webroot", config.getValue("root")); + } + if (resConfig == null) { + DefaultAnyValue dresConfig = new DefaultAnyValue(); + dresConfig.addValue("webroot", config.getValue("root")); + resConfig = dresConfig; + } + this.resourceHttpServlet.init(context, resConfig); + } + this.flashdomain = config.getValue("crossdomain-domain", "*"); + this.flashports = config.getValue("crossdomain-ports", "80,443,$"); + } + } + + @Override + public void execute(HttpRequest request, HttpResponse response) throws IOException { + try { + if (request.flashPolicy) { + response.skipHeader(); + if (flashPolicyBuffer == null) { + flashPolicyBuffer = ByteBuffer.wrap(("" + + "" + + "" + + "").getBytes()).asReadOnlyBuffer(); + } + response.finish(true, flashPolicyBuffer.duplicate()); + return; + } + final String uri = request.getRequestURI(); + HttpServlet servlet = this.strmaps.isEmpty() ? null : this.strmaps.get(uri); + if (servlet == null && this.regArray != null) { + for (SimpleEntry, HttpServlet> en : regArray) { + if (en.getKey().test(uri)) { + servlet = en.getValue(); + break; + } + } + } + if (servlet == null) servlet = this.resourceHttpServlet; + servlet.execute(request, response); + } catch (Exception e) { + request.getContext().getLogger().log(Level.WARNING, "Servlet occur, forece to close channel. request = " + request, e); + response.finish(500, null); + } + } + + public void addHttpServlet(HttpServlet servlet, AnyValue conf, String... mappings) { + for (String mapping : mappings) { + if (contains(mapping, '.', '*', '{', '[', '(', '|', '^', '$', '+', '?', '\\')) { //是否是正则表达式)) + if (mapping.charAt(0) != '^') mapping = '^' + mapping; + if (mapping.endsWith("/*")) { + mapping = mapping.substring(0, mapping.length() - 1) + ".*"; + } else { + mapping += "$"; + } + if (regArray == null) { + regArray = new SimpleEntry[1]; + regArray[0] = new SimpleEntry<>(Pattern.compile(mapping).asPredicate(), servlet); + } else { + regArray = Arrays.copyOf(regArray, regArray.length + 1); + regArray[regArray.length - 1] = new SimpleEntry<>(Pattern.compile(mapping).asPredicate(), servlet); + } + } else if (mapping != null && !mapping.isEmpty()) { + strmaps.put(mapping, servlet); + } + } + servlet.conf = conf; + this.servlets.add(servlet); + } + + private static boolean contains(String string, char... values) { + if (string == null) return false; + for (char ch : Utility.charArray(string)) { + for (char ch2 : values) { + if (ch == ch2) return true; + } + } + return false; + } + + public void setResourceServlet(HttpServlet servlet) { + if (servlet != null) { + this.resourceHttpServlet = servlet; + } + } + + @Override + public void destroy(Context context, AnyValue config) { + this.resourceHttpServlet.destroy(context, config); + this.servlets.stream().forEach(s -> { + s.destroy(context, s.conf); + }); + } + +} diff --git a/src/org/redkale/net/http/HttpRequest.java b/src/org/redkale/net/http/HttpRequest.java new file mode 100644 index 000000000..989a6bfc2 --- /dev/null +++ b/src/org/redkale/net/http/HttpRequest.java @@ -0,0 +1,531 @@ +/* + * 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.net.http; + +import org.redkale.util.ByteArray; +import org.redkale.util.AnyValue.DefaultAnyValue; +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.nio.charset.*; +import org.redkale.convert.json.*; +import org.redkale.net.*; + +/** + * + * @author zhangjx + */ +public class HttpRequest extends Request { + + protected static final Charset UTF8 = Charset.forName("UTF-8"); + + protected static final String SESSIONID_NAME = "JSESSIONID"; + + private static final byte[] flashRequestContent1 = "\0".getBytes(); + + private static final byte[] flashRequestContent2 = "".getBytes(); + + private String method; + + private String protocol; + + protected String requestURI; + + private long contentLength = -1; + + private String contentType; + + private String host; + + private String connection; + + protected String cookiestr; + + private HttpCookie[] cookies; + + protected String newsessionid; + + protected final DefaultAnyValue header = new DefaultAnyValue(); + + protected final DefaultAnyValue params = new DefaultAnyValue(); + + private final ByteArray array = new ByteArray(); + + private boolean bodyparsed = false; + + protected boolean flashPolicy = false; + + protected boolean boundary = false; + + private final String remoteAddrHeader; + + public HttpRequest(Context context, String remoteAddrHeader) { + super(context); + this.remoteAddrHeader = remoteAddrHeader; + } + + protected void setKeepAlive(boolean keepAlive) { + this.keepAlive = keepAlive; + } + + protected boolean isKeepAlive() { + return this.keepAlive; + } + + protected AsyncConnection getChannel() { + return this.channel; + } + + protected JsonConvert getJsonConvert(){ + return this.jsonConvert; + } + + @Override + protected int readHeader(final ByteBuffer buffer) { + if (!readLine(buffer, array)) { + if (array.equal(flashRequestContent1) || array.equal(flashRequestContent2)) { //兼容 flash socket + this.flashPolicy = true; + return 0; + } + return -1; + } + Charset charset = this.context.getCharset(); + int index = 0; + int offset = array.find(index, ' '); + if (offset <= 0) return -1; + this.method = array.toString(index, offset, charset).trim(); + index = ++offset; + offset = array.find(index, ' '); + if (offset <= 0) return -1; + int off = array.find(index, '#'); + if (off > 0) offset = off; + int qst = array.find(index, offset, (byte) '?'); + if (qst > 0) { + this.requestURI = array.toDecodeString(index, qst - index, charset).trim(); + addParameter(array, qst + 1, offset - qst - 1); + } else { + this.requestURI = array.toDecodeString(index, offset - index, charset).trim(); + } + if (this.requestURI.contains("../")) return -1; + index = ++offset; + this.protocol = array.toString(index, array.count() - index, charset).trim(); + while (readLine(buffer, array)) { + if (array.count() < 2) break; + index = 0; + offset = array.find(index, ':'); + if (offset <= 0) return -1; + String name = array.toString(index, offset, charset).trim(); + index = offset + 1; + String value = array.toString(index, array.count() - index, charset).trim(); + switch (name) { + case "Content-Type": + this.contentType = value; + break; + case "Content-Length": + this.contentLength = Long.decode(value); + break; + case "Host": + this.host = value; + break; + case "Cookie": + if (this.cookiestr == null || this.cookiestr.isEmpty()) { + this.cookiestr = value; + } else { + this.cookiestr += ";" + value; + } + break; + case "Connection": + this.connection = value; + this.setKeepAlive(!"close".equalsIgnoreCase(value)); + break; + default: + header.addValue(name, value); + } + } + array.clear(); + if (buffer.hasRemaining()) array.add(buffer, buffer.remaining()); + if (this.contentType != null && this.contentType.contains("boundary=")) { + this.boundary = true; + } + if (this.contentLength > 0 && (this.contentType == null || !this.boundary)) { + if (this.contentLength > context.getMaxbody()) return -1; + int lr = (int) this.contentLength - array.count(); + return lr > 0 ? lr : 0; + } + return 0; + } + + @Override + protected int readBody(ByteBuffer buffer) { + int len = buffer.remaining(); + array.add(buffer, len); + return len; + } + + @Override + protected void prepare() { + } + + private void parseBody() { + if (this.boundary || bodyparsed) return; + addParameter(array, 0, array.count()); + bodyparsed = true; + } + + private void addParameter(final ByteArray array, final int offset, final int len) { + if (len < 1) return; + Charset charset = this.context.getCharset(); + int limit = offset + len; + int keypos = array.find(offset, limit, '='); + int valpos = array.find(offset, limit, '&'); + if (keypos <= 0 || (valpos >= 0 && valpos < keypos)) { + if (valpos > 0) addParameter(array, valpos + 1, limit - valpos - 1); + return; + } + String name = array.toDecodeString(offset, keypos - offset, charset); + ++keypos; + String value = array.toDecodeString(keypos, (valpos < 0) ? (limit - keypos) : (valpos - keypos), charset); + this.params.addValue(name, value); + if (valpos >= 0) { + addParameter(array, valpos + 1, limit - valpos - 1); + } + } + + private boolean readLine(ByteBuffer buffer, ByteArray bytes) { + byte lasted = '\r'; + bytes.clear(); + for (;;) { + if (!buffer.hasRemaining()) { + if (lasted != '\r') bytes.add(lasted); + return false; + } + byte b = buffer.get(); + if (b == -1 || (lasted == '\r' && b == '\n')) break; + if (lasted != '\r') bytes.add(lasted); + lasted = b; + } + return true; + } + + @Override + protected void setProperty(String name, Object value) { + super.setProperty(name, value); + } + + @Override + @SuppressWarnings("unchecked") + protected T getProperty(String name) { + return super.getProperty(name); + } + + @Override + protected void removeProperty(String name) { + super.removeProperty(name); + } + + @Override + public HttpContext getContext() { + return (HttpContext) this.context; + } + + public String getRemoteAddr() { + if (remoteAddrHeader != null) { + String val = getHeader(remoteAddrHeader); + if (val != null) return val; + } + SocketAddress addr = getRemoteAddress(); + if (addr == null) return ""; + if (addr instanceof InetSocketAddress) return ((InetSocketAddress) addr).getAddress().getHostAddress(); + return String.valueOf(addr); + } + + public String getBody(final Charset charset) { + return array.toString(charset); + } + + public String getBody() { + return array.toString(); + } + + public String getBodyUTF8() { + return array.toString(UTF8); + } + + public SocketAddress getRemoteAddress() { + return this.channel.getRemoteAddress(); + } + + @Override + public String toString() { + parseBody(); + return this.getClass().getSimpleName() + "{method:" + this.method + ", requestURI:" + this.requestURI + + ", contentType:" + this.contentType + ", connection:" + this.connection + ", protocol:" + this.protocol + + ", contentLength:" + this.contentLength + ", cookies:" + this.cookiestr + + ", host:" + this.host + ", params:" + this.params + ", header:" + this.header + "}"; + } + + public final MultiContext getMultiContext() { + return new MultiContext(context.getCharset(), this.getContentType(), + new BufferedInputStream(Channels.newInputStream(this.channel), Math.max(array.count(), 8192)) { + { + array.write(this.buf); + this.count = array.count(); + } + }); + } + + @Override + protected void recycle() { + this.cookiestr = null; + this.cookies = null; + this.newsessionid = null; + this.method = null; + this.protocol = null; + this.requestURI = null; + this.contentType = null; + this.host = null; + this.connection = null; + this.contentLength = -1; + this.boundary = false; + this.bodyparsed = false; + this.flashPolicy = false; + + this.header.clear(); + this.params.clear(); + this.array.clear(); + super.recycle(); + } + + public String getSessionid(boolean create) { + String sessionid = getCookie(SESSIONID_NAME, null); + if (create && (sessionid == null || sessionid.isEmpty())) { + sessionid = ((HttpContext) context).createSessionid(); + this.newsessionid = sessionid; + } + return sessionid; + } + + public String changeSessionid() { + this.newsessionid = ((HttpContext) context).createSessionid(); + return newsessionid; + } + + public void invalidateSession() { + this.newsessionid = ""; //为空表示删除sessionid + } + + public HttpCookie[] getCookies() { + if (this.cookies == null) this.cookies = parseCookies(this.cookiestr); + return this.cookies; + } + + public String getCookie(String name) { + return getCookie(name, null); + } + + public String getCookie(String name, String dfvalue) { + for (HttpCookie cookie : getCookies()) { + if (name.equals(cookie.getName())) return cookie.getValue(); + } + return dfvalue; + } + + private static HttpCookie[] parseCookies(String cookiestr) { + if (cookiestr == null || cookiestr.isEmpty()) return new HttpCookie[0]; + String str = cookiestr.replaceAll("(^;)|(;$)", "").replaceAll(";+", ";"); + if (str.isEmpty()) return new HttpCookie[0]; + String[] strs = str.split(";"); + HttpCookie[] cookies = new HttpCookie[strs.length]; + for (int i = 0; i < strs.length; i++) { + String s = strs[i]; + int pos = s.indexOf('='); + String v = (pos < 0 ? "" : s.substring(pos + 1)); + if (v.indexOf('"') == 0 && v.lastIndexOf('"') == v.length() - 1) v = v.substring(1, v.length() - 1); + cookies[i] = new HttpCookie((pos < 0 ? s : s.substring(0, pos)), v); + } + return cookies; + } + + public String getConnection() { + return connection; + } + + public String getMethod() { + return method; + } + + public String getProtocol() { + return protocol; + } + + public String getHost() { + return host; + } + + protected static InetSocketAddress parseSocketAddress(String host) { + if (host == null || host.isEmpty()) return null; + int pos = host.indexOf(':'); + String hostname = pos < 0 ? host : host.substring(0, pos); + int port = pos < 0 ? 80 : Integer.parseInt(host.substring(pos + 1)); + return new InetSocketAddress(hostname, port); + } + + protected InetSocketAddress getHostSocketAddress() { + return parseSocketAddress(host); + } + + /** + * 截取getRequestURI最后的一个/后面的部分 + * + * @return + */ + public String getRequstURILastPath() { + if (requestURI == null) return ""; + return requestURI.substring(requestURI.lastIndexOf('/') + 1); + } + + /** + * + * 从prefix之后截取getRequestURI再对"/"进行分隔 + *

+ * @param prefix + * @return + */ + public String[] getRequstURIPaths(String prefix) { + if (requestURI == null || prefix == null) return new String[0]; + return requestURI.substring(requestURI.indexOf(prefix) + prefix.length() + (prefix.endsWith("/") ? 0 : 1)).split("/"); + } + + public String getRequstURIPath(String prefix, String defvalue) { + if (requestURI == null || prefix == null) return defvalue; + int pos = requestURI.indexOf(prefix); + if (pos < 0) return defvalue; + String sub = requestURI.substring(pos + prefix.length()); + pos = sub.indexOf('/'); + return pos < 0 ? sub : sub.substring(0, pos); + } + + public short getRequstURIPath(String prefix, short defvalue) { + String val = getRequstURIPath(prefix, null); + return val == null ? defvalue : Short.parseShort(val); + } + + public int getRequstURIPath(String prefix, int defvalue) { + String val = getRequstURIPath(prefix, null); + return val == null ? defvalue : Integer.parseInt(val); + } + + public long getRequstURIPath(String prefix, long defvalue) { + String val = getRequstURIPath(prefix, null); + return val == null ? defvalue : Long.parseLong(val); + } + + public String getRequestURI() { + return requestURI; + } + + public long getContentLength() { + return contentLength; + } + + public String getContentType() { + return contentType; + } + + //------------------------------------------------------------------------------ + public String[] getHeaderNames() { + return header.getNames(); + } + + public String getHeader(String name) { + return header.getValue(name); + } + + public T getJsonHeader(Class clazz, String name) { + String v = getHeader(name); + return v == null || v.isEmpty() ? null : jsonConvert.convertFrom(clazz, v); + } + + public boolean getBooleanHeader(String name, boolean defaultValue) { + return header.getBoolValue(name, defaultValue); + } + + public short getShortHeader(String name, short defaultValue) { + return header.getShortValue(name, defaultValue); + } + + public int getIntHeader(String name, int defaultValue) { + return header.getIntValue(name, defaultValue); + } + + public long getLongHeader(String name, long defaultValue) { + return header.getLongValue(name, defaultValue); + } + + public float getFloatHeader(String name, float defaultValue) { + return header.getFloatValue(name, defaultValue); + } + + public double getDoubleHeader(String name, double defaultValue) { + return header.getDoubleValue(name, defaultValue); + } + + public String getHeader(String name, String defaultValue) { + return header.getValue(name, defaultValue); + } + + //------------------------------------------------------------------------------ + public String[] getParameterNames() { + parseBody(); + return params.getNames(); + } + + public String getParameter(String name) { + parseBody(); + return params.getValue(name); + } + + public T getJsonParameter(Class clazz, String name) { + String v = getParameter(name); + return v == null || v.isEmpty() ? null : jsonConvert.convertFrom(clazz, v); + } + + public boolean getBooleanParameter(String name, boolean defaultValue) { + parseBody(); + return params.getBoolValue(name, defaultValue); + } + + public short getShortParameter(String name, short defaultValue) { + parseBody(); + return params.getShortValue(name, defaultValue); + } + + public int getIntParameter(String name, int defaultValue) { + parseBody(); + return params.getIntValue(name, defaultValue); + } + + public long getLongParameter(String name, long defaultValue) { + parseBody(); + return params.getLongValue(name, defaultValue); + } + + public float getFloatParameter(String name, float defaultValue) { + parseBody(); + return params.getFloatValue(name, defaultValue); + } + + public double getDoubleParameter(String name, double defaultValue) { + parseBody(); + return params.getDoubleValue(name, defaultValue); + } + + public String getParameter(String name, String defaultValue) { + parseBody(); + return params.getValue(name, defaultValue); + } +} diff --git a/src/org/redkale/net/http/HttpResourceServlet.java b/src/org/redkale/net/http/HttpResourceServlet.java new file mode 100644 index 000000000..7a6277b46 --- /dev/null +++ b/src/org/redkale/net/http/HttpResourceServlet.java @@ -0,0 +1,260 @@ +/* + * 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.net.http; + +import java.io.*; +import java.nio.*; +import java.nio.file.*; +import static java.nio.file.StandardWatchEventKinds.*; +import java.util.*; +import java.util.AbstractMap.SimpleEntry; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.function.*; +import java.util.logging.*; +import java.util.regex.*; +import org.redkale.net.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class HttpResourceServlet extends HttpServlet { + + private static final Logger logger = Logger.getLogger(HttpResourceServlet.class.getSimpleName()); + + protected class WatchThread extends Thread { + + protected final File root; + + protected final WatchService watcher; + + public WatchThread(File root) throws IOException { + this.root = root; + this.setName("Servlet-ResourceWatch-Thread"); + this.setDaemon(true); + this.watcher = this.root.toPath().getFileSystem().newWatchService(); + } + + @Override + public void run() { + try { + final String rootstr = root.getCanonicalPath(); + while (!this.isInterrupted()) { + final WatchKey key = watcher.take(); + final Path parent = keymaps.get(key); + if (parent == null) { + key.cancel(); + continue; + } + key.pollEvents().stream().forEach((event) -> { + try { + Path path = parent.resolve((Path) event.context()); + final String uri = path.toString().substring(rootstr.length()).replace('\\', '/'); + //logger.log(Level.FINEST, "file(" + uri + ") happen " + event.kind() + " event"); + if (event.kind() == ENTRY_DELETE) { + files.remove(uri); + } else if (event.kind() == ENTRY_MODIFY) { + FileEntry en = files.get(uri); + if (en != null) { + Thread.sleep(5000L); //等待update file完毕 + en.update(); + } + } + } catch (Exception ex) { + logger.log(Level.FINE, event.context() + " occur erroneous", ex); + } + }); + key.reset(); + } + } catch (Exception e) { + } + } + } + + //缓存总大小, 默认128M + protected long cachelimit = 128 * 1024 * 1024L; + + protected final LongAdder cachedLength = new LongAdder(); + + //最大可缓存的文件大小, 大于该值的文件将不被缓存 + protected long cachelengthmax = 1 * 1024 * 1024; + + protected File root = new File("./root/"); + + protected final ConcurrentHashMap files = new ConcurrentHashMap<>(); + + protected final ConcurrentHashMap keymaps = new ConcurrentHashMap<>(); + + protected SimpleEntry[] locationRewrites; + + protected WatchThread watchThread; + + protected Predicate ranges; + + @Override + public void init(Context context, AnyValue config) { + if (config != null) { + String rootstr = config.getValue("webroot", "root"); + if (rootstr.indexOf(':') < 0 && rootstr.indexOf('/') != 0 && System.getProperty("APP_HOME") != null) { + rootstr = new File(System.getProperty("APP_HOME"), rootstr).getPath(); + } + String rangesValue = config.getValue("ranges"); + this.ranges = rangesValue != null ? Pattern.compile(rangesValue).asPredicate() : null; + try { + this.root = new File(rootstr).getCanonicalFile(); + } catch (IOException ioe) { + this.root = new File(rootstr); + } + AnyValue cacheconf = config.getAnyValue("caches"); + if (cacheconf != null) { + this.cachelimit = parseLenth(cacheconf.getValue("limit"), 128 * 1024 * 1024L); + this.cachelengthmax = parseLenth(cacheconf.getValue("lengthmax"), 1 * 1024 * 1024L); + } + List> locations = new ArrayList<>(); + for (AnyValue av : config.getAnyValues("rewrite")) { + if ("location".equals(av.getValue("type"))) { + String m = av.getValue("match"); + String f = av.getValue("forward"); + if (m != null && f != null) { + locations.add(new SimpleEntry<>(Pattern.compile(m), f)); + } + } + } + this.locationRewrites = locations.isEmpty() ? null : locations.toArray(new SimpleEntry[locations.size()]); + } + if (this.cachelimit < 1) return; //不缓存不需要开启WatchThread监听 + if (this.root != null) { + try { + this.watchThread = new WatchThread(this.root); + this.watchThread.start(); + } catch (IOException ex) { + logger.log(Level.WARNING, HttpResourceServlet.class.getSimpleName() + " start watch-thread error", ex); + } + } + } + + @Override + public void destroy(Context context, AnyValue config) { + if (this.watchThread != null) { + try { + this.watchThread.watcher.close(); + } catch (IOException ex) { + logger.log(Level.WARNING, HttpResourceServlet.class.getSimpleName() + " close watch-thread error", ex); + } + if (this.watchThread.isAlive()) this.watchThread.interrupt(); + } + } + + private static long parseLenth(String value, long defValue) { + if (value == null) return defValue; + value = value.toUpperCase().replace("B", ""); + if (value.endsWith("G")) return Long.decode(value.replace("G", "")) * 1024 * 1024 * 1024; + if (value.endsWith("M")) return Long.decode(value.replace("M", "")) * 1024 * 1024; + if (value.endsWith("K")) return Long.decode(value.replace("K", "")) * 1024; + return Long.decode(value); + } + + @Override + public void execute(HttpRequest request, HttpResponse response) throws IOException { + String uri = request.getRequestURI(); + if (locationRewrites != null) { + for (SimpleEntry entry : locationRewrites) { + Matcher matcher = entry.getKey().matcher(uri); + if (matcher.find()) { + StringBuffer sb = new StringBuffer(uri.length()); + matcher.appendReplacement(sb, entry.getValue()); + matcher.appendTail(sb); + uri = sb.toString(); + break; + } + } + } + if (uri.length() == 0 || uri.equals("/")) uri = "/index.html"; + //System.out.println(request); + FileEntry entry; + if (watchThread == null) { + entry = createFileEntry(uri); + } else { //有缓存 + entry = files.computeIfAbsent(uri, x -> createFileEntry(x)); + } + if (entry == null) { + response.finish404(); + } else { + response.finishFile(entry.file, entry.content); + } + } + + private FileEntry createFileEntry(String uri) { + File file = new File(root, uri); + if (file.isDirectory()) file = new File(file, "index.html"); + if (!file.isFile() || !file.canRead()) return null; + FileEntry en = new FileEntry(this, file); + if (watchThread == null) return en; + try { + Path p = file.getParentFile().toPath(); + keymaps.put(p.register(watchThread.watcher, ENTRY_MODIFY, ENTRY_DELETE), p); + } catch (IOException e) { + logger.log(Level.INFO, HttpResourceServlet.class.getSimpleName() + " watch FileEntry(" + uri + ") erroneous", e); + } + return en; + } + + private static final class FileEntry { + + final File file; + + private final HttpResourceServlet servlet; + + ByteBuffer content; + + public FileEntry(final HttpResourceServlet servlet, File file) { + this.servlet = servlet; + this.file = file; + update(); + } + + public void update() { + if (this.content != null) { + this.servlet.cachedLength.add(0L - this.content.remaining()); + this.content = null; + } + long length = this.file.length(); + if (length > this.servlet.cachelengthmax) return; + if (this.servlet.cachedLength.longValue() + length > this.servlet.cachelimit) return; //超过缓存总容量 + try { + FileInputStream in = new FileInputStream(file); + ByteArrayOutputStream out = new ByteArrayOutputStream((int) file.length()); + byte[] bytes = new byte[10240]; + int pos; + while ((pos = in.read(bytes)) != -1) { + out.write(bytes, 0, pos); + } + in.close(); + byte[] bs = out.toByteArray(); + ByteBuffer buf = ByteBuffer.allocateDirect(bs.length); + buf.put(bs); + buf.flip(); + this.content = buf.asReadOnlyBuffer(); + this.servlet.cachedLength.add(this.content.remaining()); + } catch (Exception e) { + logger.log(Level.INFO, HttpResourceServlet.class.getSimpleName() + " update FileEntry(" + file + ") erroneous", e); + } + } + + @Override + protected void finalize() throws Throwable { + if (this.content != null) this.servlet.cachedLength.add(0L - this.content.remaining()); + super.finalize(); + } + + public long getCachedLength() { + return this.content == null ? 0L : this.content.remaining(); + } + + } +} diff --git a/src/org/redkale/net/http/HttpResponse.java b/src/org/redkale/net/http/HttpResponse.java new file mode 100644 index 000000000..c715911eb --- /dev/null +++ b/src/org/redkale/net/http/HttpResponse.java @@ -0,0 +1,561 @@ +/* + * 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.net.http; + +import org.redkale.util.AnyValue.DefaultAnyValue; +import org.redkale.util.AnyValue.Entry; +import java.io.*; +import java.lang.reflect.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.nio.file.*; +import java.text.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import org.redkale.net.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + * @param + */ +public class HttpResponse extends Response { + + /** + * HttpResponse.finish 方法内调用 + * + */ + public static interface Interceptor { + + public ByteBuffer[] invoke(final HttpResponse response, final ByteBuffer[] buffers); + } + + private static final ByteBuffer buffer304 = ByteBuffer.wrap("HTTP/1.1 304 Not Modified\r\n\r\n".getBytes()).asReadOnlyBuffer(); + + private static final ByteBuffer buffer404 = ByteBuffer.wrap("HTTP/1.1 404 Not Found\r\nContent-Length:0\r\n\r\n".getBytes()).asReadOnlyBuffer(); + + protected static final byte[] LINE = new byte[]{'\r', '\n'}; + + private static final Set options = new HashSet<>(); + + private static final DateFormat GMT_DATE_FORMAT = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss z", Locale.ENGLISH); + + private static final Map httpCodes = new HashMap<>(); + + static { + options.add(StandardOpenOption.READ); + GMT_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT")); + + httpCodes.put(100, "Continue"); + httpCodes.put(101, "Switching Protocols"); + + httpCodes.put(200, "OK"); + httpCodes.put(201, "Created"); + httpCodes.put(202, "Accepted"); + httpCodes.put(203, "Non-Authoritative Information"); + httpCodes.put(204, "No Content"); + httpCodes.put(205, "Reset Content"); + httpCodes.put(206, "Partial Content"); + + httpCodes.put(300, "Multiple Choices"); + httpCodes.put(301, "Moved Permanently"); + httpCodes.put(302, "Found"); + httpCodes.put(303, "See Other"); + httpCodes.put(304, "Not Modified"); + httpCodes.put(305, "Use Proxy"); + httpCodes.put(307, "Temporary Redirect"); + + httpCodes.put(400, "Bad Request"); + httpCodes.put(401, "Unauthorized"); + httpCodes.put(402, "Payment Required"); + httpCodes.put(403, "Forbidden"); + httpCodes.put(404, "Not Found"); + httpCodes.put(405, "Method Not Allowed"); + httpCodes.put(406, "Not Acceptable"); + httpCodes.put(407, "Proxy Authentication Required"); + httpCodes.put(408, "Request Timeout"); + httpCodes.put(409, "Conflict"); + httpCodes.put(410, "Gone"); + httpCodes.put(411, "Length Required"); + httpCodes.put(412, "Precondition Failed"); + httpCodes.put(413, "Request Entity Too Large"); + httpCodes.put(414, "Request URI Too Long"); + httpCodes.put(415, "Unsupported Media Type"); + httpCodes.put(416, "Requested Range Not Satisfiable"); + httpCodes.put(417, "Expectation Failed"); + + httpCodes.put(500, "Internal Server Error"); + httpCodes.put(501, "Not Implemented"); + httpCodes.put(502, "Bad Gateway"); + httpCodes.put(503, "Service Unavailable"); + httpCodes.put(504, "Gateway Timeout"); + httpCodes.put(505, "HTTP Version Not Supported"); + } + + private int status = 200; + + private String contentType = "text/plain; charset=utf-8"; + + private long contentLength = -1; + + private HttpCookie[] cookies; + + private boolean headsended = false; + + private Interceptor interceptor; + //------------------------------------------------ + + private final DefaultAnyValue header = new DefaultAnyValue(); + + private final String[][] defaultAddHeaders; + + private final String[][] defaultSetHeaders; + + private final HttpCookie defcookie; + + public static ObjectPool createPool(AtomicLong creatCounter, AtomicLong cycleCounter, int max, Creator creator) { + return new ObjectPool<>(creatCounter, cycleCounter, max, creator, (x) -> ((HttpResponse) x).prepare(), (x) -> ((HttpResponse) x).recycle()); + } + + public HttpResponse(Context context, R request, String[][] defaultAddHeaders, String[][] defaultSetHeaders, HttpCookie defcookie) { + super(context, request); + this.defaultAddHeaders = defaultAddHeaders; + this.defaultSetHeaders = defaultSetHeaders; + this.defcookie = defcookie; + } + + @Override + protected AsyncConnection removeChannel() { + return super.removeChannel(); + } + + @Override + protected boolean recycle() { + this.status = 200; + this.contentLength = -1; + this.contentType = null; + this.cookies = null; + this.headsended = false; + this.header.clear(); + this.interceptor = null; + return super.recycle(); + } + + @Override + protected void init(AsyncConnection channel) { + super.init(channel); + } + + protected String getHttpCode(int status) { + return httpCodes.get(status); + } + + protected HttpRequest getRequest() { + return request; + } + + protected String getHttpCode(int status, String defValue) { + String v = httpCodes.get(status); + return v == null ? defValue : v; + } + + @Override + public HttpContext getContext() { + return (HttpContext) context; + } + + public void addCookie(HttpCookie... cookies) { + if (this.cookies == null) { + this.cookies = cookies; + } else { + HttpCookie[] news = new HttpCookie[this.cookies.length + cookies.length]; + System.arraycopy(this.cookies, 0, news, 0, this.cookies.length); + System.arraycopy(cookies, 0, news, this.cookies.length, cookies.length); + this.cookies = news; + } + } + + public void finishJson(Object obj) { + this.contentType = "text/plain; charset=utf-8"; + finish(request.getJsonConvert().convertTo(context.getCharset(), context.getBufferSupplier(), obj)); + } + + public void finishJson(Type type, Object obj) { + this.contentType = "text/plain; charset=utf-8"; + finish(request.getJsonConvert().convertTo(context.getCharset(), context.getBufferSupplier(), type, obj)); + } + + public void finishJson(Object... objs) { + this.contentType = "text/plain; charset=utf-8"; + finish(request.getJsonConvert().convertTo(context.getCharset(), context.getBufferSupplier(), objs)); + } + + public void finish(String obj) { + if (obj == null || obj.isEmpty()) { + final ByteBuffer headbuf = createHeader(); + headbuf.flip(); + super.finish(headbuf); + return; + } + if (context.getCharset() == null) { + if (interceptor != null) { + interceptor.invoke(this, new ByteBuffer[]{ByteBuffer.wrap(Utility.encodeUTF8(obj))}); + } + final char[] chars = Utility.charArray(obj); + this.contentLength = Utility.encodeUTF8Length(chars); + final ByteBuffer headbuf = createHeader(); + ByteBuffer buf2 = Utility.encodeUTF8(headbuf, (int) this.contentLength, chars); + headbuf.flip(); + if (buf2 == null) { + super.finish(headbuf); + } else { + super.finish(headbuf, buf2); + } + } else { + ByteBuffer buffer = context.getCharset().encode(obj); + if (interceptor != null) { + ByteBuffer[] bufs = interceptor.invoke(this, new ByteBuffer[]{buffer}); + if (bufs != null) buffer = bufs[0]; + } + this.contentLength = buffer.remaining(); + final ByteBuffer headbuf = createHeader(); + headbuf.flip(); + super.finish(headbuf, buffer); + } + } + + public void finish(int status, String message) { + this.status = status; + if (status != 200) super.refuseAlive(); + finish(message); + } + + public void finish304() { + super.finish(buffer304.duplicate()); + } + + public void finish404() { + super.finish(buffer404.duplicate()); + } + + @Override + public void finish(ByteBuffer buffer) { + finish(false, buffer); + } + + @Override + public void finish(boolean kill, ByteBuffer buffer) { + if (!this.headsended) { + this.contentLength = buffer == null ? 0 : buffer.remaining(); + ByteBuffer headbuf = createHeader(); + headbuf.flip(); + if (buffer == null) { + super.finish(kill, headbuf); + } else { + super.finish(kill, new ByteBuffer[]{headbuf, buffer}); + } + } else { + super.finish(kill, buffer); + } + } + + @Override + public void finish(ByteBuffer... buffers) { + finish(false, buffers); + } + + @Override + public void finish(boolean kill, ByteBuffer... buffers) { + if (interceptor != null) { + ByteBuffer[] bufs = interceptor.invoke(this, buffers); + if (bufs != null) buffers = bufs; + } + if (kill) refuseAlive(); + if (!this.headsended) { + long len = 0; + for (ByteBuffer buf : buffers) { + len += buf.remaining(); + } + this.contentLength = len; + ByteBuffer headbuf = createHeader(); + headbuf.flip(); + if (buffers == null) { + super.finish(kill, headbuf); + } else { + ByteBuffer[] newbuffers = new ByteBuffer[buffers.length + 1]; + newbuffers[0] = headbuf; + System.arraycopy(buffers, 0, newbuffers, 1, buffers.length); + super.finish(kill, newbuffers); + } + } else { + super.finish(kill, buffers); + } + } + + public void sendBody(ByteBuffer buffer, A attachment, CompletionHandler handler) { + if (!this.headsended) { + if (this.contentLength < 0) this.contentLength = buffer == null ? 0 : buffer.remaining(); + ByteBuffer headbuf = createHeader(); + headbuf.flip(); + if (buffer == null) { + super.send(headbuf, attachment, handler); + } else { + super.send(new ByteBuffer[]{headbuf, buffer}, attachment, handler); + } + } else { + super.send(buffer, attachment, handler); + } + } + + public void finish(File file) throws IOException { + finishFile(file, null); + } + + protected void finishFile(final File file, ByteBuffer fileBody) throws IOException { + if (file == null || !file.isFile() || !file.canRead()) { + finish404(); + return; + } + if (fileBody != null) fileBody = fileBody.duplicate().asReadOnlyBuffer(); + final long length = file.length(); + final String match = request.getHeader("If-None-Match"); + if (match != null && (file.lastModified() + "-" + length).equals(match)) { + finish304(); + return; + } + this.contentLength = fileBody == null ? file.length() : fileBody.remaining(); + this.contentType = MimeType.getByFilename(file.getName()); + if (this.contentType == null) this.contentType = "application/octet-stream"; + String range = request.getHeader("Range"); + if (range != null && (!range.startsWith("bytes=") || range.indexOf(',') >= 0)) range = null; + long start = -1; + long len = -1; + if (range != null) { + range = range.substring("bytes=".length()); + int pos = range.indexOf('-'); + start = pos == 0 ? 0 : Integer.parseInt(range.substring(0, pos)); + long end = (pos == range.length() - 1) ? -1 : Long.parseLong(range.substring(pos + 1)); + long clen = end > 0 ? (end - start + 1) : (file.length() - start); + this.status = 206; + addHeader("Accept-Ranges", "bytes"); + addHeader("Content-Range", "bytes " + start + "-" + (end > 0 ? end : length - 1) + "/" + length); + this.contentLength = clen; + len = end > 0 ? clen : end; + } + this.addHeader("ETag", file.lastModified() + "-" + length); + ByteBuffer hbuffer = createHeader(); + hbuffer.flip(); + if (fileBody == null) { + finishFile(hbuffer, file, start, len); + } else { + if (start >= 0) { + fileBody.position((int) start); + if (len > 0) fileBody.limit((int) (fileBody.position() + len)); + } + super.finish(hbuffer, fileBody); + } + } + + private void finishFile(ByteBuffer hbuffer, File file, long offset, long length) throws IOException { + this.channel.write(hbuffer, hbuffer, new TransferFileHandler(AsynchronousFileChannel.open(file.toPath(), options, ((HttpContext) context).getExecutor()), offset, length)); + } + + private ByteBuffer createHeader() { + this.headsended = true; + ByteBuffer buffer = this.context.pollBuffer(); + buffer.put(("HTTP/1.1 " + this.status + " " + (this.status == 200 ? "OK" : httpCodes.get(this.status)) + "\r\n").getBytes()); + + buffer.put(("Content-Type: " + (this.contentType == null ? "text/plain; charset=utf-8" : this.contentType) + "\r\n").getBytes()); + + if (this.contentLength > 0) { + buffer.put(("Content-Length: " + this.contentLength + "\r\n").getBytes()); + } + if (!this.request.isKeepAlive()) { + buffer.put("Connection: close\r\n".getBytes()); + } + if (this.defaultAddHeaders != null) { + for (String[] headers : this.defaultAddHeaders) { + if (headers.length > 2) { + String v = request.getHeader(headers[2]); + if (v != null) this.header.addValue(headers[0], v); + } else { + this.header.addValue(headers[0], headers[1]); + } + } + } + if (this.defaultSetHeaders != null) { + for (String[] headers : this.defaultSetHeaders) { + if (headers.length > 2) { + this.header.setValue(headers[0], request.getHeader(headers[2])); + } else { + this.header.setValue(headers[0], headers[1]); + } + } + } + for (Entry en : this.header.getStringEntrys()) { + buffer.put((en.name + ": " + en.getValue() + "\r\n").getBytes()); + } + if (request.newsessionid != null) { + String domain = defcookie == null ? null : defcookie.getDomain(); + if (domain == null) { + domain = ""; + } else { + domain = "Domain=" + domain + "; "; + } + String path = defcookie == null ? null : defcookie.getPath(); + if (path == null) path = "/"; + if (request.newsessionid.isEmpty()) { + buffer.put(("Set-Cookie: " + HttpRequest.SESSIONID_NAME + "=; " + domain + "Path=" + path + "; Max-Age=0; HttpOnly\r\n").getBytes()); + } else { + buffer.put(("Set-Cookie: " + HttpRequest.SESSIONID_NAME + "=" + request.newsessionid + "; " + domain + "Path=" + path + "; HttpOnly\r\n").getBytes()); + } + } + if (this.cookies != null) { + for (HttpCookie cookie : this.cookies) { + if (cookie == null) continue; + if (defcookie != null) { + if (defcookie.getDomain() != null && cookie.getDomain() == null) cookie.setDomain(defcookie.getDomain()); + if (defcookie.getPath() != null && cookie.getPath() == null) cookie.setPath(defcookie.getPath()); + } + buffer.put(("Set-Cookie: " + genString(cookie) + "\r\n").getBytes()); + } + } + buffer.put(LINE); + return buffer; + } + + private CharSequence genString(HttpCookie cookie) { + StringBuilder sb = new StringBuilder(); + sb.append(cookie.getName()).append("=\"").append(cookie.getValue()).append('"').append("; Version=1"); + if (cookie.getDomain() != null) sb.append("; Domain=").append(cookie.getDomain()); + if (cookie.getPath() != null) sb.append("; Path=").append(cookie.getPath()); + if (cookie.getPortlist() != null) sb.append("; Port=").append(cookie.getPortlist()); + if (cookie.getMaxAge() > 0) { + sb.append("; Max-Age=").append(cookie.getMaxAge()); + synchronized (GMT_DATE_FORMAT) { + sb.append("; Expires=").append(GMT_DATE_FORMAT.format(new Date(System.currentTimeMillis() + cookie.getMaxAge() * 1000))); + } + } + if (cookie.getSecure()) sb.append("; Secure"); + if (cookie.isHttpOnly()) sb.append("; HttpOnly"); + return sb; + } + + public void skipHeader() { + this.headsended = true; + } + + protected DefaultAnyValue duplicateHeader() { + return this.header.duplicate(); + } + + public void setHeader(String name, Object value) { + this.header.setValue(name, String.valueOf(value)); + } + + public void addHeader(String name, Object value) { + this.header.addValue(name, String.valueOf(value)); + } + + public void setStatus(int status) { + this.status = status; + } + + public int getStatus() { + return this.status; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public long getContentLength() { + return contentLength; + } + + public void setContentLength(long contentLength) { + this.contentLength = contentLength; + } + + public Interceptor getInterceptor() { + return interceptor; + } + + public void setInterceptor(Interceptor interceptor) { + this.interceptor = interceptor; + } + + protected final class TransferFileHandler implements CompletionHandler { + + private final AsynchronousFileChannel filechannel; + + private final long max; //需要读取的字节数, -1表示读到文件结尾 + + private long count;//读取文件的字节数 + + private long position = 0; + + private boolean next = false; + + private boolean read = true; + + public TransferFileHandler(AsynchronousFileChannel channel) { + this.filechannel = channel; + this.max = -1; + } + + public TransferFileHandler(AsynchronousFileChannel channel, long offset, long len) { + this.filechannel = channel; + this.position = offset <= 0 ? 0 : offset; + this.max = len; + } + + @Override + public void completed(Integer result, ByteBuffer attachment) { + if (result < 0 || (max > 0 && count >= max)) { + failed(null, attachment); + return; + } + if (read) { + read = false; + if (next) { + position += result; + } else { + next = true; + } + attachment.clear(); + filechannel.read(attachment, position, attachment, this); + } else { + read = true; + if (max > 0) { + count += result; + if (count > max) { + attachment.limit((int) (attachment.position() + max - count)); + } + } + attachment.flip(); + channel.write(attachment, attachment, this); + } + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + getContext().offerBuffer(attachment); + finish(true); + try { + filechannel.close(); + } catch (IOException e) { + } + } + + } +} diff --git a/src/org/redkale/net/http/HttpServer.java b/src/org/redkale/net/http/HttpServer.java new file mode 100644 index 000000000..0ff1bd9d4 --- /dev/null +++ b/src/org/redkale/net/http/HttpServer.java @@ -0,0 +1,127 @@ +/* + * 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.net.http; + +import java.net.*; +import java.nio.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import org.redkale.net.*; +import org.redkale.util.*; +import org.redkale.watch.*; + +/** + * + * @author zhangjx + */ +public final class HttpServer extends Server { + + private String contextPath; + + public HttpServer() { + this(System.currentTimeMillis(), null); + } + + public HttpServer(long serverStartTime, final WatchFactory watch) { + super(serverStartTime, "TCP", new HttpPrepareServlet(), watch); + } + + @Override + public void init(AnyValue config) throws Exception { + super.init(config); + AnyValue conf = config == null ? null : config.getAnyValue("servlets"); + this.contextPath = conf == null ? "" : conf.getValue("prefix", ""); + } + + public void addHttpServlet(HttpServlet servlet, AnyValue conf, String... mappings) { + ((HttpPrepareServlet) this.prepare).addHttpServlet(servlet, conf, mappings); + } + + @Override + @SuppressWarnings("unchecked") + protected Context createContext() { + final int port = this.address.getPort(); + AtomicLong createBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("HTTP_" + port + ".Buffer.creatCounter"); + AtomicLong cycleBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("HTTP_" + port + ".Buffer.cycleCounter"); + final int rcapacity = Math.max(this.capacity, 16 * 1024 + 8); //兼容 HTTP 2.0 + ObjectPool bufferPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, this.bufferPoolSize, + (Object... params) -> ByteBuffer.allocateDirect(rcapacity), null, (e) -> { + if (e == null || e.isReadOnly() || e.capacity() != rcapacity) return false; + e.clear(); + return true; + }); + final List defaultAddHeaders = new ArrayList<>(); + final List defaultSetHeaders = new ArrayList<>(); + HttpCookie defaultCookie = null; + String remoteAddrHeader = null; + if (config != null) { + AnyValue reqs = config == null ? null : config.getAnyValue("request"); + if (reqs != null) { + AnyValue raddr = reqs.getAnyValue("remoteaddr"); + remoteAddrHeader = raddr == null ? null : raddr.getValue("value"); + if (remoteAddrHeader != null) { + if (remoteAddrHeader.startsWith("request.headers.")) { + remoteAddrHeader = remoteAddrHeader.substring("request.headers.".length()); + } else { + remoteAddrHeader = null; + } + } + } + + AnyValue resps = config == null ? null : config.getAnyValue("response"); + if (resps != null) { + AnyValue[] addHeaders = resps.getAnyValues("addheader"); + if (addHeaders.length > 0) { + for (int i = 0; i < addHeaders.length; i++) { + String val = addHeaders[i].getValue("value"); + if (val == null) continue; + if (val.startsWith("request.headers.")) { + defaultAddHeaders.add(new String[]{addHeaders[i].getValue("name"), val, val.substring("request.headers.".length())}); + } else if (val.startsWith("system.property.")) { + String v = System.getProperty(val.substring("system.property.".length())); + if (v != null) defaultAddHeaders.add(new String[]{addHeaders[i].getValue("name"), v}); + } else { + defaultAddHeaders.add(new String[]{addHeaders[i].getValue("name"), val}); + } + } + } + AnyValue[] setHeaders = resps.getAnyValues("setheader"); + if (setHeaders.length > 0) { + for (int i = 0; i < setHeaders.length; i++) { + String val = setHeaders[i].getValue("value"); + if (val != null && val.startsWith("request.headers.")) { + defaultSetHeaders.add(new String[]{setHeaders[i].getValue("name"), val, val.substring("request.headers.".length())}); + } else { + defaultSetHeaders.add(new String[]{setHeaders[i].getValue("name"), val}); + } + } + } + AnyValue defcookieValue = resps.getAnyValue("defcookie"); + if (defcookieValue != null) { + String domain = defcookieValue.getValue("domain"); + String path = defcookieValue.getValue("path"); + if (domain != null || path != null) { + defaultCookie = new HttpCookie("DEFAULTCOOKIE", ""); + defaultCookie.setDomain(domain); + defaultCookie.setPath(path); + } + } + } + } + final String[][] addHeaders = defaultAddHeaders.isEmpty() ? null : defaultAddHeaders.toArray(new String[defaultAddHeaders.size()][]); + final String[][] setHeaders = defaultSetHeaders.isEmpty() ? null : defaultSetHeaders.toArray(new String[defaultSetHeaders.size()][]); + final HttpCookie defCookie = defaultCookie; + final String addrHeader = remoteAddrHeader; + AtomicLong createResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("HTTP_" + port + ".Response.creatCounter"); + AtomicLong cycleResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("HTTP_" + port + ".Response.cycleCounter"); + ObjectPool responsePool = HttpResponse.createPool(createResponseCounter, cycleResponseCounter, this.responsePoolSize, null); + HttpContext httpcontext = new HttpContext(this.serverStartTime, this.logger, executor, rcapacity, bufferPool, responsePool, + this.maxbody, this.charset, this.address, this.prepare, this.watch, this.readTimeoutSecond, this.writeTimeoutSecond, contextPath); + responsePool.setCreator((Object... params) -> new HttpResponse(httpcontext, new HttpRequest(httpcontext, addrHeader), addHeaders, setHeaders, defCookie)); + return httpcontext; + } + +} diff --git a/src/org/redkale/net/http/HttpServlet.java b/src/org/redkale/net/http/HttpServlet.java new file mode 100644 index 000000000..99678729d --- /dev/null +++ b/src/org/redkale/net/http/HttpServlet.java @@ -0,0 +1,29 @@ +/* + * 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.net.http; + +import org.redkale.net.Servlet; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public abstract class HttpServlet implements Servlet> { + + AnyValue conf; //当前HttpServlet的配置 + + @Override + public final boolean equals(Object obj) { + return obj != null && obj.getClass() == this.getClass(); + } + + @Override + public final int hashCode() { + return this.getClass().hashCode(); + } + +} diff --git a/src/org/redkale/net/http/MimeType.java b/src/org/redkale/net/http/MimeType.java new file mode 100644 index 000000000..b54533a57 --- /dev/null +++ b/src/org/redkale/net/http/MimeType.java @@ -0,0 +1,240 @@ +/* + * 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.net.http; + +import java.util.*; + +/** + * + * @author zhangjx + */ +public class MimeType { + + private final static Map contentTypes = new HashMap<>(); + + static { + contentTypes.put("abs", "audio/x-mpeg"); + contentTypes.put("ai", "application/postscript"); + contentTypes.put("aif", "audio/x-aiff"); + contentTypes.put("aifc", "audio/x-aiff"); + contentTypes.put("aiff", "audio/x-aiff"); + contentTypes.put("aim", "application/x-aim"); + contentTypes.put("art", "image/x-jg"); + contentTypes.put("asf", "video/x-ms-asf"); + contentTypes.put("asx", "video/x-ms-asf"); + contentTypes.put("au", "audio/basic"); + contentTypes.put("avi", "video/x-msvideo"); + contentTypes.put("avx", "video/x-rad-screenplay"); + contentTypes.put("bcpio", "application/x-bcpio"); + contentTypes.put("bin", "application/octet-stream"); + contentTypes.put("bmp", "image/bmp"); + contentTypes.put("body", "text/html"); + contentTypes.put("cdf", "application/x-cdf"); + contentTypes.put("cer", "application/x-x509-ca-cert"); + contentTypes.put("class", "application/java"); + contentTypes.put("cpio", "application/x-cpio"); + contentTypes.put("csh", "application/x-csh"); + contentTypes.put("css", "text/css"); + contentTypes.put("dib", "image/bmp"); + contentTypes.put("doc", "application/msword"); + contentTypes.put("dtd", "application/xml-dtd"); + contentTypes.put("dv", "video/x-dv"); + contentTypes.put("dvi", "application/x-dvi"); + contentTypes.put("eps", "application/postscript"); + contentTypes.put("etx", "text/x-setext"); + contentTypes.put("exe", "application/octet-stream"); + contentTypes.put("gif", "image/gif"); + contentTypes.put("gk", "application/octet-stream"); + contentTypes.put("gtar", "application/x-gtar"); + contentTypes.put("gz", "application/x-gzip"); + contentTypes.put("hdf", "application/x-hdf"); + contentTypes.put("hqx", "application/mac-binhex40"); + contentTypes.put("htc", "text/x-component"); + contentTypes.put("htm", "text/html"); + contentTypes.put("html", "text/html"); + contentTypes.put("hqx", "application/mac-binhex40"); + contentTypes.put("ief", "image/ief"); + contentTypes.put("jad", "text/vnd.sun.j2me.app-descriptor"); + contentTypes.put("jar", "application/java-archive"); + contentTypes.put("java", "text/plain"); + contentTypes.put("jnlp", "application/x-java-jnlp-file"); + contentTypes.put("jpe", "image/jpeg"); + contentTypes.put("jpeg", "image/jpeg"); + contentTypes.put("jpg", "image/jpeg"); + contentTypes.put("js", "text/javascript"); + contentTypes.put("kar", "audio/x-midi"); + contentTypes.put("latex", "application/x-latex"); + contentTypes.put("log", "text/plain"); + contentTypes.put("m3u", "audio/x-mpegurl"); + contentTypes.put("mac", "image/x-macpaint"); + contentTypes.put("man", "application/x-troff-man"); + contentTypes.put("mathml", "application/mathml+xml"); + contentTypes.put("me", "application/x-troff-me"); + contentTypes.put("mid", "audio/x-midi"); + contentTypes.put("midi", "audio/x-midi"); + contentTypes.put("mif", "application/x-mif"); + contentTypes.put("mov", "video/quicktime"); + contentTypes.put("movie", "video/x-sgi-movie"); + contentTypes.put("mp1", "audio/x-mpeg"); + contentTypes.put("mp2", "audio/x-mpeg"); + contentTypes.put("mp3", "audio/x-mpeg"); + contentTypes.put("mpa", "audio/x-mpeg"); + contentTypes.put("mp4", "video/mp4"); + contentTypes.put("ogv", "video/ogv"); + contentTypes.put("webm", "video/webm"); + contentTypes.put("flv", "video/x-flv"); + contentTypes.put("mpe", "video/mpeg"); + contentTypes.put("mpeg", "video/mpeg"); + contentTypes.put("mpega", "audio/x-mpeg"); + contentTypes.put("mpg", "video/mpeg"); + contentTypes.put("mpv2", "video/mpeg2"); + contentTypes.put("ms", "application/x-wais-source"); + contentTypes.put("nc", "application/x-netcdf"); + contentTypes.put("oda", "application/oda"); + contentTypes.put("ogg", "application/ogg"); + contentTypes.put("out", "text/plain"); + contentTypes.put("pbm", "image/x-portable-bitmap"); + contentTypes.put("pct", "image/pict"); + contentTypes.put("pdf", "application/pdf"); + contentTypes.put("pgm", "image/x-portable-graymap"); + contentTypes.put("pic", "image/pict"); + contentTypes.put("pict", "image/pict"); + contentTypes.put("pls", "audio/x-scpls"); + contentTypes.put("png", "image/png"); + contentTypes.put("pnm", "image/x-portable-anymap"); + contentTypes.put("pnt", "image/x-macpaint"); + contentTypes.put("ppm", "image/x-portable-pixmap"); + contentTypes.put("ppt", "application/powerpoint"); + contentTypes.put("ps", "application/postscript"); + contentTypes.put("psd", "image/x-photoshop"); + contentTypes.put("qt", "video/quicktime"); + contentTypes.put("qti", "image/x-quicktime"); + contentTypes.put("qtif", "image/x-quicktime"); + contentTypes.put("ras", "image/x-cmu-raster"); + contentTypes.put("rdf", "application/rdf+xml"); + contentTypes.put("rgb", "image/x-rgb"); + contentTypes.put("rm", "application/vnd.rn-realmedia"); + contentTypes.put("roff", "application/x-troff"); + contentTypes.put("rtf", "application/rtf"); + contentTypes.put("rtx", "text/richtext"); + contentTypes.put("sh", "application/x-sh"); + contentTypes.put("shar", "application/x-shar"); + contentTypes.put("shtml", "text/x-server-parsed-html"); + contentTypes.put("sit", "application/x-stuffit"); + contentTypes.put("smf", "audio/x-midi"); + contentTypes.put("snd", "audio/basic"); + contentTypes.put("src", "application/x-wais-source"); + contentTypes.put("sv4cpio", "application/x-sv4cpio"); + contentTypes.put("sv4crc", "application/x-sv4crc"); + contentTypes.put("svg", "image/svg+xml"); + contentTypes.put("svgz", "image/svg+xml"); + contentTypes.put("swf", "application/x-shockwave-flash"); + contentTypes.put("t", "application/x-troff"); + contentTypes.put("tar", "application/x-tar"); + contentTypes.put("tcl", "application/x-tcl"); + contentTypes.put("tex", "application/x-tex"); + contentTypes.put("texi", "application/x-texinfo"); + contentTypes.put("texinfo", "application/x-texinfo"); + contentTypes.put("tif", "image/tiff"); + contentTypes.put("tiff", "image/tiff"); + contentTypes.put("tr", "application/x-troff"); + contentTypes.put("tsv", "text/tab-separated-values"); + contentTypes.put("txt", "text/plain"); + contentTypes.put("ulw", "audio/basic"); + contentTypes.put("ustar", "application/x-ustar"); + contentTypes.put("xbm", "image/x-xbitmap"); + contentTypes.put("xml", "application/xml"); + contentTypes.put("xpm", "image/x-xpixmap"); + contentTypes.put("xsl", "application/xml"); + contentTypes.put("xslt", "application/xslt+xml"); + contentTypes.put("xwd", "image/x-xwindowdump"); + contentTypes.put("vsd", "application/x-visio"); + contentTypes.put("vxml", "application/voicexml+xml"); + contentTypes.put("wav", "audio/x-wav"); + contentTypes.put("wbmp", "image/vnd.wap.wbmp"); + contentTypes.put("wml", "text/vnd.wap.wml"); + contentTypes.put("wmlc", "application/vnd.wap.wmlc"); + contentTypes.put("wmls", "text/vnd.wap.wmls"); + contentTypes.put("wmlscriptc", "application/vnd.wap.wmlscriptc"); + contentTypes.put("wrl", "x-world/x-vrml"); + contentTypes.put("xht", "application/xhtml+xml"); + contentTypes.put("xhtml", "application/xhtml+xml"); + contentTypes.put("xls", "application/vnd.ms-excel"); + contentTypes.put("xul", "application/vnd.mozilla.xul+xml"); + contentTypes.put("Z", "application/x-compress"); + contentTypes.put("z", "application/x-compress"); + contentTypes.put("zip", "application/zip"); + } + + /** + * @param extension the extension + * + * @return the content type associated with extension. If no association is found, this method will return text/plain + */ + public static String get(String extension) { + return get(extension, "text/plain"); + } + + /** + * @param extension the extension + * @param defaultCt the content type to return if there is no known association for the specified extension + * + * @return the content type associated with extension or if no associate is found, returns defaultCt + */ + public static String get(String extension, String defaultCt) { + final String mime = contentTypes.get(extension); + return mime == null ? defaultCt : mime; + } + + /** + * @param extension the extension + * + * @return true if the specified extension has been registered otherwise, returns false + */ + public static boolean contains(String extension) { + return contentTypes.containsKey(extension); + } + + /** + *

+ * Associates the specified extension and content type

+ * + * @param extension the extension + * @param contentType the content type associated with the extension + */ + public static void add(String extension, String contentType) { + if (extension != null && extension.length() != 0 + && contentType != null && contentType.length() != 0) { + contentTypes.put(extension, contentType); + } + } + + /** + * @param fileName the filename + * + * @return the content type associated with extension of the given filename or if no associate is found, returns null + */ + public static String getByFilename(String fileName) { + String extn = getExtension(fileName); + return extn == null ? null : get(extn); + } + + /** + * Get extension of file, without fragment id + */ + private static String getExtension(String fileName) { + // play it safe and get rid of any fragment id + // that might be there + int length = fileName.length(); + + int newEnd = fileName.lastIndexOf('#'); + if (newEnd == -1) newEnd = length; + // Instead of creating a new string. + // if (i != -1) fileName = fileName.substring(0, i); + int i = fileName.lastIndexOf('.', newEnd); + return i == -1 ? null : fileName.substring(i + 1, newEnd); + } +} diff --git a/src/org/redkale/net/http/MultiContext.java b/src/org/redkale/net/http/MultiContext.java new file mode 100644 index 000000000..98a9c1fa0 --- /dev/null +++ b/src/org/redkale/net/http/MultiContext.java @@ -0,0 +1,284 @@ +/* + * To change this license header, choose License Headers input Project Properties. + * To change this template file, choose Tools | Templates + * and open the template input the editor. + */ +package org.redkale.net.http; + +import org.redkale.util.ByteArray; +import java.io.*; +import java.nio.charset.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import java.util.logging.*; +import java.util.regex.*; + +/** + * + * @author zhangjx + */ +public final class MultiContext { + + private static final Logger logger = Logger.getLogger(MultiContext.class.getSimpleName()); + + private static final Charset UTF8 = Charset.forName("UTF-8"); + + private final String contentType; + + private final InputStream in; + + private final Charset charset; + + private final String boundary; + + private final byte[] endboundarray; + + private final ByteArray buf = new ByteArray(64); + + private final Map parameters = new HashMap<>(); + + private final Pattern fielnamePattern; + + private static final Iterable emptyIterable = () -> new Iterator() { + + @Override + public boolean hasNext() { + return false; + } + + @Override + public MultiPart next() { + return null; + } + }; + + public MultiContext(final String contentType, final InputStream in) { + this(null, contentType, in); + } + + public MultiContext(final Charset charsetName, final String contentType, final InputStream in) { + this(charsetName, contentType, in, null); + } + + public MultiContext(final String contentType, final InputStream in, String extregex) { + this(null, contentType, in, extregex); + } + + public MultiContext(final Charset charsetName, final String contentType, final InputStream in, String fielnameRegex) { + this.charset = charsetName == null ? UTF8 : charsetName; + this.contentType = contentType.trim(); + this.boundary = parseBoundary(this.contentType); + this.endboundarray = ("--" + this.boundary + "--").getBytes(); + this.in = in instanceof BufferedInputStream ? in : new BufferedInputStream(in); + this.fielnamePattern = fielnameRegex == null || fielnameRegex.isEmpty() ? null : Pattern.compile(fielnameRegex); + } + + public Map getParameters() { + return parameters; + } + + public String getParameter(String name) { + return getParameters().get(name); + } + + public final String getParameter(String name, String defaultValue) { + String value = this.getParameter(name); + return value == null ? defaultValue : value; + } + + public final int getIntParameter(String name, int defaultValue) { + String value = this.getParameter(name); + try { + return value == null ? defaultValue : Integer.decode(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + public final long getLongParameter(String name, long defaultValue) { + String value = this.getParameter(name); + try { + return value == null ? defaultValue : Long.decode(value); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + private String parseBoundary(String contentType) { + if (!contentType.startsWith("multipart/")) { + return null; + } + for (String str : contentType.split(";")) { + int pos = str.indexOf("boundary="); + if (pos >= 0) return str.substring(pos + "boundary=".length()).trim(); + } + return null; + } + + public boolean isMultipart() { + return this.boundary != null; + } + + public Iterable listMultiPart() throws IOException { + if (!isMultipart()) return emptyIterable; + final boolean debug = true; + final String boundarystr = "--" + this.boundary; + final Pattern fielnameReg = this.fielnamePattern; + final String endboundary = boundarystr + "--"; + final byte[] boundarray = ("\n" + boundarystr).getBytes(); + final byte[] buffer = new byte[boundarray.length]; + final InputStream input = this.in; + final Map params = this.parameters; + final AtomicBoolean finaled = new AtomicBoolean(false); + return () -> new Iterator() { + + private String boundaryline; + + private MultiPart lastentry; + + @Override + public boolean hasNext() { + try { + if (lastentry != null) { + lastentry.skip(); + if (finaled.get()) return false; + } + if (boundaryline == null) boundaryline = readBoundary(); + //if (debug) System.out.print("boundaryline=" + boundaryline + " "); + if (endboundary.equals(boundaryline) || !boundarystr.equals(boundaryline)) { //结尾或异常 + lastentry = null; + return false; + } + final String disposition = readLine(); + //if (debug) System.out.println("disposition=" + disposition); + if (disposition.contains("; filename=\"")) { //是上传文件 + String contentType = readLine(); + //if (debug) System.out.println("file.contentType=" + contentType); + contentType = contentType.substring(contentType.indexOf(':') + 1).trim(); + readLine(); //读掉空白行 + String name = parseValue(disposition, "name"); + String filename = parseValue(disposition, "filename"); + if (filename == null || filename.isEmpty()) { //没有上传 + readLine(); //读掉空白行 + this.boundaryline = null; + this.lastentry = null; + return this.hasNext(); + } else { + int p1 = filename.lastIndexOf('/'); + if (p1 < 0) p1 = filename.lastIndexOf('\\'); + if (p1 >= 0) filename = filename.substring(p1 + 1); + } + final AtomicLong counter = new AtomicLong(0); + InputStream source = new InputStream() { + + private int bufposition = buffer.length; + + private boolean end; + + @Override + public int read() throws IOException { + if (end) return -1; + final byte[] buf = buffer; + int ch = (this.bufposition < buf.length) ? (buf[this.bufposition++] & 0xff) : input.read(); + if ((ch == '\r' && readBuffer())) return -1; + counter.incrementAndGet(); + return ch; + } + + private boolean readBuffer() throws IOException { + final byte[] buf = buffer; + final int pos = this.bufposition; + int s = 0; + for (int i = pos; i < buf.length; i++) { + buf[s++] = buf[i]; + } + int readed = 0; + while ((readed += input.read(buf, s + readed, pos - readed)) != pos); + this.bufposition = 0; + if (Arrays.equals(boundarray, buf)) { + this.end = true; + int c1 = input.read(); + int c2 = input.read(); + finaled.set(c1 == '-' && c2 == '-'); + return true; + } + return false; + } + + @Override + public long skip(long count) throws IOException { + if (end) return -1; + if (count <= 0) return 0; + long s = 0; + while (read() != -1) { + s++; + if (--count <= 0) break; + } + return s; + } + }; + this.lastentry = new MultiPart(filename, name, contentType, counter, source); + if (fielnameReg != null && !fielnameReg.matcher(filename).matches()) { + return this.hasNext(); + } + return true; + } else { //不是文件 + readLine(); //读掉空白 + params.put(parseValue(disposition, "name"), readLine()); + this.boundaryline = null; + this.lastentry = null; + return this.hasNext(); + } + } catch (IOException ex) { + logger.log(Level.FINER, "listMultiPart abort", ex); + return false; + } + } + + @Override + public MultiPart next() { + return lastentry; + } + + }; + } + + private String readLine() throws IOException { + return readLine(false); + } + + private String readBoundary() throws IOException { + return readLine(true); + } + + private String readLine(boolean bd) throws IOException { // bd : 是否是读取boundary + byte lasted = '\r'; + buf.clear(); + final int bc = this.endboundarray.length; + int c = 0; + for (;;) { + int b = in.read(); + c++; + if (b == -1 || (lasted == '\r' && b == '\n')) break; + if (lasted != '\r') buf.add(lasted); + lasted = (byte) b; + if (bd && bc == c) { + buf.add(lasted); + if (buf.equal(this.endboundarray)) break; + buf.removeLastByte(); + } + } + if (buf.count() == 0) return ""; + return buf.toString(this.charset).trim(); + } + + private static String parseValue(final String str, String name) { + if (str == null) return null; + final String key = "; " + name + "=\""; + int pos = str.indexOf(key); + if (pos < 0) return null; + String sub = str.substring(pos + key.length()); + return sub.substring(0, sub.indexOf('"')); + } + +} diff --git a/src/org/redkale/net/http/MultiPart.java b/src/org/redkale/net/http/MultiPart.java new file mode 100644 index 000000000..efae834bf --- /dev/null +++ b/src/org/redkale/net/http/MultiPart.java @@ -0,0 +1,115 @@ +/* + * 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.net.http; + +import java.io.*; +import java.util.concurrent.atomic.AtomicLong; + +/** + * + * @author zhangjx + */ +public final class MultiPart { + + private final String filename; + + private final String name; + + private final String contentType; + + private final InputStream in; + + private final AtomicLong received; + + MultiPart(String filename, String name, String contentType, AtomicLong received, InputStream in) { + this.filename = filename; + this.name = name; + this.in = in; + this.contentType = contentType; + this.received = received; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "{" + "name=" + name + ", filename=" + filename + ", contentType=" + contentType + ", received=" + received + '}'; + } + + public boolean save(File file) throws IOException { + return save(Long.MAX_VALUE, file); + } + + public boolean save(long max, File file) throws IOException { + OutputStream out = new FileOutputStream(file); + boolean rs = save(max, out); + out.close(); + return rs; + } + + public byte[] getContentBytes() throws IOException { + return getContentBytes(Long.MAX_VALUE); + } + + /** + * 将文件流读进bytes, 如果超出max指定的值则返回null + * + * @param max 最大长度限制 + * @return + * @throws IOException + */ + public byte[] getContentBytes(long max) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + return save(max, out) ? out.toByteArray() : null; + } + + public boolean save(OutputStream out) throws IOException { + return save(Long.MAX_VALUE, out); + } + + /** + * 将文件流写进out, 如果超出max指定的值则中断并返回false + * + * @param max 最大长度限制 + * @param out + * @return + * @throws IOException + */ + public boolean save(long max, OutputStream out) throws IOException { + byte[] bytes = new byte[4096]; + int pos; + InputStream in0 = this.getInputStream(); + while ((pos = in0.read(bytes)) != -1) { + if (max < 0) return false; + out.write(bytes, 0, pos); + max -= pos; + } + return true; + } + + public String getContentType() { + return contentType; + } + + public String getFilename() { + return filename; + } + + public String getName() { + return name; + } + + public InputStream getInputStream() { + return in; + } + + public long getReceived() { + return received.get(); + } + + public void skip() throws IOException { + in.skip(Long.MAX_VALUE); + } + +} diff --git a/src/org/redkale/net/http/WebAction.java b/src/org/redkale/net/http/WebAction.java new file mode 100644 index 000000000..3a7fc68f4 --- /dev/null +++ b/src/org/redkale/net/http/WebAction.java @@ -0,0 +1,22 @@ +/* + * 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.net.http; + +import java.lang.annotation.*; + +/** + * + * @author zhangjx + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface WebAction { + + int actionid() default 0; + + String url(); +} diff --git a/src/org/redkale/net/http/WebInitParam.java b/src/org/redkale/net/http/WebInitParam.java new file mode 100644 index 000000000..ae8d356b5 --- /dev/null +++ b/src/org/redkale/net/http/WebInitParam.java @@ -0,0 +1,24 @@ +/* + * 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.net.http; + +import java.lang.annotation.*; + +/** + * + * @author zhangjx + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface WebInitParam { + + String name(); + + String value(); + + String description() default ""; +} diff --git a/src/org/redkale/net/http/WebServlet.java b/src/org/redkale/net/http/WebServlet.java new file mode 100644 index 000000000..5a6d6402c --- /dev/null +++ b/src/org/redkale/net/http/WebServlet.java @@ -0,0 +1,28 @@ +/* + * 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.net.http; + +import java.lang.annotation.*; + +/** + * + * @author zhangjx + */ +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface WebServlet { + + String name() default ""; + + boolean fillurl() default true; + + String[] value() default {}; + + int moduleid() default 0; + + WebInitParam[] initParams() default {}; +} diff --git a/src/org/redkale/net/http/WebSocket.java b/src/org/redkale/net/http/WebSocket.java new file mode 100644 index 000000000..277526b25 --- /dev/null +++ b/src/org/redkale/net/http/WebSocket.java @@ -0,0 +1,387 @@ +/* + * 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.net.http; + +import org.redkale.net.http.WebSocketPacket.FrameType; +import java.io.*; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import org.redkale.net.*; + +/** + * 一个WebSocket连接对应一个WebSocket实体,即一个WebSocket会绑定一个TCP连接。 + * WebSocket 有两种模式: + * 1) 普通模式: 协议上符合HTML5规范, 其流程顺序如下: + * 1.1 onOpen 如果方法返回null,则视为WebSocket的连接不合法,框架会强制关闭WebSocket连接;通常用于判断登录态。 + * 1.2 createGroupid 如果方法返回null,则视为WebSocket的连接不合法,框架会强制关闭WebSocket连接;通常用于判断用户权限是否符合。 + * 1.3 onConnected WebSocket成功连接后在准备接收数据前回调此方法。 + * 1.4 onMessage/onFragment+ WebSocket接收到消息后回调此消息类方法。 + * 1.5 onClose WebSocket被关闭后回调此方法。 + * + * 此模式下 以上方法都应该被重载。 + * + * 2) 原始二进制模式: 此模式有别于HTML5规范,可以视为原始的TCP连接。通常用于音频视频通讯场景。期流程顺序如下: + * 2.1 onOpen 如果方法返回null,则视为WebSocket的连接不合法,框架会强制关闭WebSocket连接;通常用于判断登录态。 + * 2.2 createGroupid 如果方法返回null,则视为WebSocket的连接不合法,框架会强制关闭WebSocket连接;通常用于判断用户权限是否符合。 + * 2.3 onRead WebSocket成功连接后回调此方法, 由此方法处理原始的TCP连接, 同时业务代码去控制WebSocket的关闭。 + * + * 此模式下 以上方法都应该被重载。 + *

+ * + * @author zhangjx + */ +public abstract class WebSocket { + + //消息不合法 + public static final int RETCODE_SEND_ILLPACKET = 1 << 1; //2 + + //ws已经关闭 + public static final int RETCODE_WSOCKET_CLOSED = 1 << 2; //4 + + //socket的buffer不合法 + public static final int RETCODE_ILLEGALBUFFER = 1 << 3; //8 + + //ws发送消息异常 + public static final int RETCODE_SENDEXCEPTION = 1 << 4; //16 + + public static final int RETCODE_ENGINE_NULL = 1 << 5; //32 + + public static final int RETCODE_NODESERVICE_NULL = 1 << 6; //64 + + public static final int RETCODE_GROUP_EMPTY = 1 << 7; //128 + + public static final int RETCODE_WSOFFLINE = 1 << 8; //256 + + WebSocketRunner runner; + + WebSocketEngine engine; + + WebSocketGroup group; + + WebSocketNode node; + + Serializable sessionid; + + Serializable groupid; + + private final long createtime = System.currentTimeMillis(); + + private final Map attributes = new ConcurrentHashMap<>(); + + protected WebSocket() { + } + + //---------------------------------------------------------------- + /** + * 发送消息体, 包含二进制/文本 + *

+ * @param packet + * @return + */ + public final int send(WebSocketPacket packet) { + if (this.runner != null) return this.runner.sendMessage(packet); + return RETCODE_WSOCKET_CLOSED; + } + + /** + * 显式地关闭WebSocket + */ + public final void close() { + if (this.runner != null) this.runner.closeRunner(); + } + + /** + * 发送单一的文本消息 + *

+ * @param text 不可为空 + * @return + */ + public final int send(String text) { + return send(text, true); + } + + /** + * 发送文本消息 + *

+ * @param text 不可为空 + * @param last 是否最后一条 + * @return + */ + public final int send(String text, boolean last) { + return send(new WebSocketPacket(text, last)); + } + + /** + * 发送单一的二进制消息 + *

+ * @param data + * @return + */ + public final int send(byte[] data) { + return send(data, true); + } + + public final int sendPing(byte[] data) { + return send(new WebSocketPacket(FrameType.PING, data)); + } + + public final int sendPong(byte[] data) { + return send(new WebSocketPacket(FrameType.PONG, data)); + } + + public final long getCreatetime() { + return createtime; + } + + /** + * 发送二进制消息 + *

+ * @param data 不可为空 + * @param last 是否最后一条 + * @return + */ + public final int send(byte[] data, boolean last) { + return send(new WebSocketPacket(data, last)); + } + + /** + * 发送消息, 消息类型是String或byte[] + *

+ * @param message 不可为空, 只能是String或者byte[] + * @param last 是否最后一条 + * @return + */ + public final int send(Serializable message, boolean last) { + return send(new WebSocketPacket(message, last)); + } + + //---------------------------------------------------------------- + /** + * 给指定groupid的WebSocketGroup下所有WebSocket节点发送文本消息 + *

+ * @param groupid + * @param text 不可为空 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendEachMessage(Serializable groupid, String text) { + return sendEachMessage(groupid, text, true); + } + + /** + * 给指定groupid的WebSocketGroup下所有WebSocket节点发送二进制消息 + *

+ * @param groupid + * @param data 不可为空 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendEachMessage(Serializable groupid, byte[] data) { + return WebSocket.this.sendEachMessage(groupid, data, true); + } + + /** + * 给指定groupid的WebSocketGroup下所有WebSocket节点发送文本消息 + *

+ * @param groupid + * @param text 不可为空 + * @param last + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendEachMessage(Serializable groupid, String text, boolean last) { + return sendMessage(groupid, false, text, last); + } + + /** + * 给指定groupid的WebSocketGroup下所有WebSocket节点发送二进制消息 + *

+ * @param groupid + * @param data 不可为空 + * @param last 是否最后一条 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendEachMessage(Serializable groupid, byte[] data, boolean last) { + return sendMessage(groupid, false, data, last); + } + + /** + * 给指定groupid的WebSocketGroup下最近活跃的WebSocket节点发送文本消息 + *

+ * @param groupid + * @param text 不可为空 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendRecentMessage(Serializable groupid, String text) { + return sendRecentMessage(groupid, text, true); + } + + /** + * 给指定groupid的WebSocketGroup下最近活跃的WebSocket节点发送二进制消息 + *

+ * @param groupid + * @param data 不可为空 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendRecentMessage(Serializable groupid, byte[] data) { + return sendRecentMessage(groupid, data, true); + } + + /** + * 给指定groupid的WebSocketGroup下最近活跃的WebSocket节点发送文本消息 + *

+ * @param groupid + * @param text 不可为空 + * @param last 是否最后一条 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendRecentMessage(Serializable groupid, String text, boolean last) { + return sendMessage(groupid, true, text, last); + } + + /** + * 给指定groupid的WebSocketGroup下最近活跃的WebSocket节点发送二进制消息 + *

+ * @param groupid + * @param data 不可为空 + * @param last 是否最后一条 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendRecentMessage(Serializable groupid, byte[] data, boolean last) { + return sendMessage(groupid, true, data, last); + } + + private int sendMessage(Serializable groupid, boolean recent, String text, boolean last) { + if (node == null) return RETCODE_NODESERVICE_NULL; + return node.sendMessage(groupid, recent, text, last); + } + + private int sendMessage(Serializable groupid, boolean recent, byte[] data, boolean last) { + if (node == null) return RETCODE_NODESERVICE_NULL; + return node.sendMessage(groupid, recent, data, last); + } + + /** + * 获取当前WebSocket下的属性 + *

+ * @param + * @param name + * @return + */ + @SuppressWarnings("unchecked") + public final T getAttribute(String name) { + return (T) attributes.get(name); + } + + /** + * 移出当前WebSocket下的属性 + *

+ * @param + * @param name + * @return + */ + public final T removeAttribute(String name) { + return (T) attributes.remove(name); + } + + /** + * 给当前WebSocket下的增加属性 + *

+ * @param name + * @param value + */ + public final void setAttribute(String name, Object value) { + attributes.put(name, value); + } + + /** + * 获取当前WebSocket所属的groupid + *

+ * @return + */ + public final Serializable getGroupid() { + return groupid; + } + + /** + * 获取当前WebSocket的会话ID, 不会为null + *

+ * @return + */ + public final Serializable getSessionid() { + return sessionid; + } + + //------------------------------------------------------------------- + /** + * 获取当前WebSocket所属的WebSocketGroup, 不会为null + *

+ * @return + */ + protected final WebSocketGroup getWebSocketGroup() { + return group; + } + + /** + * 获取指定groupid的WebSocketGroup, 没有返回null + *

+ * @param groupid + * @return + */ + protected final WebSocketGroup getWebSocketGroup(Serializable groupid) { + return engine.getWebSocketGroup(groupid); + } + + protected final Collection getWebSocketGroups() { + return engine.getWebSocketGroups(); + } + + //------------------------------------------------------------------- + /** + * 返回sessionid, null表示连接不合法或异常 + * + * @param request + * @return + */ + public Serializable onOpen(final HttpRequest request) { + return request.getSessionid(false); + } + + /** + * 创建groupid, null表示异常 + * + * @return + */ + protected abstract Serializable createGroupid(); + + /** + * + * @param channel + */ + public void onRead(AsyncConnection channel) { + } + + public void onConnected() { + } + + public void onMessage(String text) { + } + + public void onPing(byte[] bytes) { + } + + public void onPong(byte[] bytes) { + } + + public void onMessage(byte[] bytes) { + } + + public void onFragment(String text, boolean last) { + } + + public void onFragment(byte[] bytes, boolean last) { + } + + public void onClose(int code, String reason) { + } +} diff --git a/src/org/redkale/net/http/WebSocketBinary.java b/src/org/redkale/net/http/WebSocketBinary.java new file mode 100644 index 000000000..ab40319c7 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketBinary.java @@ -0,0 +1,23 @@ +/* + * 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.net.http; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 被标记为 @WebSocketBinary 的WebSocketServlet 将使用原始的TCP传输, 通常用于类似音频/视频传输场景 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({TYPE}) +@Retention(RUNTIME) +public @interface WebSocketBinary { + +} diff --git a/src/org/redkale/net/http/WebSocketEngine.java b/src/org/redkale/net/http/WebSocketEngine.java new file mode 100644 index 000000000..7548c78b1 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketEngine.java @@ -0,0 +1,92 @@ +/* + * 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.net.http; + +import static org.redkale.net.http.WebSocketPacket.DEFAULT_PING_PACKET; +import static org.redkale.net.http.WebSocketServlet.DEFAILT_LIVEINTERVAL; +import java.io.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.logging.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class WebSocketEngine { + + private static final AtomicInteger sequence = new AtomicInteger(); + + private final int index; + + private final String engineid; + + private final Map containers = new ConcurrentHashMap<>(); + + private ScheduledThreadPoolExecutor scheduler; + + protected final Logger logger; + + protected final boolean finest; + + protected WebSocketEngine(String engineid, Logger logger) { + this.engineid = engineid; + this.logger = logger; + this.index = sequence.getAndIncrement(); + this.finest = logger.isLoggable(Level.FINEST); + } + + void init(AnyValue conf) { + final int liveinterval = conf == null ? DEFAILT_LIVEINTERVAL : conf.getIntValue("liveinterval", DEFAILT_LIVEINTERVAL); + if (liveinterval == 0) return; + if (scheduler != null) return; + this.scheduler = new ScheduledThreadPoolExecutor(1, (Runnable r) -> { + final Thread t = new Thread(r, engineid + "-WebSocket-LiveInterval-Thread"); + t.setDaemon(true); + return t; + }); + long delay = (liveinterval - System.currentTimeMillis() / 1000 % liveinterval) + index * 5; + scheduler.scheduleWithFixedDelay(() -> { + getWebSocketGroups().stream().forEach(x -> x.sendEach(DEFAULT_PING_PACKET)); + //if (finest) logger.finest(engineid + " ping..."); + }, delay, liveinterval, TimeUnit.SECONDS); + if (finest) logger.finest(this.getClass().getSimpleName() + "(" + engineid + ")" + " start keeplive(delay:" + delay + ", interval:" + liveinterval + "s) scheduler executor"); + } + + void add(WebSocket socket) { + WebSocketGroup group = containers.get(socket.groupid); + if (group == null) { + group = new WebSocketGroup(socket.groupid); + containers.put(socket.groupid, group); + } + group.add(socket); + } + + void remove(WebSocket socket) { + WebSocketGroup group = containers.get(socket.groupid); + if (group == null) return; + group.remove(socket); + if (group.isEmpty()) containers.remove(socket.groupid); + } + + Collection getWebSocketGroups() { + return containers.values(); + } + + public WebSocketGroup getWebSocketGroup(Serializable groupid) { + return containers.get(groupid); + } + + void close() { + if (scheduler != null) scheduler.shutdownNow(); + } + + public String getEngineid() { + return engineid; + } +} diff --git a/src/org/redkale/net/http/WebSocketGroup.java b/src/org/redkale/net/http/WebSocketGroup.java new file mode 100644 index 000000000..943b39d36 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketGroup.java @@ -0,0 +1,128 @@ +/* + * 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.net.http; + +import java.io.*; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Stream; + +/** + * + * @author zhangjx + */ +public final class WebSocketGroup { + + private final Serializable groupid; + + private WebSocket recentWebSocket; + + private final List list = new CopyOnWriteArrayList<>(); + + private final Map attributes = new HashMap<>(); + + WebSocketGroup(Serializable groupid) { + this.groupid = groupid; + } + + public Serializable getGroupid() { + return groupid; + } + + public Stream getWebSockets() { + return list.stream(); + } + + void remove(WebSocket socket) { + list.remove(socket); + } + + void add(WebSocket socket) { + socket.group = this; + this.recentWebSocket = socket; + list.add(socket); + } + + void setRecentWebSocket(WebSocket socket) { + this.recentWebSocket = socket; + } + + public final boolean isEmpty() { + return list.isEmpty(); + } + + public final int size() { + return list.size(); + } + + /** + * 最近发送消息的WebSocket + *

+ * @return + */ + public final WebSocket getRecentWebSocket() { + return recentWebSocket; + } + + @SuppressWarnings("unchecked") + public final T getAttribute(String name) { + return (T) attributes.get(name); + } + + public final void removeAttribute(String name) { + attributes.remove(name); + } + + public final void setAttribute(String name, Object value) { + attributes.put(name, value); + } + + public final int send(boolean recent, Serializable message, boolean last) { + if (recent) { + return recentWebSocket.send(message, last); + } else { + return sendEach(message, last); + } + } + + public final int sendEach(Serializable message) { + return sendEach(message, true); + } + + public final int sendEach(WebSocketPacket packet) { + int rs = 0; + for (WebSocket s : list) { + rs |= s.send(packet); + } + return rs; + } + + public final int sendRecent(Serializable message) { + return sendRecent(message, true); + } + + public final int sendRecent(WebSocketPacket packet) { + return recentWebSocket.send(packet); + } + + public final int sendEach(Serializable message, boolean last) { + int rs = 0; + for (WebSocket s : list) { + rs |= s.send(message, last); + } + return rs; + } + + public final int sendRecent(Serializable message, boolean last) { + return recentWebSocket.send(message, last); + } + + @Override + public String toString() { + return "{groupid: " + groupid + ", list.size: " + (list == null ? -1 : list.size()) + "}"; + } + +} diff --git a/src/org/redkale/net/http/WebSocketNode.java b/src/org/redkale/net/http/WebSocketNode.java new file mode 100644 index 000000000..4ebcccfe0 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketNode.java @@ -0,0 +1,196 @@ +/* + * 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.net.http; + +import static org.redkale.net.http.WebSocket.*; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.logging.*; +import javax.annotation.*; +import org.redkale.net.sncp.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public abstract class WebSocketNode { + + protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName()); + + protected final boolean finest = logger.isLoggable(Level.FINEST); + + @Resource(name = "SERVER_ADDR") + protected InetSocketAddress localSncpAddress; //为SncpServer的服务address + + @SncpRemote + protected WebSocketNode remoteNode; + + //存放所有用户分布在节点上的队列信息,Set 为 sncpnode 的集合 + protected final ConcurrentHashMap> dataNodes = new ConcurrentHashMap(); + + //存放所有用户分布在节点上的队列信息,Set 为 engineid 的集合 + protected final ConcurrentHashMap> localNodes = new ConcurrentHashMap(); + + protected final ConcurrentHashMap engines = new ConcurrentHashMap(); + + public void init(AnyValue conf) { + if (remoteNode != null) { + new Thread() { + { + setDaemon(true); + } + + @Override + public void run() { + try { + Map> map = remoteNode.getDataNodes(); + if (map != null) dataNodes.putAll(map); + } catch (Exception e) { + logger.log(Level.INFO, WebSocketNode.class.getSimpleName() + "(" + localSncpAddress + ") not load data nodes ", e); + } + } + }.start(); + } + } + + public void destroy(AnyValue conf) { + HashMap> nodes = new HashMap<>(localNodes); + nodes.forEach((k, v) -> { + new HashSet<>(v).forEach(e -> { + if (engines.containsKey(e)) disconnect(k, e); + }); + }); + } + + public Map> getDataNodes() { + return dataNodes; + } + + protected abstract int sendMessage(@SncpTargetAddress InetSocketAddress addr, Serializable groupid, boolean recent, Serializable message, boolean last); + + protected abstract void connect(Serializable groupid, InetSocketAddress addr); + + protected abstract void disconnect(Serializable groupid, InetSocketAddress addr); + + //-------------------------------------------------------------------------------- + public final void connect(Serializable groupid, String engineid) { + if (finest) logger.finest(localSncpAddress + " receive websocket connect event (" + groupid + " on " + engineid + ")."); + Set engineids = localNodes.get(groupid); + if (engineids == null) { + engineids = new CopyOnWriteArraySet<>(); + localNodes.put(groupid, engineids); + } + if (localSncpAddress != null && engineids.isEmpty()) connect(groupid, localSncpAddress); + engineids.add(engineid); + } + + public final void disconnect(Serializable groupid, String engineid) { + if (finest) logger.finest(localSncpAddress + " receive websocket disconnect event (" + groupid + " on " + engineid + ")."); + Set engineids = localNodes.get(groupid); + if (engineids == null || engineids.isEmpty()) return; + engineids.remove(engineid); + if (engineids.isEmpty()) { + localNodes.remove(groupid); + if (localSncpAddress != null) disconnect(groupid, localSncpAddress); + } + } + + public final void putWebSocketEngine(WebSocketEngine engine) { + engines.put(engine.getEngineid(), engine); + } + + public final int sendMessage(Serializable groupid, boolean recent, Serializable message, boolean last) { + final Set engineids = localNodes.get(groupid); + int rscode = RETCODE_GROUP_EMPTY; + if (engineids != null && !engineids.isEmpty()) { + for (String engineid : engineids) { + final WebSocketEngine engine = engines.get(engineid); + if (engine != null) { //在本地 + final WebSocketGroup group = engine.getWebSocketGroup(groupid); + if (group == null || group.isEmpty()) { + if (finest) logger.finest("receive websocket but result is " + RETCODE_GROUP_EMPTY + " in message {engineid:'" + engineid + "', groupid:" + groupid + ", content:'" + message + "'}"); + rscode = RETCODE_GROUP_EMPTY; + break; + } + rscode = group.send(recent, message, last); + } + } + } + if ((recent && rscode == 0) || remoteNode == null) return rscode; + LinkedHashSet addrs = dataNodes.get(groupid); + if (addrs != null && !addrs.isEmpty()) { //对方连接在远程节点 + if (recent) { + InetSocketAddress one = null; + for (InetSocketAddress addr : addrs) { + one = addr; + } + rscode = remoteNode.sendMessage(one, groupid, recent, message, last); + } else { + for (InetSocketAddress addr : addrs) { + if (!addr.equals(localSncpAddress)) { + rscode |= remoteNode.sendMessage(addr, groupid, recent, message, last); + } + } + } + } else { + rscode = RETCODE_GROUP_EMPTY; + } + return rscode; + } + + //-------------------------------------------------------------------------------- + public final int sendEachMessage(Serializable groupid, String text) { + return sendMessage(groupid, false, text); + } + + public final int sendEachMessage(Serializable groupid, String text, boolean last) { + return sendMessage(groupid, false, text, last); + } + + public final int sendRecentMessage(Serializable groupid, String text) { + return sendMessage(groupid, true, text); + } + + public final int sendRecentMessage(Serializable groupid, String text, boolean last) { + return sendMessage(groupid, true, text, last); + } + + public final int sendMessage(Serializable groupid, boolean recent, String text) { + return sendMessage(groupid, recent, text, true); + } + + public final int sendMessage(Serializable groupid, boolean recent, String text, boolean last) { + return sendMessage(groupid, recent, (Serializable) text, last); + } + + //-------------------------------------------------------------------------------- + public final int sendEachMessage(Serializable groupid, byte[] data) { + return sendMessage(groupid, false, data); + } + + public final int sendEachMessage(Serializable groupid, byte[] data, boolean last) { + return sendMessage(groupid, false, data, last); + } + + public final int sendRecentMessage(Serializable groupid, byte[] data) { + return sendMessage(groupid, true, data); + } + + public final int sendRecentMessage(Serializable groupid, byte[] data, boolean last) { + return sendMessage(groupid, true, data, last); + } + + public final int sendMessage(Serializable groupid, boolean recent, byte[] data) { + return sendMessage(groupid, recent, data, true); + } + + public final int sendMessage(Serializable groupid, boolean recent, byte[] data, boolean last) { + return sendMessage(groupid, recent, (Serializable) data, last); + } +} diff --git a/src/org/redkale/net/http/WebSocketPacket.java b/src/org/redkale/net/http/WebSocketPacket.java new file mode 100644 index 000000000..68bd79667 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketPacket.java @@ -0,0 +1,123 @@ +/* + * 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.net.http; + +import org.redkale.util.Utility; +import java.io.*; +import java.util.*; + +/** + * + * @author zhangjx + */ +public final class WebSocketPacket { + + public static final WebSocketPacket DEFAULT_PING_PACKET = new WebSocketPacket(FrameType.PING, new byte[0]); + + public static enum FrameType { + + TEXT(0x01), BINARY(0x02), CLOSE(0x08), PING(0x09), PONG(0x0A); + + private final int value; + + private FrameType(int v) { + this.value = v; + } + + public int getValue() { + return value; + } + + public static FrameType valueOf(int v) { + switch (v) { + case 0x01: return TEXT; + case 0x02: return BINARY; + case 0x08: return CLOSE; + case 0x09: return PING; + case 0x0A: return PONG; + default: return null; + } + } + } + + protected FrameType type; + + protected String payload; + + protected byte[] bytes; + + protected boolean last = true; + + public WebSocketPacket() { + } + + public WebSocketPacket(String payload) { + this(payload, true); + } + + public WebSocketPacket(Serializable message, boolean fin) { + boolean bin = message != null && message.getClass() == byte[].class; + if (bin) { + this.type = FrameType.BINARY; + this.bytes = (byte[]) message; + } else { + this.type = FrameType.TEXT; + this.payload = String.valueOf(message); + } + this.last = fin; + } + + public WebSocketPacket(String payload, boolean fin) { + this.type = FrameType.TEXT; + this.payload = payload; + this.last = fin; + } + + public WebSocketPacket(byte[] data) { + this(FrameType.BINARY, data, true); + } + + public WebSocketPacket(byte[] data, boolean fin) { + this(FrameType.BINARY, data, fin); + } + + public WebSocketPacket(FrameType type, byte[] data) { + this(type, data, true); + } + + public WebSocketPacket(FrameType type, byte[] data, boolean fin) { + this.type = type; + if (type == FrameType.TEXT) { + this.payload = new String(Utility.decodeUTF8(data)); + } else { + this.bytes = data; + } + this.last = fin; + } + + public byte[] getContent() { + if (this.type == FrameType.TEXT) return Utility.encodeUTF8(getPayload()); + if (this.bytes == null) return new byte[0]; + return this.bytes; + } + + public String getPayload() { + return payload; + } + + public byte[] getBytes() { + return bytes; + } + + public boolean isLast() { + return last; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "[type=" + type + ", last=" + last + (payload != null ? (", payload=" + payload) : "") + (bytes != null ? (", bytes=" + Arrays.toString(bytes)) : "") + "]"; + } +} diff --git a/src/org/redkale/net/http/WebSocketRunner.java b/src/org/redkale/net/http/WebSocketRunner.java new file mode 100644 index 000000000..4a149d665 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketRunner.java @@ -0,0 +1,529 @@ +/* + * 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.net.http; + +import org.redkale.net.AsyncConnection; +import org.redkale.net.Context; +import static org.redkale.net.http.WebSocket.*; +import org.redkale.net.http.WebSocketPacket.FrameType; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.security.SecureRandom; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.*; + +/** + * + * @author zhangjx + */ +public class WebSocketRunner implements Runnable { + + private final WebSocketEngine engine; + + private final AsyncConnection channel; + + private final WebSocket webSocket; + + protected final Context context; + + private ByteBuffer readBuffer; + + private ByteBuffer writeBuffer; + + protected boolean closed = false; + + private AtomicBoolean writing = new AtomicBoolean(); + + private final Coder coder = new Coder(); + + private final BlockingQueue queue = new ArrayBlockingQueue(1024); + + private final boolean wsbinary; + + public WebSocketRunner(Context context, WebSocket webSocket, AsyncConnection channel, final boolean wsbinary) { + this.context = context; + this.engine = webSocket.engine; + this.webSocket = webSocket; + this.channel = channel; + this.wsbinary = wsbinary; + webSocket.runner = this; + this.coder.logger = context.getLogger(); + this.coder.debugable = false;//context.getLogger().isLoggable(Level.FINEST); + this.readBuffer = context.pollBuffer(); + this.writeBuffer = context.pollBuffer(); + } + + @Override + public void run() { + final boolean debug = this.coder.debugable; + try { + if (webSocket.node != null) webSocket.node.connect(webSocket.groupid, webSocket.engine.getEngineid()); + webSocket.onConnected(); + channel.setReadTimeoutSecond(300); //读取超时5分钟 + if (channel.isOpen()) { + if (wsbinary) { + webSocket.onRead(channel); + return; + } + channel.read(readBuffer, null, new CompletionHandler() { + + private ByteBuffer recentExBuffer; + + //当接收的数据流长度大于ByteBuffer长度时, 则需要额外的ByteBuffer 辅助; + private final List readBuffers = new ArrayList<>(); + + @Override + public void completed(Integer count, Void attachment1) { + if (count < 1 && readBuffers.isEmpty()) { + closeRunner(); + if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort on read buffer count, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds"); + return; + } + if (readBuffer == null) return; + if (!readBuffer.hasRemaining() && (recentExBuffer == null || !recentExBuffer.hasRemaining())) { + final ByteBuffer buffer = context.pollBuffer(); + recentExBuffer = buffer; + readBuffers.add(buffer); + channel.read(buffer, null, this); + return; + } + readBuffer.flip(); + try { + ByteBuffer[] exBuffers = null; + if (!readBuffers.isEmpty()) { + exBuffers = readBuffers.toArray(new ByteBuffer[readBuffers.size()]); + readBuffers.clear(); + recentExBuffer = null; + for (ByteBuffer b : exBuffers) { + b.flip(); + } + } + WebSocketPacket packet = coder.decode(readBuffer, exBuffers); + if (exBuffers != null) { + for (ByteBuffer b : exBuffers) { + context.offerBuffer(b); + } + } + if (packet == null) { + failed(null, attachment1); + if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort on decode WebSocketPacket, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds"); + return; + } + if (readBuffer != null) { + readBuffer.clear(); + channel.read(readBuffer, null, this); + } + webSocket.group.setRecentWebSocket(webSocket); + try { + if (packet.type == FrameType.TEXT) { + webSocket.onMessage(packet.getPayload()); + } else if (packet.type == FrameType.BINARY) { + webSocket.onMessage(packet.getBytes()); + } else if (packet.type == FrameType.PONG) { + webSocket.onPong(packet.getBytes()); + } else if (packet.type == FrameType.PING) { + webSocket.onPing(packet.getBytes()); + } + } catch (Exception e) { + context.getLogger().log(Level.INFO, "WebSocket onMessage error (" + packet + ")", e); + } + } catch (Throwable t) { + closeRunner(); + if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner abort on read WebSocketPacket, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", t); + } + } + + @Override + public void failed(Throwable exc, Void attachment2) { + closeRunner(); + if (exc != null) { + context.getLogger().log(Level.FINEST, "WebSocketRunner read WebSocketPacket failed, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", exc); + } + } + }); + } else { + closeRunner(); + context.getLogger().log(Level.FINEST, "WebSocketRunner abort by AsyncConnection closed"); + } + } catch (Exception e) { + closeRunner(); + context.getLogger().log(Level.FINEST, "WebSocketRunner abort on read bytes from channel, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", e); + } + } + + public int sendMessage(WebSocketPacket packet) { + if (packet == null) return RETCODE_SEND_ILLPACKET; + if (closed) return RETCODE_WSOCKET_CLOSED; + final boolean debug = this.coder.debugable; + //System.out.println("推送消息"); + final byte[] bytes = coder.encode(packet); + if (debug) context.getLogger().log(Level.FINEST, "send web socket message's length = " + bytes.length); + if (writing.getAndSet(true)) { + queue.add(bytes); + return 0; + } + if (writeBuffer == null) return RETCODE_ILLEGALBUFFER; + ByteBuffer sendBuffer = null; + if (bytes.length <= writeBuffer.capacity()) { + writeBuffer.clear(); + writeBuffer.put(bytes); + writeBuffer.flip(); + sendBuffer = writeBuffer; + } else { + sendBuffer = ByteBuffer.wrap(bytes); + } + try { + channel.write(sendBuffer, sendBuffer, new CompletionHandler() { + + @Override + public void completed(Integer result, ByteBuffer attachment) { + if (attachment == null || closed) return; + try { + if (attachment.hasRemaining()) { + if (debug) context.getLogger().log(Level.FINEST, "WebSocketRunner write completed reemaining: " + attachment.remaining()); + channel.write(attachment, attachment, this); + return; + } + byte[] bs = queue.poll(); + if (bs != null && writeBuffer != null) { + ByteBuffer sendBuffer; + if (bs.length <= writeBuffer.capacity()) { + writeBuffer.clear(); + writeBuffer.put(bs); + writeBuffer.flip(); + sendBuffer = writeBuffer; + } else { + sendBuffer = ByteBuffer.wrap(bs); + } + channel.write(sendBuffer, sendBuffer, this); + return; + } + } catch (NullPointerException e) { + } catch (Exception e) { + closeRunner(); + context.getLogger().log(Level.WARNING, "WebSocket sendMessage abort on rewrite, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", e); + } + writing.set(false); + } + + @Override + public void failed(Throwable exc, ByteBuffer attachment) { + writing.set(false); + closeRunner(); + if (exc != null) { + context.getLogger().log(Level.FINE, "WebSocket sendMessage on CompletionHandler failed, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", exc); + } + } + }); + return 0; + } catch (Exception t) { + writing.set(false); + closeRunner(); + context.getLogger().log(Level.FINE, "WebSocket sendMessage abort, force to close channel, live " + (System.currentTimeMillis() - webSocket.getCreatetime()) / 1000 + " seconds", t); + return RETCODE_SENDEXCEPTION; + } + } + + public void closeRunner() { + if (closed) return; + synchronized (this) { + if (closed) return; + closed = true; + try { + channel.close(); + } catch (Throwable t) { + } + context.offerBuffer(readBuffer); + context.offerBuffer(writeBuffer); + readBuffer = null; + writeBuffer = null; + engine.remove(webSocket); + if (webSocket.node != null) { + WebSocketGroup group = webSocket.getWebSocketGroup(); + if (group == null || group.isEmpty()) webSocket.node.disconnect(webSocket.groupid, webSocket.engine.getEngineid()); + } + webSocket.onClose(0, null); + } + } + + private static final class Masker { + + public static final int MASK_SIZE = 4; + + private ByteBuffer buffer; + + private ByteBuffer[] exbuffers; + + private byte[] mask; + + private int index = 0; + + public Masker(ByteBuffer buffer, ByteBuffer... exbuffers) { + this.buffer = buffer; + this.exbuffers = exbuffers == null || exbuffers.length == 0 ? null : exbuffers; + } + + public Masker() { + generateMask(); + } + + public int remaining() { + int r = buffer.remaining(); + if (exbuffers != null) { + for (ByteBuffer b : exbuffers) { + r += b.remaining(); + } + } + return r; + } + + public byte get() { + return buffer.get(); + } + + public byte[] get(final int size) { + byte[] bytes = new byte[size]; + if (buffer.remaining() >= size) { + buffer.get(bytes); + } else { //必须有 exbuffers + int offset = buffer.remaining(); + buffer.get(bytes, 0, buffer.remaining()); + for (ByteBuffer b : exbuffers) { + b.get(bytes, offset, b.remaining()); + offset += b.remaining(); + } + } + return bytes; + } + + public byte unmask() { + final byte b = get(); + return mask == null ? b : (byte) (b ^ mask[index++ % MASK_SIZE]); + } + + public byte[] unmask(int count) { + byte[] bytes = get(count); + if (mask != null) { + for (int i = 0; i < bytes.length; i++) { + bytes[i] ^= mask[index++ % MASK_SIZE]; + } + } + + return bytes; + } + + public void generateMask() { + mask = new byte[MASK_SIZE]; + new SecureRandom().nextBytes(mask); + } + + public void mask(byte[] bytes, int location, byte b) { + bytes[location] = mask == null ? b : (byte) (b ^ mask[index++ % MASK_SIZE]); + } + + public void mask(byte[] target, int location, byte[] bytes) { + if (bytes != null && target != null) { + for (int i = 0; i < bytes.length; i++) { + target[location + i] = mask == null ? bytes[i] : (byte) (bytes[i] ^ mask[index++ % MASK_SIZE]); + } + } + } + + public byte[] maskAndPrepend(byte[] packet) { + byte[] masked = new byte[packet.length + MASK_SIZE]; + System.arraycopy(getMask(), 0, masked, 0, MASK_SIZE); + mask(masked, MASK_SIZE, packet); + return masked; + } + + public void setBuffer(ByteBuffer buffer) { + this.buffer = buffer; + } + + public byte[] getMask() { + return mask; + } + + public void readMask() { + mask = get(MASK_SIZE); + } + } + + private static final class Coder { + + protected byte inFragmentedType; + + protected byte outFragmentedType; + + protected final boolean maskData = false; + + protected boolean processingFragment; + + private boolean debugable; + + private Logger logger; + + /** + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued | + +-----------------------------------------------------------------------+ + @param buffer + @param exbuffers + @return + */ + public WebSocketPacket decode(final ByteBuffer buffer, ByteBuffer... exbuffers) { + final boolean debug = this.debugable; + if (debug) { + int remain = buffer.remaining(); + if (exbuffers != null) { + for (ByteBuffer b : exbuffers) { + remain += b == null ? 0 : b.remaining(); + } + } + logger.log(Level.FINEST, "read web socket message's length = " + remain); + } + if (buffer.remaining() < 2) return null; + byte opcode = buffer.get(); + final boolean last = (opcode & 0b1000000) != 0; + final boolean checkrsv = false;//暂时不校验 + if (checkrsv && (opcode & 0b01110000) != 0) { + if (debug) logger.log(Level.FINE, "rsv1 rsv2 rsv3 must be 0, but not (" + opcode + ")"); + return null; //rsv1 rsv2 rsv3 must be 0 + } + //0x00 表示一个后续帧 + //0x01 表示一个文本帧 + //0x02 表示一个二进制帧 + //0x03-07 为以后的非控制帧保留 + //0x8 表示一个连接关闭 + //0x9 表示一个ping + //0xA 表示一个pong + //0x0B-0F 为以后的控制帧保留 + final boolean control = (opcode & 0x08) == 0x08; //是否控制帧 + //final boolean continuation = opcode == 0; + FrameType type = FrameType.valueOf(opcode & 0xf); + if (type == FrameType.CLOSE) { + if (debug) logger.log(Level.FINEST, " receive close command from websocket client"); + return null; + } + byte lengthCode = buffer.get(); + final Masker masker = new Masker(buffer, exbuffers); + final boolean masked = (lengthCode & 0x80) == 0x80; + if (masked) lengthCode ^= 0x80; //mask + int length; + if (lengthCode <= 125) { + length = lengthCode; + } else { + if (control) { + if (debug) logger.log(Level.FINE, " receive control command from websocket client"); + return null; + } + + final int lengthBytes = lengthCode == 126 ? 2 : 8; + if (buffer.remaining() < lengthBytes) { + if (debug) logger.log(Level.FINE, " read illegal message length from websocket, expect " + lengthBytes + " but " + buffer.remaining()); + return null; + } + length = toInt(masker.unmask(lengthBytes)); + } + if (masked) { + if (buffer.remaining() < Masker.MASK_SIZE) { + if (debug) logger.log(Level.FINE, " read illegal masker length from websocket, expect " + Masker.MASK_SIZE + " but " + buffer.remaining()); + return null; + } + masker.readMask(); + } + if (masker.remaining() < length) { + if (debug) logger.log(Level.FINE, " read illegal remaining length from websocket, expect " + length + " but " + masker.remaining()); + return null; + } + final byte[] data = masker.unmask(length); + if (data.length != length) { + if (debug) logger.log(Level.FINE, " read illegal unmask length from websocket, expect " + length + " but " + data.length); + return null; + } + return new WebSocketPacket(type, data, last); + } + + public byte[] encode(WebSocketPacket frame) { + byte opcode = (byte) (frame.type.getValue() | 0x80); + final byte[] bytes = frame.getContent(); + final byte[] lengthBytes = encodeLength(bytes.length); + + int length = 1 + lengthBytes.length + bytes.length + (maskData ? Masker.MASK_SIZE : 0); + int payloadStart = 1 + lengthBytes.length + (maskData ? Masker.MASK_SIZE : 0); + final byte[] packet = new byte[length]; + packet[0] = opcode; + System.arraycopy(lengthBytes, 0, packet, 1, lengthBytes.length); + if (maskData) { + Masker masker = new Masker(); + packet[1] |= 0x80; + masker.mask(packet, payloadStart, bytes); + System.arraycopy(masker.getMask(), 0, packet, payloadStart - Masker.MASK_SIZE, Masker.MASK_SIZE); + } else { + System.arraycopy(bytes, 0, packet, payloadStart, bytes.length); + } + return packet; + } + + private static byte[] encodeLength(final int length) { + byte[] lengthBytes; + if (length <= 125) { + lengthBytes = new byte[1]; + lengthBytes[0] = (byte) length; + } else { + byte[] b = toArray(length); + if (length <= 0xFFFF) { + lengthBytes = new byte[3]; + lengthBytes[0] = 126; + System.arraycopy(b, 6, lengthBytes, 1, 2); + } else { + lengthBytes = new byte[9]; + lengthBytes[0] = 127; + System.arraycopy(b, 0, lengthBytes, 1, 8); + } + } + return lengthBytes; + } + + private static byte[] toArray(long length) { + long value = length; + byte[] b = new byte[8]; + for (int i = 7; i >= 0 && value > 0; i--) { + b[i] = (byte) (value & 0xFF); + value >>= 8; + } + return b; + } + + private static int toInt(byte[] bytes) { + int value = 0; + for (int i = 0; i < bytes.length; i++) { + value <<= 8; + value ^= (int) bytes[i] & 0xFF; + } + return value; + } + + } + +} diff --git a/src/org/redkale/net/http/WebSocketServlet.java b/src/org/redkale/net/http/WebSocketServlet.java new file mode 100644 index 000000000..529e69d25 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketServlet.java @@ -0,0 +1,145 @@ +/* + * 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.net.http; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.security.*; +import java.util.*; +import java.util.logging.*; +import javax.annotation.*; +import org.redkale.net.*; +import org.redkale.util.*; + +/** + * 当WebSocketServlet接收一个TCP连接后,进行协议判断,如果成功就会创建一个WebSocket。 + * + * WebSocketServlet + * | + * | + * WebSocketEngine + * / \ + * / \ + * / \ + * WebSocketGroup1 WebSocketGroup2 + * / \ / \ + * / \ / \ + * WebSocket1 WebSocket2 WebSocket3 WebSocket4 + * + * @author zhangjx + */ +public abstract class WebSocketServlet extends HttpServlet implements Nameable { + + public static final String WEBPARAM__LIVEINTERVAL = "liveinterval"; + + public static final int DEFAILT_LIVEINTERVAL = 60; + + protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName()); + + private final MessageDigest digest = getMessageDigest(); + + private static MessageDigest getMessageDigest() { + try { + return MessageDigest.getInstance("SHA-1"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //是否用于二进制流传输 + protected final boolean wsbinary = getClass().getAnnotation(WebSocketBinary.class) != null; + + @Resource(name = "$") + protected WebSocketNode node; + + protected WebSocketEngine engine; + + @Override + public void init(Context context, AnyValue conf) { + InetSocketAddress addr = context.getServerAddress(); + this.engine = new WebSocketEngine(addr.getHostString() + ":" + addr.getPort() + "-" + name(), logger); + this.node.putWebSocketEngine(engine); + this.node.init(conf); + this.engine.init(conf); + } + + @Override + public void destroy(Context context, AnyValue conf) { + this.node.destroy(conf); + super.destroy(context, conf); + engine.close(); + } + + @Override + public String name() { + return this.getClass().getSimpleName().replace("Servlet", "").replace("WebSocket", "").toLowerCase(); + } + + @Override + public final void execute(final HttpRequest request, final HttpResponse response) throws IOException { + final boolean debug = logger.isLoggable(Level.FINEST); + if (!"GET".equalsIgnoreCase(request.getMethod()) + || !request.getConnection().contains("Upgrade") + || !"websocket".equalsIgnoreCase(request.getHeader("Upgrade"))) { + if (debug) logger.finest("WebSocket connect abort, (Not GET Method) or (Connection != Upgrade) or (Upgrade != websocket). request=" + request); + response.finish(true); + return; + } + String key = request.getHeader("Sec-WebSocket-Key"); + if (key == null) { + if (debug) logger.finest("WebSocket connect abort, Not found Sec-WebSocket-Key header. request=" + request); + response.finish(true); + return; + } + final WebSocket webSocket = this.createWebSocket(); + webSocket.engine = engine; + webSocket.node = node; + Serializable sessionid = webSocket.onOpen(request); + if (sessionid == null) { + if (debug) logger.finest("WebSocket connect abort, Not found sessionid. request=" + request); + response.finish(true); + return; + } + webSocket.sessionid = sessionid; + request.setKeepAlive(true); + byte[] bytes = (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes(); + synchronized (digest) { + bytes = digest.digest(bytes); + } + key = Base64.getEncoder().encodeToString(bytes); + response.setStatus(101); + response.setHeader("Connection", "Upgrade"); + response.addHeader("Upgrade", "websocket"); + response.addHeader("Sec-WebSocket-Accept", key); + response.sendBody((ByteBuffer) null, null, new CompletionHandler() { + + @Override + public void completed(Integer result, Void attachment) { + HttpContext context = response.getContext(); + Serializable groupid = webSocket.createGroupid(); + if (groupid == null) { + if (debug) logger.finest("WebSocket connect abort, Create groupid abort. request = " + request); + response.finish(true); + return; + } + webSocket.groupid = groupid; + engine.add(webSocket); + context.submit(new WebSocketRunner(context, webSocket, response.removeChannel(), wsbinary)); + response.finish(true); + } + + @Override + public void failed(Throwable exc, Void attachment) { + logger.log(Level.FINEST, "WebSocket connect abort, Response send abort. request = " + request, exc); + response.finish(true); + } + }); + } + + protected abstract WebSocket createWebSocket(); +} diff --git a/src/org/redkale/net/sncp/ServiceWrapper.java b/src/org/redkale/net/sncp/ServiceWrapper.java new file mode 100644 index 000000000..2c12c98d0 --- /dev/null +++ b/src/org/redkale/net/sncp/ServiceWrapper.java @@ -0,0 +1,92 @@ +/* + * 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.net.sncp; + +import org.redkale.service.Service; +import org.redkale.util.AnyValue; +import java.util.*; +import org.redkale.boot.*; + +/** + * Service对象的封装类 + * + * @author zhangjx + * @param + */ +public final class ServiceWrapper { + + private final Class type; + + private final T service; + + private final AnyValue conf; + + private final String group; + + private final Set groups; + + private final String name; + + private final boolean remote; + + public ServiceWrapper(Class type, T service, String group, ClassFilter.FilterEntry entry) { + this(type, service, entry.getName(), group, entry.getGroups(), entry.getProperty()); + } + + public ServiceWrapper(Class type, T service, String name, String group, Set groups, AnyValue conf) { + this.type = type == null ? (Class) service.getClass() : type; + this.service = service; + this.conf = conf; + this.group = group; + this.groups = groups; + this.name = name; + this.remote = Sncp.isRemote(service); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null) return false; + if (!(obj instanceof ServiceWrapper)) return false; + ServiceWrapper other = (ServiceWrapper) obj; + return (this.type.equals(other.type) && this.remote == other.remote && this.name.equals(other.name) && this.group.equals(other.group)); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 67 * hash + Objects.hashCode(this.type); + hash = 67 * hash + Objects.hashCode(this.group); + hash = 67 * hash + Objects.hashCode(this.name); + hash = 67 * hash + (this.remote ? 1 : 0); + return hash; + } + + public Class getType() { + return type; + } + + public Service getService() { + return service; + } + + public AnyValue getConf() { + return conf; + } + + public String getName() { + return service.name(); + } + + public boolean isRemote() { + return remote; + } + + public Set getGroups() { + return groups; + } + +} diff --git a/src/org/redkale/net/sncp/Sncp.java b/src/org/redkale/net/sncp/Sncp.java new file mode 100644 index 000000000..8bbe487fc --- /dev/null +++ b/src/org/redkale/net/sncp/Sncp.java @@ -0,0 +1,1108 @@ +/* + * 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.net.sncp; + +import org.redkale.net.sncp.SncpClient.SncpAction; +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import java.util.function.*; +import javax.annotation.*; +import jdk.internal.org.objectweb.asm.*; +import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import jdk.internal.org.objectweb.asm.Type; +import org.redkale.convert.bson.*; +import org.redkale.net.*; +import org.redkale.service.*; +import org.redkale.util.*; + +/** + * 生成Service的本地模式或远程模式Service-Class的工具类 + * + * @author zhangjx + */ +public abstract class Sncp { + + //当前SNCP Server的IP地址+端口 类型: SocketAddress、InetSocketAddress、String + public static final String RESNAME_SNCP_ADDR = "SNCP_ADDR"; + + //当前Service所属的组 类型: Set、String[] + public static final String RESNAME_SNCP_GROUPS = "SNCP_GROUPS"; + + private static final java.lang.reflect.Type GROUPS_TYPE1 = new TypeToken>() { + }.getType(); + + private static final java.lang.reflect.Type GROUPS_TYPE2 = new TypeToken() { + }.getType(); + + static final String LOCALPREFIX = "_DynLocal"; + + static final String REMOTEPREFIX = "_DynRemote"; + + private static final byte[] hashes = new byte[255]; + + static { + //0-9:48-57 A-Z:65-90 a-z:97-122 $:36 _:95 + byte index = 0; + hashes['_'] = index++; + hashes['$'] = index++; + for (int i = '0'; i <= '9'; i++) { + hashes[i] = index++; + } + for (int i = 'A'; i <= 'Z'; i++) { + hashes[i] = index++; + } + for (int i = 'a'; i <= 'z'; i++) { + hashes[i] = index++; + } + } + + private Sncp() { + } + + public static long nodeid(InetSocketAddress ip) { + byte[] bytes = ip.getAddress().getAddress(); + return ((0L + ip.getPort()) << 32) | ((0xffffffff & bytes[0]) << 24) | ((0xffffff & bytes[1]) << 16) | ((0xffff & bytes[2]) << 8) | (0xff & bytes[3]); + } + + public static long hash(final Class clazz) { + if (clazz == null) return Long.MIN_VALUE; + long rs = hash(clazz.getSimpleName()); + return (rs < Integer.MAX_VALUE) ? rs | 0xF00000000L : rs; + } + + public static long hashClass(final String clazzName) { + if (clazzName == null || clazzName.isEmpty()) return Long.MIN_VALUE; + long rs = hash(clazzName.substring(clazzName.lastIndexOf('.') + 1)); + return (rs < Integer.MAX_VALUE) ? rs | 0xF00000000L : rs; + } + + public static DLong hash(final java.lang.reflect.Method method) { + if (method == null) return new DLong(-1L, -1L); + String n = method.getName(); + if (n.length() > 11) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < n.length(); i += 2) { + sb.append(n.charAt(i)); + } + sb.append(n.length()); + n = sb.toString(); + } + long rs1 = hash(n); + if (rs1 < Integer.MAX_VALUE) rs1 |= (method.getParameterCount() + 0L) << 32; + rs1 = (rs1 < Integer.MAX_VALUE) ? rs1 | 0xF00000000L : rs1; + + long rs2 = hash(wrapName(method), true); + if (rs2 < Integer.MAX_VALUE) rs2 |= (method.getParameterCount() + 0L) << 32; + rs2 = (rs2 < Integer.MAX_VALUE) ? rs2 | 0xF00000000L : rs2; + return new DLong(rs1, rs2); + } + + private static String wrapName(final java.lang.reflect.Method method) { + final Class[] params = method.getParameterTypes(); + if (params.length == 0) return method.getName() + "00"; + StringBuilder sb = new StringBuilder(); + for (Class clzz : params) { + String s = clzz.getSimpleName(); + sb.append(s.substring(0, s.length() > 1 ? 2 : 1)).append(s.substring(s.length() - 1)); + } + String n = method.getName(); + if (n.length() > 11) { + StringBuilder zsb = new StringBuilder(); + for (int i = 0; i < n.length(); i += 2) { + zsb.append(n.charAt(i)); + } + zsb.append(n.length()); + n = zsb.toString(); + } + return n + sb + Integer.toString(params.length, 36); + } + + public static long hash(final String name) { + return hash(name, false); + } + + public static long hash(final String name, boolean reverse) { + if (name == null) return Long.MIN_VALUE; + if (name.isEmpty()) return 0; + char[] chars = Utility.charArray(name); + long rs = 0L; + if (reverse) { + int start = Math.max(chars.length - 10, 0); + for (int i = chars.length - 1; i >= start; i--) { + rs = (rs << 6) | hashes[0xff & chars[i]]; + } + } else { + int end = Math.min(chars.length, 11); + for (int i = 0; i < end; i++) { + rs = (rs << 6) | hashes[0xff & chars[i]]; + } + } + return Math.abs(rs); + } + + public static boolean isRemote(Service service) { + return service.getClass().getName().startsWith(REMOTEPREFIX); + } + + /** + * public class TestService implements Service{ + * + * public String queryNode(){ + * return "hello"; + * } + * + * @MultiRun + * public String updateSomeThing(String id){ + * return "hello" + id; + * } + * + * @MultiRun(selfrun = false) + * public void createSomeThing(TestBean bean){ + * "xxxxx" + bean; + * } + * } + * + * public final class _DynLocalTestService extends TestService{ + * + * @Resource + * private BsonConvert _convert; + * + * private Transport[] _sameGroupTransports; + * + * private Transport[] _diffGroupTransports; + * + * private SncpClient _client; + * + * private String _selfstring; + * + * @Override + * public final String name() { + * return ""; + * } + * + * @Override + * public String toString() { + * return _selfstring == null ? super.toString() : _selfstring; + * } + * + * @Override + * public String updateSomeThing(String id){ + * return _updateSomeThing(true, true, true, id); + * } + * + * public String _updateSomeThing(boolean canselfrun, boolean cansamerun, boolean candiffrun, String id){ + * String rs = super.updateSomeThing(id); + * if (_client== null) return; + * _client.remote(_convert, _sameGroupTransports, cansamerun, 0, true, false, false, id); + * _client.remote(_convert, _diffGroupTransports, candiffrun, 0, true, true, false, id); + * return rs; + * } + * + * @Override + * public void createSomeThing(TestBean bean){ + * _createSomeThing(false, true, true, bean); + * } + * + * public void _createSomeThing(boolean canselfrun, boolean cansamerun, boolean candiffrun, TestBean bean){ + * if(canselfrun) super.createSomeThing(bean); + * if (_client== null) return; + * _client.remote(_convert, _sameGroupTransports, cansamerun, 1, true, false, false, bean); + * _client.remote(_convert, _diffGroupTransports, candiffrun, 1, true, true, false, bean); + * } + * } + * + * 创建Service的本地模式Class + * @param + * @param name + * @param serviceClass + * @return + */ + @SuppressWarnings("unchecked") + public static Class createLocalServiceClass(final String name, final Class serviceClass) { + if (serviceClass == null) return null; + if (!Service.class.isAssignableFrom(serviceClass)) return serviceClass; + int mod = serviceClass.getModifiers(); + if (!java.lang.reflect.Modifier.isPublic(mod)) return serviceClass; + if (java.lang.reflect.Modifier.isAbstract(mod)) return serviceClass; + final List methods = SncpClient.parseMethod(serviceClass, false); + boolean hasMultiRun0 = false; + for (Method method : methods) { + if (method.getAnnotation(MultiRun.class) != null) { + hasMultiRun0 = true; + break; + } + } + final boolean hasMultiRun = hasMultiRun0; + final String supDynName = serviceClass.getName().replace('.', '/'); + final String clientName = SncpClient.class.getName().replace('.', '/'); + final String clientDesc = Type.getDescriptor(SncpClient.class); + final String convertDesc = Type.getDescriptor(BsonConvert.class); + final String sncpDynDesc = Type.getDescriptor(SncpDyn.class); + final String transportsDesc = Type.getDescriptor(Transport[].class); + ClassLoader loader = Sncp.class.getClassLoader(); + String newDynName = supDynName.substring(0, supDynName.lastIndexOf('/') + 1) + LOCALPREFIX + serviceClass.getSimpleName(); + try { + return (Class) Class.forName(newDynName.replace('/', '.')); + } catch (Exception ex) { + } + //------------------------------------------------------------------------------ + ClassWriter cw = new ClassWriter(COMPUTE_FRAMES); + FieldVisitor fv; + DebugMethodVisitor mv; + AnnotationVisitor av0; + + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, supDynName, null); + { + av0 = cw.visitAnnotation(sncpDynDesc, true); + av0.visitEnd(); + } + if (hasMultiRun) { + { + fv = cw.visitField(ACC_PRIVATE, "_convert", convertDesc, null, null); + av0 = fv.visitAnnotation("Ljavax/annotation/Resource;", true); + av0.visitEnd(); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE, "_sameGroupTransports", transportsDesc, null, null); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE, "_diffGroupTransports", transportsDesc, null, null); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE, "_client", clientDesc, null, null); + fv.visitEnd(); + } + } + { + fv = cw.visitField(ACC_PRIVATE, "_selfstring", "Ljava/lang/String;", null, null); + fv.visitEnd(); + } + { //构造函数 + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC, "", "()V", null, null)); + //mv.setDebug(true); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, supDynName, "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { // name() + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "name", "()Ljava/lang/String;", null, null)); + mv.visitLdcInsn(name); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { // toString() + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null)); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_selfstring", "Ljava/lang/String;"); + Label l1 = new Label(); + mv.visitJumpInsn(IFNONNULL, l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false); + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + mv.visitLabel(l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_selfstring", "Ljava/lang/String;"); + mv.visitLabel(l2); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + int i = - 1; + for (final Method method : methods) { + final MultiRun mrun = method.getAnnotation(MultiRun.class); + if (mrun == null) continue; + final Class returnType = method.getReturnType(); + final String methodDesc = Type.getMethodDescriptor(method); + final Class[] paramtypes = method.getParameterTypes(); + final int index = ++i; + { //原始方法 + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC + (method.isVarArgs() ? ACC_VARARGS : 0), method.getName(), methodDesc, null, null)); + //mv.setDebug(true); + { //给参数加上 Annotation + final Annotation[][] anns = method.getParameterAnnotations(); + for (int k = 0; k < anns.length; k++) { + for (Annotation ann : anns[k]) { + visitAnnotation(mv.visitParameterAnnotation(k, Type.getDescriptor(ann.annotationType()), true), ann); + } + } + } + mv.visitVarInsn(ALOAD, 0); + mv.visitInsn(mrun.selfrun() ? ICONST_1 : ICONST_0); + mv.visitInsn(mrun.samerun() ? ICONST_1 : ICONST_0); + mv.visitInsn(mrun.diffrun() ? ICONST_1 : ICONST_0); + int varindex = 0; + for (Class pt : paramtypes) { + if (pt.isPrimitive()) { + if (pt == long.class) { + mv.visitVarInsn(LLOAD, ++varindex); + ++varindex; + } else if (pt == double.class) { + mv.visitVarInsn(DLOAD, ++varindex); + ++varindex; + } else if (pt == float.class) { + mv.visitVarInsn(FLOAD, ++varindex); + } else { + mv.visitVarInsn(ILOAD, ++varindex); + } + } else { + mv.visitVarInsn(ALOAD, ++varindex); + } + } + mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "_" + method.getName(), "(ZZZ" + methodDesc.substring(1), false); + if (returnType == void.class) { + mv.visitInsn(RETURN); + } else if (returnType.isPrimitive()) { + if (returnType == long.class) { + mv.visitInsn(LRETURN); + } else if (returnType == float.class) { + mv.visitInsn(FRETURN); + } else if (returnType == double.class) { + mv.visitInsn(DRETURN); + } else { + mv.visitInsn(IRETURN); + } + } else { + mv.visitInsn(ARETURN); + } + mv.visitMaxs(varindex + 3, varindex + 1); + mv.visitEnd(); + } + { // _方法 + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC + (method.isVarArgs() ? ACC_VARARGS : 0), "_" + method.getName(), "(ZZZ" + methodDesc.substring(1), null, null)); + //mv.setDebug(true); + { //给参数加上 Annotation + final Annotation[][] anns = method.getParameterAnnotations(); + for (int k = 0; k < anns.length; k++) { + for (Annotation ann : anns[k]) { + visitAnnotation(mv.visitParameterAnnotation(k, Type.getDescriptor(ann.annotationType()), true), ann); + } + } + } + av0 = mv.visitAnnotation(sncpDynDesc, true); + av0.visit("index", index); + av0.visitEnd(); + Label l1 = new Label(); + if (returnType == void.class) { // if + mv.visitVarInsn(ILOAD, 1); + mv.visitJumpInsn(IFEQ, l1); + } + mv.visitVarInsn(ALOAD, 0); + int varindex = 3; + for (Class pt : paramtypes) { + if (pt.isPrimitive()) { + if (pt == long.class) { + mv.visitVarInsn(LLOAD, ++varindex); + ++varindex; + } else if (pt == double.class) { + mv.visitVarInsn(DLOAD, ++varindex); + ++varindex; + } else if (pt == float.class) { + mv.visitVarInsn(FLOAD, ++varindex); + } else { + mv.visitVarInsn(ILOAD, ++varindex); + } + } else { + mv.visitVarInsn(ALOAD, ++varindex); + } + } + mv.visitMethodInsn(INVOKESPECIAL, supDynName, method.getName(), methodDesc, false); + if (returnType == void.class) { // end if + mv.visitLabel(l1); + } + if (returnType == void.class) { + } else if (returnType.isPrimitive()) { + if (returnType == long.class) { + mv.visitVarInsn(LSTORE, ++varindex); + ++varindex; //多加1 + } else if (returnType == float.class) { + mv.visitVarInsn(FSTORE, ++varindex); + } else if (returnType == double.class) { + mv.visitVarInsn(DSTORE, ++varindex); + ++varindex; //多加1 + } else { + mv.visitVarInsn(ISTORE, ++varindex); + } + } else { + mv.visitVarInsn(ASTORE, ++varindex); + } + final int rsindex = varindex; // + + //---------------------------if (_client== null) return ---------------------------------- + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_client", clientDesc); + Label ifrt = new Label(); + mv.visitJumpInsn(IFNONNULL, ifrt); + if (returnType == void.class) { + mv.visitInsn(RETURN); + } else if (returnType.isPrimitive()) { + if (returnType == long.class) { + mv.visitVarInsn(LLOAD, rsindex); + mv.visitInsn(LRETURN); + } else if (returnType == float.class) { + mv.visitVarInsn(FLOAD, rsindex); + mv.visitInsn(FRETURN); + } else if (returnType == double.class) { + mv.visitVarInsn(DLOAD, rsindex); + mv.visitInsn(DRETURN); + } else { + mv.visitVarInsn(ILOAD, rsindex); + mv.visitInsn(IRETURN); + } + } else { + mv.visitVarInsn(ALOAD, rsindex); + mv.visitInsn(ARETURN); + } + mv.visitLabel(ifrt); + //------------------------------------------------------------- + mv.visitVarInsn(ALOAD, 0);//调用 _client + mv.visitFieldInsn(GETFIELD, newDynName, "_client", clientDesc); + mv.visitVarInsn(ALOAD, 0); //传递 _convert + mv.visitFieldInsn(GETFIELD, newDynName, "_convert", convertDesc); + mv.visitVarInsn(ALOAD, 0); //传递 _sameGroupTransports + mv.visitFieldInsn(GETFIELD, newDynName, "_sameGroupTransports", transportsDesc); + + mv.visitVarInsn(ILOAD, 2); //传递 cansamerun + + if (index <= 5) { //第几个 SncpAction + mv.visitInsn(ICONST_0 + index); + } else { + mv.visitIntInsn(BIPUSH, index); + } + if (paramtypes.length + 3 <= 5) { //参数总数量 + mv.visitInsn(ICONST_0 + paramtypes.length + 3); + } else { + mv.visitIntInsn(BIPUSH, paramtypes.length + 3); + } + + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + + mv.visitInsn(DUP); + mv.visitInsn(ICONST_0); + mv.visitInsn(ICONST_1); //第一个参数 canselfrun + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + mv.visitInsn(AASTORE); + + mv.visitInsn(DUP); + mv.visitInsn(ICONST_1); + mv.visitInsn(ICONST_0); //第一个参数 cansamerun + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + mv.visitInsn(AASTORE); + + mv.visitInsn(DUP); + mv.visitInsn(ICONST_2); + mv.visitInsn(ICONST_0); //第二个参数 candiffrun + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + mv.visitInsn(AASTORE); + + int insn = 3; + for (int j = 0; j < paramtypes.length; j++) { + final Class pt = paramtypes[j]; + mv.visitInsn(DUP); + insn++; + if (j <= 2) { + mv.visitInsn(ICONST_0 + j + 3); + } else { + mv.visitIntInsn(BIPUSH, j + 3); + } + if (pt.isPrimitive()) { + if (pt == long.class) { + mv.visitVarInsn(LLOAD, insn++); + } else if (pt == float.class) { + mv.visitVarInsn(FLOAD, insn++); + } else if (pt == double.class) { + mv.visitVarInsn(DLOAD, insn++); + } else { + mv.visitVarInsn(ILOAD, insn); + } + Class bigclaz = java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance(pt, 1), 0).getClass(); + mv.visitMethodInsn(INVOKESTATIC, bigclaz.getName().replace('.', '/'), "valueOf", "(" + Type.getDescriptor(pt) + ")" + Type.getDescriptor(bigclaz), false); + } else { + mv.visitVarInsn(ALOAD, insn); + } + mv.visitInsn(AASTORE); + } + mv.visitMethodInsn(INVOKEVIRTUAL, clientName, mrun.async() ? "asyncRemote" : "remote", "(" + convertDesc + transportsDesc + "ZI[Ljava/lang/Object;)V", false); + + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_client", clientDesc); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_convert", convertDesc); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_diffGroupTransports", transportsDesc); + + mv.visitVarInsn(ILOAD, 3); //传递 candiffrun + if (index <= 5) { //第几个 SncpAction + mv.visitInsn(ICONST_0 + index); + } else { + mv.visitIntInsn(BIPUSH, index); + } + if (paramtypes.length + 3 <= 5) { //参数总数量 + mv.visitInsn(ICONST_0 + paramtypes.length + 3); + } else { + mv.visitIntInsn(BIPUSH, paramtypes.length + 3); + } + + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + + mv.visitInsn(DUP); + mv.visitInsn(ICONST_0); + mv.visitInsn(ICONST_1); //第一个参数 cansamerun + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + mv.visitInsn(AASTORE); + + mv.visitInsn(DUP); + mv.visitInsn(ICONST_1); + mv.visitInsn(ICONST_1); //第二个参数 candiffrun + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + mv.visitInsn(AASTORE); + + mv.visitInsn(DUP); + mv.visitInsn(ICONST_2); + mv.visitInsn(ICONST_0); //第二个参数 candiffrun + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + mv.visitInsn(AASTORE); + + insn = 3; + for (int j = 0; j < paramtypes.length; j++) { + final Class pt = paramtypes[j]; + mv.visitInsn(DUP); + insn++; + if (j <= 2) { + mv.visitInsn(ICONST_0 + j + 3); + } else { + mv.visitIntInsn(BIPUSH, j + 3); + } + if (pt.isPrimitive()) { + if (pt == long.class) { + mv.visitVarInsn(LLOAD, insn++); + } else if (pt == float.class) { + mv.visitVarInsn(FLOAD, insn++); + } else if (pt == double.class) { + mv.visitVarInsn(DLOAD, insn++); + } else { + mv.visitVarInsn(ILOAD, insn); + } + Class bigclaz = java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance(pt, 1), 0).getClass(); + mv.visitMethodInsn(INVOKESTATIC, bigclaz.getName().replace('.', '/'), "valueOf", "(" + Type.getDescriptor(pt) + ")" + Type.getDescriptor(bigclaz), false); + } else { + mv.visitVarInsn(ALOAD, insn); + } + mv.visitInsn(AASTORE); + } + mv.visitMethodInsn(INVOKEVIRTUAL, clientName, mrun.async() ? "asyncRemote" : "remote", "(" + convertDesc + transportsDesc + "ZI[Ljava/lang/Object;)V", false); + + if (returnType == void.class) { + mv.visitInsn(RETURN); + } else if (returnType.isPrimitive()) { + if (returnType == long.class) { + mv.visitVarInsn(LLOAD, rsindex); + mv.visitInsn(LRETURN); + } else if (returnType == float.class) { + mv.visitVarInsn(FLOAD, rsindex); + mv.visitInsn(FRETURN); + } else if (returnType == double.class) { + mv.visitVarInsn(DLOAD, rsindex); + mv.visitInsn(DRETURN); + } else { + mv.visitVarInsn(ILOAD, rsindex); + mv.visitInsn(IRETURN); + } + } else { + mv.visitVarInsn(ALOAD, rsindex); + mv.visitInsn(ARETURN); + } + + mv.visitMaxs(Math.max(varindex, 10), varindex + 4); + mv.visitEnd(); + } + } + cw.visitEnd(); + byte[] bytes = cw.toByteArray(); + Class newClazz = new ClassLoader(loader) { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass(newDynName.replace('/', '.'), bytes); + return (Class) newClazz; + } + + private static void visitAnnotation(final AnnotationVisitor av, final Annotation ann) { + try { + for (Method anm : ann.annotationType().getMethods()) { + final String mname = anm.getName(); + if ("equals".equals(mname) || "hashCode".equals(mname) || "toString".equals(mname) || "annotationType".equals(mname)) continue; + final Object r = anm.invoke(ann); + if (r instanceof String[]) { + AnnotationVisitor av1 = av.visitArray(mname); + for (String item : (String[]) r) { + av1.visit(null, item); + } + av1.visitEnd(); + } else if (r instanceof Class[]) { + AnnotationVisitor av1 = av.visitArray(mname); + for (Class item : (Class[]) r) { + av1.visit(null, Type.getType(item)); + } + av1.visitEnd(); + } else if (r instanceof Enum[]) { + AnnotationVisitor av1 = av.visitArray(mname); + for (Enum item : (Enum[]) r) { + av1.visitEnum(null, Type.getDescriptor(item.getClass()), ((Enum) item).name()); + } + av1.visitEnd(); + } else if (r instanceof Annotation[]) { + AnnotationVisitor av1 = av.visitArray(mname); + for (Annotation item : (Annotation[]) r) { + visitAnnotation(av1.visitAnnotation(null, Type.getDescriptor(((Annotation) item).annotationType())), item); + } + av1.visitEnd(); + } else if (r instanceof Class) { + av.visit(mname, Type.getType((Class) r)); + } else if (r instanceof Enum) { + av.visitEnum(mname, Type.getDescriptor(r.getClass()), ((Enum) r).name()); + } else if (r instanceof Annotation) { + visitAnnotation(av.visitAnnotation(null, Type.getDescriptor(((Annotation) r).annotationType())), (Annotation) r); + } else { + av.visit(mname, r); + } + } + av.visitEnd(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * + * 创建本地模式Service实例 + * + * @param + * @param name + * @param executor + * @param serviceClass + * @param clientAddress + * @param groups + * @param sameGroupTransports + * @param diffGroupTransports + * @return + */ + @SuppressWarnings("unchecked") + public static T createLocalService( + final String name, + final Consumer executor, + final Class serviceClass, + final InetSocketAddress clientAddress, HashSet groups, Collection sameGroupTransports, Collection diffGroupTransports + ) { + try { + final Class newClazz = createLocalServiceClass(name, serviceClass); + T rs = (T) newClazz.newInstance(); + //-------------------------------------- + if (sameGroupTransports == null) sameGroupTransports = new ArrayList<>(); + if (diffGroupTransports == null) diffGroupTransports = new ArrayList<>(); + Transport remoteTransport = null; + { + Class loop = newClazz; + String[] groupArray = null; + do { + for (Field field : loop.getDeclaredFields()) { + int mod = field.getModifiers(); + if (Modifier.isFinal(mod) || Modifier.isStatic(mod)) continue; + if (field.getAnnotation(SncpRemote.class) != null) { + field.setAccessible(true); + if (remoteTransport == null) { + List list = new ArrayList<>(); + list.addAll(sameGroupTransports); + list.addAll(diffGroupTransports); + if (!list.isEmpty()) remoteTransport = new Transport(list.get(0), clientAddress, list); + } + if (field.getType().isAssignableFrom(newClazz) && remoteTransport != null) { + field.set(rs, createRemoteService(name, executor, serviceClass, clientAddress, groups, remoteTransport)); + } + continue; + } + Resource res = field.getAnnotation(Resource.class); + if (res == null) continue; + field.setAccessible(true); + if (res.name().equals(RESNAME_SNCP_GROUPS)) { + if (groups == null) groups = new LinkedHashSet<>(); + if (groupArray == null) groupArray = groups.toArray(new String[groups.size()]); + if (field.getGenericType().equals(GROUPS_TYPE1)) { + field.set(rs, groups); + } else if (field.getGenericType().equals(GROUPS_TYPE2)) { + field.set(rs, groupArray); + } + } else if (res.name().endsWith(RESNAME_SNCP_ADDR)) { + if (field.getType() == String.class) { + field.set(rs, clientAddress == null ? null : (clientAddress.getHostString() + ":" + clientAddress.getPort())); + } else { + field.set(rs, clientAddress); + } + } + } + } while ((loop = loop.getSuperclass()) != Object.class); + } + SncpClient client = null; + { + try { + Field e = newClazz.getDeclaredField("_client"); + e.setAccessible(true); + client = new SncpClient(name, executor, hash(serviceClass), false, newClazz, true, clientAddress, groups); + e.set(rs, client); + } catch (NoSuchFieldException ne) { + } + } + { + StringBuilder sb = new StringBuilder(); + sb.append(newClazz.getName()).append("{name = ").append(name); + if (client != null) { + sb.append(", nameid = ").append(client.getNameid()).append(", serviceid = ").append(client.getServiceid()); + sb.append(", action.size = ").append(client.getActionCount()); + + sb.append(", address = ").append(clientAddress).append(", groups = ").append(groups); + List addrs = new ArrayList<>(); + for (Transport t : sameGroupTransports) { + addrs.addAll(Arrays.asList(t.getRemoteAddress())); + } + sb.append(", samegroups = ").append(addrs); + + addrs.clear(); + for (Transport t : diffGroupTransports) { + addrs.addAll(Arrays.asList(t.getRemoteAddress())); + } + sb.append(", diffgroups = ").append(addrs); + } else { + sb.append(", ").append(MultiRun.class.getSimpleName().toLowerCase()).append(" = false"); + } + sb.append("}"); + Field s = newClazz.getDeclaredField("_selfstring"); + s.setAccessible(true); + s.set(rs, sb.toString()); + } + if (client == null) return rs; + { + Field c = newClazz.getDeclaredField("_sameGroupTransports"); + c.setAccessible(true); + c.set(rs, sameGroupTransports.toArray(new Transport[sameGroupTransports.size()])); + } + { + Field t = newClazz.getDeclaredField("_diffGroupTransports"); + t.setAccessible(true); + t.set(rs, diffGroupTransports.toArray(new Transport[diffGroupTransports.size()])); + } + return rs; + } catch (RuntimeException rex) { + throw rex; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + } + + /** + * public final class _DynRemoteTestService extends TestService{ + * + * @Resource + * private BsonConvert _convert; + * + * private Transport _transport; + * + * private SncpClient _client; + * + * private String _selfstring; + * + * @Override + * public final String name() { + * return ""; + * } + * + * @Override + * public String toString() { + * return _selfstring == null ? super.toString() : _selfstring; + * } + * + * @Override + * public boolean testChange(TestBean bean) { + * return _client.remote(_convert, _transport, 0, bean); + * } + * + * @Override + * public TestBean findTestBean(long id) { + * return _client.remote(_convert, _transport, 1, id); + * } + * + * @Override + * public void runTestBean(long id, TestBean bean) { + * _client.remote(_convert, _transport, 2, id, bean); + * } + * } + * + * 创建远程模式的Service实例 + *

+ * @param + * @param name + * @param executor + * @param serviceClass + * @param clientAddress + * @param groups + * @param transport + * @return + */ + @SuppressWarnings("unchecked") + public static T createRemoteService( + final String name, + final Consumer executor, + final Class serviceClass, + final InetSocketAddress clientAddress, HashSet groups, final Transport transport + ) { + if (serviceClass == null) return null; + if (!Service.class.isAssignableFrom(serviceClass)) return null; + int mod = serviceClass.getModifiers(); + if (!java.lang.reflect.Modifier.isPublic(mod)) return null; + if (java.lang.reflect.Modifier.isAbstract(mod)) return null; + final String supDynName = serviceClass.getName().replace('.', '/'); + final String clientName = SncpClient.class.getName().replace('.', '/'); + final String clientDesc = Type.getDescriptor(SncpClient.class); + final String sncpDynDesc = Type.getDescriptor(SncpDyn.class); + final String convertDesc = Type.getDescriptor(BsonConvert.class); + final String transportDesc = Type.getDescriptor(Transport.class); + final String anyValueDesc = Type.getDescriptor(AnyValue.class); + ClassLoader loader = Sncp.class.getClassLoader(); + String newDynName = supDynName.substring(0, supDynName.lastIndexOf('/') + 1) + REMOTEPREFIX + serviceClass.getSimpleName(); + final SncpClient client = new SncpClient(name, executor, hash(serviceClass), true, createLocalServiceClass(name, serviceClass), false, clientAddress, groups); + try { + Class newClazz = Class.forName(newDynName.replace('/', '.')); + T rs = (T) newClazz.newInstance(); + Field c = newClazz.getDeclaredField("_client"); + c.setAccessible(true); + c.set(rs, client); + Field t = newClazz.getDeclaredField("_transport"); + t.setAccessible(true); + t.set(rs, transport); + { + StringBuilder sb = new StringBuilder(); + sb.append(newClazz.getName()).append("{name = ").append(name); + sb.append(", nameid = ").append(client.getNameid()).append(", serviceid = ").append(client.getServiceid()); + sb.append(", action.size = ").append(client.getActionCount()); + sb.append(", address = ").append(clientAddress).append(", groups = ").append(groups); + sb.append(", remotes = ").append(transport == null ? null : Arrays.asList(transport.getRemoteAddress())); + sb.append("}"); + Field s = newClazz.getDeclaredField("_selfstring"); + s.setAccessible(true); + s.set(rs, sb.toString()); + } + return rs; + } catch (Exception ex) { + } + //------------------------------------------------------------------------------ + ClassWriter cw = new ClassWriter(COMPUTE_FRAMES); + FieldVisitor fv; + DebugMethodVisitor mv; + AnnotationVisitor av0; + + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, supDynName, null); + { + av0 = cw.visitAnnotation(sncpDynDesc, true); + av0.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE, "_convert", convertDesc, null, null); + av0 = fv.visitAnnotation("Ljavax/annotation/Resource;", true); + av0.visitEnd(); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE, "_transport", transportDesc, null, null); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE, "_client", clientDesc, null, null); + fv.visitEnd(); + } + { + fv = cw.visitField(ACC_PRIVATE, "_selfstring", "Ljava/lang/String;", null, null); + fv.visitEnd(); + } + { //构造函数 + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC, "", "()V", null, null)); + //mv.setDebug(true); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, supDynName, "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { //init + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC, "init", "(" + anyValueDesc + ")V", null, null)); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 2); + mv.visitEnd(); + } + { //destroy + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC, "destroy", "(" + anyValueDesc + ")V", null, null)); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 2); + mv.visitEnd(); + } + { // name() + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "name", "()Ljava/lang/String;", null, null)); + mv.visitLdcInsn(name); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { // toString() + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null)); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_selfstring", "Ljava/lang/String;"); + Label l1 = new Label(); + mv.visitJumpInsn(IFNONNULL, l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false); + Label l2 = new Label(); + mv.visitJumpInsn(GOTO, l2); + mv.visitLabel(l1); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_selfstring", "Ljava/lang/String;"); + mv.visitLabel(l2); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + int i = -1; + for (final SncpAction entry : client.actions) { + final int index = ++i; + final java.lang.reflect.Method method = entry.method; + { + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method), null, null)); + //mv.setDebug(true); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_client", clientDesc); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_convert", convertDesc); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_transport", transportDesc); + if (index <= 5) { + mv.visitInsn(ICONST_0 + index); + } else { + mv.visitIntInsn(BIPUSH, index); + } + + { //传参数 + int paramlen = entry.paramTypes.length; + if (paramlen <= 5) { + mv.visitInsn(ICONST_0 + paramlen); + } else { + mv.visitIntInsn(BIPUSH, paramlen); + } + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + java.lang.reflect.Type[] paramtypes = entry.paramTypes; + int insn = 0; + for (int j = 0; j < paramtypes.length; j++) { + final java.lang.reflect.Type pt = paramtypes[j]; + mv.visitInsn(DUP); + insn++; + if (j <= 5) { + mv.visitInsn(ICONST_0 + j); + } else { + mv.visitIntInsn(BIPUSH, j); + } + if (pt instanceof Class && ((Class) pt).isPrimitive()) { + if (pt == long.class) { + mv.visitVarInsn(LLOAD, insn++); + } else if (pt == float.class) { + mv.visitVarInsn(FLOAD, insn++); + } else if (pt == double.class) { + mv.visitVarInsn(DLOAD, insn++); + } else { + mv.visitVarInsn(ILOAD, insn); + } + Class bigclaz = java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance((Class) pt, 1), 0).getClass(); + mv.visitMethodInsn(INVOKESTATIC, bigclaz.getName().replace('.', '/'), "valueOf", "(" + Type.getDescriptor((Class) pt) + ")" + Type.getDescriptor(bigclaz), false); + } else { + mv.visitVarInsn(ALOAD, insn); + } + mv.visitInsn(AASTORE); + } + } + + mv.visitMethodInsn(INVOKEVIRTUAL, clientName, "remote", "(" + convertDesc + transportDesc + "I[Ljava/lang/Object;)Ljava/lang/Object;", false); + //mv.visitMethodInsn(INVOKEVIRTUAL, convertName, "convertFrom", convertFromDesc, false); + if (method.getGenericReturnType() == void.class) { + mv.visitInsn(POP); + mv.visitInsn(RETURN); + } else { + Class returnclz = method.getReturnType(); + Class bigPrimitiveClass = returnclz.isPrimitive() ? java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance(returnclz, 1), 0).getClass() : returnclz; + mv.visitTypeInsn(CHECKCAST, (returnclz.isPrimitive() ? bigPrimitiveClass : returnclz).getName().replace('.', '/')); + if (returnclz.isPrimitive()) { + String bigPrimitiveName = bigPrimitiveClass.getName().replace('.', '/'); + try { + java.lang.reflect.Method pm = bigPrimitiveClass.getMethod(returnclz.getSimpleName() + "Value"); + mv.visitMethodInsn(INVOKEVIRTUAL, bigPrimitiveName, pm.getName(), Type.getMethodDescriptor(pm), false); + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + if (returnclz == long.class) { + mv.visitInsn(LRETURN); + } else if (returnclz == float.class) { + mv.visitInsn(FRETURN); + } else if (returnclz == double.class) { + mv.visitInsn(DRETURN); + } else { + mv.visitInsn(IRETURN); + } + } else { + mv.visitInsn(ARETURN); + } + } + mv.visitMaxs(20, 20); + mv.visitEnd(); + } + } + cw.visitEnd(); + byte[] bytes = cw.toByteArray(); + Class newClazz = new ClassLoader(loader) { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass(newDynName.replace('/', '.'), bytes); + try { + T rs = (T) newClazz.newInstance(); + Field c = newClazz.getDeclaredField("_client"); + c.setAccessible(true); + c.set(rs, client); + Field t = newClazz.getDeclaredField("_transport"); + t.setAccessible(true); + t.set(rs, transport); + { + StringBuilder sb = new StringBuilder(); + sb.append(newClazz.getName()).append("{name = ").append(name); + sb.append(", nameid = ").append(client.getNameid()).append(", serviceid = ").append(client.getServiceid()); + sb.append(", action.size = ").append(client.getActionCount()); + sb.append(", address = ").append(clientAddress).append(", groups = ").append(groups); + sb.append(", remotes = ").append(transport == null ? null : Arrays.asList(transport.getRemoteAddress())); + sb.append("}"); + Field s = newClazz.getDeclaredField("_selfstring"); + s.setAccessible(true); + s.set(rs, sb.toString()); + } + return rs; + } catch (Exception ex) { + throw new RuntimeException(ex); + } + + } +} diff --git a/src/org/redkale/net/sncp/SncpCall.java b/src/org/redkale/net/sncp/SncpCall.java new file mode 100644 index 000000000..ec4be5865 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpCall.java @@ -0,0 +1,24 @@ +/* + * 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.net.sncp; + +import java.lang.annotation.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import org.redkale.util.*; + +/** + * 参数回写, 当Service的方法需要更改参数对象内部的数据时,需要使用SncpCall + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({ElementType.PARAMETER}) +@Retention(RUNTIME) +public @interface SncpCall { + + Class value(); +} diff --git a/src/org/redkale/net/sncp/SncpClient.java b/src/org/redkale/net/sncp/SncpClient.java new file mode 100644 index 000000000..d55a43e75 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpClient.java @@ -0,0 +1,406 @@ +/* + * 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.net.sncp; + +import static org.redkale.net.sncp.SncpRequest.*; +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.function.*; +import java.util.logging.*; +import org.redkale.convert.bson.*; +import org.redkale.convert.json.*; +import org.redkale.net.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class SncpClient { + + protected static final class SncpAction { + + protected final DLong actionid; + + protected final Method method; + + protected final Type resultTypes; //void 必须设为 null + + protected final Type[] paramTypes; + + protected final Attribute[] paramAttrs; // 为null表示无SncpCall处理,index=0固定为null, 其他为参数标记的SncpCall回调方法 + + protected final int addressParamIndex; + + public SncpAction(Method method, DLong actionid) { + this.actionid = actionid; + Type rt = method.getGenericReturnType(); + if (rt instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) rt; + if (tv.getBounds().length == 1) rt = tv.getBounds()[0]; + } + this.resultTypes = rt == void.class ? null : rt; + this.paramTypes = method.getGenericParameterTypes(); + this.method = method; + Annotation[][] anns = method.getParameterAnnotations(); + int addrIndex = -1; + boolean hasattr = false; + Attribute[] atts = new Attribute[paramTypes.length + 1]; + if (anns.length > 0) { + Class[] params = method.getParameterTypes(); + for (int i = 0; i < anns.length; i++) { + if (anns[i].length > 0) { + for (Annotation ann : anns[i]) { + if (ann.annotationType() == SncpTargetAddress.class && SocketAddress.class.isAssignableFrom(params[i])) { + addrIndex = i; + break; + } + } + for (Annotation ann : anns[i]) { + if (ann.annotationType() == SncpCall.class) { + try { + atts[i + 1] = ((SncpCall) ann).value().newInstance(); + hasattr = true; + } catch (Exception e) { + logger.log(Level.SEVERE, SncpCall.class.getSimpleName() + ".attribute cannot a newInstance for" + method, e); + } + break; + } + } + } + } + } + this.addressParamIndex = addrIndex; + this.paramAttrs = hasattr ? atts : null; + } + + @Override + public String toString() { + return "{" + actionid + "," + (method == null ? "null" : method.getName()) + "}"; + } + } + + private static final Logger logger = Logger.getLogger(SncpClient.class.getSimpleName()); + + private final boolean finest = logger.isLoggable(Level.FINEST); + + protected final JsonConvert jsonConvert = JsonFactory.root().getConvert(); + + protected final String name; + + protected final boolean remote; + + private final Class serviceClass; + + protected final InetSocketAddress address; + + protected final HashSet groups; + + private final byte[] addrBytes; + + private final int addrPort; + + protected final long nameid; + + protected final long serviceid; + + protected final SncpAction[] actions; + + protected final Consumer executor; + + public SncpClient(final String serviceName, final Consumer executor, final long serviceid0, boolean remote, final Class serviceClass, + boolean onlySncpDyn, final InetSocketAddress clientAddress, final HashSet groups) { + if (serviceName.length() > 10) throw new RuntimeException(serviceClass + " @Resource name(" + serviceName + ") too long , must less 11"); + this.remote = remote; + this.executor = executor; + this.serviceClass = serviceClass; + this.address = clientAddress; + this.groups = groups; + //if (subLocalClass != null && !serviceClass.isAssignableFrom(subLocalClass)) throw new RuntimeException(subLocalClass + " is not " + serviceClass + " sub class "); + this.name = serviceName; + this.nameid = Sncp.hash(serviceName); + this.serviceid = serviceid0 > 0 ? serviceid0 : Sncp.hash(serviceClass); + final List methodens = new ArrayList<>(); + //------------------------------------------------------------------------------ + for (java.lang.reflect.Method method : parseMethod(serviceClass, onlySncpDyn)) { + SncpAction en = new SncpAction(method, Sncp.hash(method)); + methodens.add(en); + } + this.actions = methodens.toArray(new SncpAction[methodens.size()]); + this.addrBytes = clientAddress == null ? new byte[4] : clientAddress.getAddress().getAddress(); + this.addrPort = clientAddress == null ? 0 : clientAddress.getPort(); + } + + public long getNameid() { + return nameid; + } + + public long getServiceid() { + return serviceid; + } + + public int getActionCount() { + return actions.length; + } + + @Override + public String toString() { + String service = serviceClass.getName(); + if (remote) service = service.replace(Sncp.LOCALPREFIX, Sncp.REMOTEPREFIX); + return this.getClass().getSimpleName() + "(service = " + service + ", serviceid = " + serviceid + + ", name = " + name + ", nameid = " + nameid + ", address = " + (address == null ? "" : (address.getHostString() + ":" + address.getPort())) + + ", groups = " + groups + ", actions.size = " + actions.length + ")"; + } + + public static List parseMethod(final Class serviceClass, boolean onlySncpDyn) { + final List list = new ArrayList<>(); + final List multis = new ArrayList<>(); + final Map actionids = new HashMap<>(); + + for (final java.lang.reflect.Method method : serviceClass.getMethods()) { + if (method.isSynthetic()) continue; + final int mod = method.getModifiers(); + if (Modifier.isStatic(mod)) continue; + if (Modifier.isFinal(mod)) continue; + if (method.getName().equals("getClass") || method.getName().equals("toString")) continue; + if (method.getName().equals("equals") || method.getName().equals("hashCode")) continue; + if (method.getName().equals("notify") || method.getName().equals("notifyAll") || method.getName().equals("wait")) continue; + if (method.getName().equals("init") || method.getName().equals("destroy") || method.getName().equals("name")) continue; + if (onlySncpDyn && method.getAnnotation(SncpDyn.class) == null) continue; + DLong actionid = Sncp.hash(method); + Method old = actionids.get(actionid); + if (old != null) { + if (old.getDeclaringClass().equals(method.getDeclaringClass())) + throw new RuntimeException(serviceClass.getName() + " have one more same action(Method=" + method + ", " + old + ", actionid=" + actionid + ")"); + continue; + } + actionids.put(actionid, method); + if (method.getAnnotation(SncpDyn.class) != null) { + multis.add(method); + } else { + list.add(method); + } + } + list.addAll(multis); + if (onlySncpDyn && list.size() > 1) { + list.sort((m1, m2) -> m1.getAnnotation(SncpDyn.class).index() - m2.getAnnotation(SncpDyn.class).index()); + } + return list; + } + + public T remote(final BsonConvert convert, Transport transport, final int index, final Object... params) { + Future future = remote(convert, transport, actions[index], params); + final BsonReader in = convert.pollBsonReader(); + try { + final SncpAction action = actions[index]; + in.setBytes(future.get(5, TimeUnit.SECONDS)); + byte i; + while ((i = in.readByte()) != 0) { + final Attribute attr = action.paramAttrs[i]; + attr.set(params[i - 1], convert.convertFrom(in, attr.type())); + } + return convert.convertFrom(in, action.resultTypes); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + logger.log(Level.SEVERE, actions[index].method + " sncp (params: " + jsonConvert.convertTo(params) + ") remote error", e); + throw new RuntimeException(actions[index].method + " sncp remote error", e); + } finally { + convert.offerBsonReader(in); + } + } + + public void remote(final BsonConvert convert, Transport[] transports, boolean run, final int index, final Object... params) { + if (!run || transports == null || transports.length < 1) return; + remote(convert, transports[0], index, params); + for (int i = 1; i < transports.length; i++) { + remote(convert, transports[i], actions[index], params); + } + } + + public void asyncRemote(final BsonConvert convert, Transport[] transports, boolean run, final int index, final Object... params) { + if (!run || transports == null || transports.length < 1) return; + if (executor != null) { + executor.accept(() -> { + remote(convert, transports[0], index, params); + for (int i = 1; i < transports.length; i++) { + remote(convert, transports[i], actions[index], params); + } + }); + } else { + remote(convert, transports[0], index, params); + for (int i = 1; i < transports.length; i++) { + remote(convert, transports[i], actions[index], params); + } + } + } + + private Future remote(final BsonConvert convert, final Transport transport, final SncpAction action, final Object... params) { + Type[] myparamtypes = action.paramTypes; + final BsonWriter writer = convert.pollBsonWriter(transport.getBufferSupplier()); // 将head写入 + writer.writeTo(DEFAULT_HEADER); + for (int i = 0; i < params.length; i++) { + convert.convertTo(writer, myparamtypes[i], params[i]); + } + final int reqBodyLength = writer.count() - HEADER_SIZE; //body总长度 + final long seqid = System.nanoTime(); + final DLong actionid = action.actionid; + final SocketAddress addr = action.addressParamIndex >= 0 ? (SocketAddress) params[action.addressParamIndex] : null; + final AsyncConnection conn = transport.pollConnection(addr); + if (conn == null || !conn.isOpen()) { + logger.log(Level.SEVERE, action.method + " sncp (params: " + jsonConvert.convertTo(params) + ") cannot connect " + (conn == null ? addr : conn.getRemoteAddress())); + throw new RuntimeException("sncp " + (conn == null ? addr : conn.getRemoteAddress()) + " cannot connect"); + } + final ByteBuffer[] sendBuffers = writer.toBuffers(); + fillHeader(sendBuffers[0], seqid, actionid, reqBodyLength, 0, reqBodyLength); + + final ByteBuffer buffer = transport.pollBuffer(); + final SncpFuture future = new SncpFuture(); + conn.write(sendBuffers, sendBuffers, new CompletionHandler() { + + @Override + public void completed(Integer result, ByteBuffer[] attachments) { + int index = -1; + for (int i = 0; i < attachments.length; i++) { + if (attachments[i].hasRemaining()) { + index = i; + break; + } else { + transport.offerBuffer(attachments[i]); + } + } + if (index == 0) { + conn.write(attachments, attachments, this); + return; + } else if (index > 0) { + ByteBuffer[] newattachs = new ByteBuffer[attachments.length - index]; + System.arraycopy(attachments, index, newattachs, 0, newattachs.length); + conn.write(newattachs, newattachs, this); + return; + } + //----------------------- 读取返回结果 ------------------------------------- + buffer.clear(); + conn.read(buffer, null, new CompletionHandler() { + + private byte[] body; + + private int received; + + @Override + public void completed(Integer count, Void attachment2) { + if (count < 1 && buffer.remaining() == buffer.limit()) { //没有数据可读 + future.set(new RuntimeException(action.method + " sncp[" + conn.getRemoteAddress() + "] remote no response data")); + transport.offerBuffer(buffer); + transport.offerConnection(true, conn); + return; + } + if (received < 1 && buffer.limit() < buffer.remaining() + HEADER_SIZE) { //header都没读全 + conn.read(buffer, attachment2, this); + return; + } + buffer.flip(); + if (received > 0) { + int offset = this.received; + this.received += buffer.remaining(); + buffer.get(body, offset, Math.min(buffer.remaining(), this.body.length - offset)); + if (this.received < this.body.length) {// 数据仍然不全,需要继续读取 + buffer.clear(); + conn.read(buffer, attachment2, this); + } else { + success(); + } + return; + } + checkResult(seqid, action, buffer); + + final int respBodyLength = buffer.getInt(); + buffer.getInt(); // bodyOffset + buffer.getInt(); // frameLength + final int retcode = buffer.getInt(); + if (retcode != 0) { + logger.log(Level.SEVERE, action.method + " sncp (params: " + jsonConvert.convertTo(params) + ") deal error (retcode=" + retcode + ", retinfo=" + SncpResponse.getRetCodeInfo(retcode) + ")"); + throw new RuntimeException("remote service(" + action.method + ") deal error (retcode=" + retcode + ", retinfo=" + SncpResponse.getRetCodeInfo(retcode) + ")"); + } + + if (respBodyLength > buffer.remaining()) { // 数据不全,需要继续读取 + this.body = new byte[respBodyLength]; + this.received = buffer.remaining(); + buffer.get(body, 0, this.received); + buffer.clear(); + conn.read(buffer, attachment2, this); + } else { + this.body = new byte[respBodyLength]; + buffer.get(body, 0, respBodyLength); + success(); + } + } + + public void success() { + future.set(this.body); + transport.offerBuffer(buffer); + transport.offerConnection(false, conn); + } + + @Override + public void failed(Throwable exc, Void attachment2) { + logger.log(Level.SEVERE, action.method + " sncp (params: " + jsonConvert.convertTo(params) + ") remote read exec failed", exc); + future.set(new RuntimeException(action.method + " sncp remote exec failed")); + transport.offerBuffer(buffer); + transport.offerConnection(true, conn); + } + }); + } + + @Override + public void failed(Throwable exc, ByteBuffer[] attachment) { + logger.log(Level.SEVERE, action.method + " sncp (params: " + jsonConvert.convertTo(params) + ") remote write exec failed", exc); + transport.offerBuffer(buffer); + transport.offerConnection(true, conn); + } + }); + return future; + } + + private void checkResult(long seqid, final SncpAction action, ByteBuffer buffer) { + long rseqid = buffer.getLong(); + if (rseqid != seqid) throw new RuntimeException("sncp(" + action.method + ") response.seqid = " + seqid + ", but request.seqid =" + rseqid); + if (buffer.getChar() != HEADER_SIZE) throw new RuntimeException("sncp(" + action.method + ") buffer receive header.length not " + HEADER_SIZE); + long rserviceid = buffer.getLong(); + if (rserviceid != serviceid) throw new RuntimeException("sncp(" + action.method + ") response.serviceid = " + serviceid + ", but request.serviceid =" + rserviceid); + long rnameid = buffer.getLong(); + if (rnameid != nameid) throw new RuntimeException("sncp(" + action.method + ") response.nameid = " + nameid + ", but receive nameid =" + rnameid); + long ractionid1 = buffer.getLong(); + long ractionid2 = buffer.getLong(); + if (!action.actionid.equals(ractionid1, ractionid2)) throw new RuntimeException("sncp(" + action.method + ") response.actionid = " + action.actionid + ", but request.actionid =(" + ractionid1 + "_" + ractionid2 + ")"); + buffer.getInt(); //地址 + buffer.getChar(); //端口 + } + + private void fillHeader(ByteBuffer buffer, long seqid, DLong actionid, int bodyLength, int bodyOffset, int frameLength) { + //---------------------head---------------------------------- + final int currentpos = buffer.position(); + buffer.position(0); + buffer.putLong(seqid); //序列号 + buffer.putChar((char) HEADER_SIZE); //header长度 + buffer.putLong(this.serviceid); + buffer.putLong(this.nameid); + buffer.putLong(actionid.getFirst()); + buffer.putLong(actionid.getSecond()); + buffer.put(addrBytes[0]); + buffer.put(addrBytes[1]); + buffer.put(addrBytes[2]); + buffer.put(addrBytes[3]); + buffer.putChar((char) this.addrPort); + buffer.putInt(bodyLength); //body长度 + buffer.putInt(bodyOffset); + buffer.putInt(frameLength); //一帧数据的长度 + buffer.putInt(0); //结果码, 请求方固定传0 + buffer.position(currentpos); + } +} diff --git a/src/org/redkale/net/sncp/SncpDyn.java b/src/org/redkale/net/sncp/SncpDyn.java new file mode 100644 index 000000000..9b3ad87a4 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpDyn.java @@ -0,0 +1,24 @@ +/* + * 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.net.sncp; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 修饰由SNCP协议动态生成的class、和method + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({METHOD, TYPE}) +@Retention(RUNTIME) +public @interface SncpDyn { + + int index() default 0; //排列顺序, 一般用于Method +} diff --git a/src/org/redkale/net/sncp/SncpDynServlet.java b/src/org/redkale/net/sncp/SncpDynServlet.java new file mode 100644 index 000000000..da940fe7e --- /dev/null +++ b/src/org/redkale/net/sncp/SncpDynServlet.java @@ -0,0 +1,407 @@ +/* + * 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.net.sncp; + +import static org.redkale.net.sncp.SncpRequest.DEFAULT_HEADER; +import java.io.*; +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.nio.*; +import java.util.*; +import java.util.function.*; +import java.util.logging.*; +import javax.annotation.*; +import jdk.internal.org.objectweb.asm.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import jdk.internal.org.objectweb.asm.Type; +import org.redkale.convert.bson.*; +import org.redkale.service.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class SncpDynServlet extends SncpServlet { + + private static final Logger logger = Logger.getLogger(SncpDynServlet.class.getSimpleName()); + + private final boolean finest = logger.isLoggable(Level.FINEST); + + private final Class type; + + private final String serviceName; + + private final long nameid; + + private final long serviceid; + + private final HashMap actions = new HashMap<>(); + + private Supplier bufferSupplier; + + public SncpDynServlet(final BsonConvert convert, final String serviceName, final Service service, final AnyValue conf) { + this.conf = conf; + this.serviceName = serviceName; + this.type = (Class) service.getClass().getSuperclass(); + this.nameid = Sncp.hash(serviceName); + this.serviceid = Sncp.hash(type); + Set actionids = new HashSet<>(); + for (java.lang.reflect.Method method : service.getClass().getMethods()) { + if (method.isSynthetic()) continue; + if (Modifier.isStatic(method.getModifiers())) continue; + if (Modifier.isFinal(method.getModifiers())) continue; + if (method.getName().equals("getClass") || method.getName().equals("toString")) continue; + if (method.getName().equals("equals") || method.getName().equals("hashCode")) continue; + if (method.getName().equals("notify") || method.getName().equals("notifyAll") || method.getName().equals("wait")) continue; + if (method.getName().equals("init") || method.getName().equals("destroy") || method.getName().equals("name")) continue; + final DLong actionid = Sncp.hash(method); + SncpServletAction action = SncpServletAction.create(service, actionid, method); + action.convert = convert; + if (actionids.contains(actionid)) { + throw new RuntimeException(type.getName() + " have action(Method=" + method + ", actionid=" + actionid + ") same to (" + actions.get(actionid).method + ")"); + } + actions.put(actionid, action); + actionids.add(actionid); + } + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "(type=" + type.getName() + ", serviceid=" + serviceid + ", actions.size=" + actions.size() + ", name=" + serviceName + ")"; + } + + @Override + public long getNameid() { + return nameid; + } + + @Override + public long getServiceid() { + return serviceid; + } + + @Override + public void execute(SncpRequest request, SncpResponse response) throws IOException { + if (bufferSupplier == null) { + bufferSupplier = request.getContext().getBufferSupplier(); + } + SncpServletAction action = actions.get(request.getActionid()); + //if (finest) logger.log(Level.FINEST, "sncpdyn.execute: " + request + ", " + (action == null ? "null" : action.method)); + if (action == null) { + response.finish(SncpResponse.RETCODE_ILLACTIONID, null); //无效actionid + } else { + BsonWriter out = action.convert.pollBsonWriter(bufferSupplier); + out.writeTo(DEFAULT_HEADER); + BsonReader in = action.convert.pollBsonReader(); + try { + in.setBytes(request.getBody()); + action.action(in, out); + response.finish(0, out); + } catch (Throwable t) { + response.getContext().getLogger().log(Level.INFO, "sncp execute error(" + request + ")", t); + response.finish(SncpResponse.RETCODE_THROWEXCEPTION, null); + } finally { + action.convert.offerBsonReader(in); + action.convert.offerBsonWriter(out); + } + } + } + + public static abstract class SncpServletAction { + + public Method method; + + @Resource + protected BsonConvert convert; + + protected org.redkale.util.Attribute[] paramAttrs; // 为null表示无SncpCall处理,index=0固定为null, 其他为参数标记的SncpCall回调方法 + + protected java.lang.reflect.Type[] paramTypes; //index=0表示返回参数的type, void的返回参数类型为null + + public abstract void action(final BsonReader in, final BsonWriter out) throws Throwable; + + public final void callParameter(final BsonWriter out, final Object... params) { + if (paramAttrs != null) { + for (int i = 1; i < paramAttrs.length; i++) { + org.redkale.util.Attribute attr = paramAttrs[i]; + if (attr == null) continue; + out.writeByte((byte) i); + convert.convertTo(out, attr.type(), attr.get(params[i - 1])); + } + } + out.writeByte((byte) 0); + } + + /** ** + * + * public class TestService implements Service { + * public boolean change(TestBean bean, String name, int id) { + * + * } + * } + * + * public class DynActionTestService_change extends SncpServletAction { + * + * public TestService service; + * + * @Override + * public void action(final BsonReader in, final BsonWriter out) throws Throwable { + * TestBean arg1 = convert.convertFrom(in, paramTypes[1]); + * String arg2 = convert.convertFrom(in, paramTypes[2]); + * int arg3 = convert.convertFrom(in, paramTypes[3]); + * Object rs = service.change(arg1, arg2, arg3); + * callParameter(out, arg1, arg2, arg3); + * convert.convertTo(out, paramTypes[0], rs); + * } + * } + */ + /** + * + * @param service + * @param actionid + * @param method + * @return + */ + @SuppressWarnings("unchecked") + public static SncpServletAction create(final Service service, final DLong actionid, final Method method) { + final Class serviceClass = service.getClass(); + final String supDynName = SncpServletAction.class.getName().replace('.', '/'); + final String serviceName = serviceClass.getName().replace('.', '/'); + final String convertName = BsonConvert.class.getName().replace('.', '/'); + final String convertReaderDesc = Type.getDescriptor(BsonReader.class); + final String convertWriterDesc = Type.getDescriptor(BsonWriter.class); + final String serviceDesc = Type.getDescriptor(serviceClass); + String newDynName = serviceName.substring(0, serviceName.lastIndexOf('/') + 1) + + "DynAction" + serviceClass.getSimpleName() + "_" + method.getName() + "_" + actionid; + while (true) { + try { + Class.forName(newDynName.replace('/', '.')); + newDynName += "_"; + } catch (Exception ex) { + break; + } + } + //------------------------------------------------------------- + ClassWriter cw = new ClassWriter(0); + FieldVisitor fv; + DebugMethodVisitor mv; + + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, supDynName, null); + + { + { + fv = cw.visitField(ACC_PUBLIC, "service", serviceDesc, null, null); + fv.visitEnd(); + } + fv.visitEnd(); + } + { // constructor方法 + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC, "", "()V", null, null)); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, supDynName, "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + String convertFromDesc = "(" + convertReaderDesc + "Ljava/lang/reflect/Type;)Ljava/lang/Object;"; + try { + convertFromDesc = Type.getMethodDescriptor(BsonConvert.class.getMethod("convertFrom", BsonReader.class, java.lang.reflect.Type.class)); + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + { // action方法 + mv = new DebugMethodVisitor(cw.visitMethod(ACC_PUBLIC, "action", "(" + convertReaderDesc + convertWriterDesc + ")V", null, new String[]{"java/lang/Throwable"})); + //mv.setDebug(true); + int iconst = ICONST_1; + int intconst = 1; + int store = 3; //action的参数个数+1 + final Class[] paramClasses = method.getParameterTypes(); + int[][] codes = new int[paramClasses.length][2]; + for (int i = 0; i < paramClasses.length; i++) { //参数 + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "convert", Type.getDescriptor(BsonConvert.class)); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "paramTypes", "[Ljava/lang/reflect/Type;"); + if (iconst > ICONST_5) { + mv.visitIntInsn(BIPUSH, intconst); + } else { + mv.visitInsn(iconst); // + } + mv.visitInsn(AALOAD); + + mv.visitMethodInsn(INVOKEVIRTUAL, convertName, "convertFrom", convertFromDesc, false); + int load = ALOAD; + int v = 0; + if (paramClasses[i].isPrimitive()) { + int storecode = ISTORE; + load = ILOAD; + if (paramClasses[i] == long.class) { + storecode = LSTORE; + load = LLOAD; + v = 1; + } else if (paramClasses[i] == float.class) { + storecode = FSTORE; + load = FLOAD; + v = 1; + } else if (paramClasses[i] == double.class) { + storecode = DSTORE; + load = DLOAD; + v = 1; + } + Class bigPrimitiveClass = Array.get(Array.newInstance(paramClasses[i], 1), 0).getClass(); + String bigPrimitiveName = bigPrimitiveClass.getName().replace('.', '/'); + try { + Method pm = bigPrimitiveClass.getMethod(paramClasses[i].getSimpleName() + "Value"); + mv.visitTypeInsn(CHECKCAST, bigPrimitiveName); + mv.visitMethodInsn(INVOKEVIRTUAL, bigPrimitiveName, pm.getName(), Type.getMethodDescriptor(pm), false); + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + mv.visitVarInsn(storecode, store); + } else { + mv.visitTypeInsn(CHECKCAST, paramClasses[i].getName().replace('.', '/')); + mv.visitVarInsn(ASTORE, store); // + } + codes[i] = new int[]{load, store}; + store += v; + iconst++; + intconst++; + store++; + } + { //调用service + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "service", serviceDesc); + for (int[] j : codes) { + mv.visitVarInsn(j[0], j[1]); + } + mv.visitMethodInsn(INVOKEVIRTUAL, serviceName, method.getName(), Type.getMethodDescriptor(method), false); + } + + final Class returnClass = method.getReturnType(); + if (returnClass != void.class) { + if (returnClass.isPrimitive()) { + Class bigClass = Array.get(Array.newInstance(returnClass, 1), 0).getClass(); + try { + Method vo = bigClass.getMethod("valueOf", returnClass); + mv.visitMethodInsn(INVOKESTATIC, bigClass.getName().replace('.', '/'), vo.getName(), Type.getMethodDescriptor(vo), false); + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + } + mv.visitVarInsn(ASTORE, store); //11 + } + //------------------------- callParameter 方法 -------------------------------- + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 2); + if (paramClasses.length <= 5) { //参数总数量 + mv.visitInsn(ICONST_0 + paramClasses.length); + } else { + mv.visitIntInsn(BIPUSH, paramClasses.length); + } + mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); + int insn = 2; + for (int j = 0; j < paramClasses.length; j++) { + final Class pt = paramClasses[j]; + mv.visitInsn(DUP); + insn++; + if (j <= 5) { + mv.visitInsn(ICONST_0 + j); + } else { + mv.visitIntInsn(BIPUSH, j); + } + if (pt.isPrimitive()) { + if (pt == long.class) { + mv.visitVarInsn(LLOAD, insn++); + } else if (pt == float.class) { + mv.visitVarInsn(FLOAD, insn++); + } else if (pt == double.class) { + mv.visitVarInsn(DLOAD, insn++); + } else { + mv.visitVarInsn(ILOAD, insn); + } + Class bigclaz = java.lang.reflect.Array.get(java.lang.reflect.Array.newInstance(pt, 1), 0).getClass(); + mv.visitMethodInsn(INVOKESTATIC, bigclaz.getName().replace('.', '/'), "valueOf", "(" + Type.getDescriptor(pt) + ")" + Type.getDescriptor(bigclaz), false); + } else { + mv.visitVarInsn(ALOAD, insn); + } + mv.visitInsn(AASTORE); + } + mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "callParameter", "(" + convertWriterDesc + "[Ljava/lang/Object;)V", false); + + //-------------------------直接返回 或者 调用convertTo方法 -------------------------------- + int maxStack = codes.length > 0 ? codes[codes.length - 1][1] : 1; + if (returnClass == void.class) { //返回 + mv.visitInsn(RETURN); + maxStack = 8; + } else { + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "convert", Type.getDescriptor(BsonConvert.class)); + mv.visitVarInsn(ALOAD, 2); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "paramTypes", "[Ljava/lang/reflect/Type;"); + mv.visitInsn(ICONST_0); + mv.visitInsn(AALOAD); + mv.visitVarInsn(ALOAD, store); + mv.visitMethodInsn(INVOKEVIRTUAL, convertName, "convertTo", "(" + convertWriterDesc + "Ljava/lang/reflect/Type;Ljava/lang/Object;)V", false); + mv.visitInsn(RETURN); + store++; + if (maxStack < 10) maxStack = 10; + } + mv.visitMaxs(maxStack, store); + mv.visitEnd(); + } + cw.visitEnd(); + + byte[] bytes = cw.toByteArray(); + Class newClazz = new ClassLoader(serviceClass.getClassLoader()) { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass(newDynName.replace('/', '.'), bytes); + try { + SncpServletAction instance = (SncpServletAction) newClazz.newInstance(); + instance.method = method; + java.lang.reflect.Type[] ptypes = method.getGenericParameterTypes(); + java.lang.reflect.Type[] types = new java.lang.reflect.Type[ptypes.length + 1]; + java.lang.reflect.Type rt = method.getGenericReturnType(); + if (rt instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) rt; + if (tv.getBounds().length == 1) rt = tv.getBounds()[0]; + } + types[0] = rt; + System.arraycopy(ptypes, 0, types, 1, ptypes.length); + instance.paramTypes = types; + + org.redkale.util.Attribute[] atts = new org.redkale.util.Attribute[ptypes.length + 1]; + Annotation[][] anns = method.getParameterAnnotations(); + boolean hasattr = false; + for (int i = 0; i < anns.length; i++) { + if (anns[i].length > 0) { + for (Annotation ann : anns[i]) { + if (ann.annotationType() == SncpCall.class) { + try { + atts[i + 1] = ((SncpCall) ann).value().newInstance(); + hasattr = true; + } catch (Exception e) { + logger.log(Level.SEVERE, SncpCall.class.getSimpleName() + ".attribute cannot a newInstance for" + method, e); + } + break; + } + } + } + } + if (hasattr) instance.paramAttrs = atts; + newClazz.getField("service").set(instance, service); + return instance; + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + } + } + +} diff --git a/src/org/redkale/net/sncp/SncpFuture.java b/src/org/redkale/net/sncp/SncpFuture.java new file mode 100644 index 000000000..389adf080 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpFuture.java @@ -0,0 +1,94 @@ +/* + * 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.net.sncp; + +import java.util.concurrent.*; + +/** + * 简单的Future实现, set、get方法均只能一个线程调用 + * + * @author zhangjx + * @param + */ +public class SncpFuture implements Future { + + private volatile boolean done; + + private T result; + + private RuntimeException ex; + + public SncpFuture() { + } + + public SncpFuture(T result) { + this.result = result; + this.done = true; + } + + public void set(T result) { + this.result = result; + this.done = true; + synchronized (this) { + notifyAll(); + } + } + + public void set(RuntimeException ex) { + this.ex = ex; + this.done = true; + synchronized (this) { + notifyAll(); + } + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return done; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + if (done) { + if (ex != null) throw ex; + return result; + } + synchronized (this) { + if (!done) wait(10_000); + } + if (done) { + if (ex != null) throw ex; + return result; + } + throw new InterruptedException(); + } + + @Override + public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + if (done) { + if (ex != null) throw ex; + return result; + } + synchronized (this) { + if (!done) wait(unit.toMillis(timeout)); + } + if (done) { + if (ex != null) throw ex; + return result; + } + throw new TimeoutException(); + } +} diff --git a/src/org/redkale/net/sncp/SncpPrepareServlet.java b/src/org/redkale/net/sncp/SncpPrepareServlet.java new file mode 100644 index 000000000..6fed4b90a --- /dev/null +++ b/src/org/redkale/net/sncp/SncpPrepareServlet.java @@ -0,0 +1,94 @@ +/* + * 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.net.sncp; + +import org.redkale.net.PrepareServlet; +import org.redkale.net.Context; +import org.redkale.util.AnyValue; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.*; + +/** + * + * @author zhangjx + */ +public class SncpPrepareServlet extends PrepareServlet { + + private static final ByteBuffer pongBuffer = ByteBuffer.wrap("PONG".getBytes()).asReadOnlyBuffer(); + + private final Map> maps = new HashMap<>(); + + private final Map singlemaps = new HashMap<>(); + + public void addSncpServlet(SncpServlet servlet) { + if (servlet.getNameid() == 0) { + synchronized (singlemaps) { + singlemaps.put(servlet.getServiceid(), servlet); + } + } else { + synchronized (maps) { + Map m = maps.get(servlet.getServiceid()); + if (m == null) { + m = new HashMap<>(); + maps.put(servlet.getServiceid(), m); + } + m.put(servlet.getNameid(), servlet); + } + } + } + + public List getSncpServlets() { + ArrayList list = new ArrayList<>(singlemaps.values()); + maps.values().forEach(x -> list.addAll(x.values())); + return list; + } + + @Override + public void init(Context context, AnyValue config) { + Collection> values = this.maps.values(); + values.stream().forEach((en) -> { + en.values().stream().forEach(s -> s.init(context, s.conf)); + }); + } + + @Override + public void destroy(Context context, AnyValue config) { + Collection> values = this.maps.values(); + values.stream().forEach((en) -> { + en.values().stream().forEach(s -> s.destroy(context, s.conf)); + }); + } + + @Override + public void execute(SncpRequest request, SncpResponse response) throws IOException { + if (request.isPing()) { + response.finish(pongBuffer.duplicate()); + return; + } + SncpServlet servlet; + if (request.getNameid() == 0) { + servlet = singlemaps.get(request.getServiceid()); + if (servlet == null) { + response.finish(SncpResponse.RETCODE_ILLSERVICEID, null); //无效serviceid + return; + } + } else { + Map m = maps.get(request.getServiceid()); + if (m == null) { + response.finish(SncpResponse.RETCODE_ILLSERVICEID, null); //无效serviceid + return; + } + servlet = m.get(request.getNameid()); + } + if (servlet == null) { + response.finish(SncpResponse.RETCODE_ILLNAMEID, null); //无效nameid + } else { + servlet.execute(request, response); + } + } + +} diff --git a/src/org/redkale/net/sncp/SncpRemote.java b/src/org/redkale/net/sncp/SncpRemote.java new file mode 100644 index 000000000..4ee122943 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpRemote.java @@ -0,0 +1,23 @@ +/* + * 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.net.sncp; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 用于在 Service 中创建自身远程模式的对象 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface SncpRemote { + +} diff --git a/src/org/redkale/net/sncp/SncpRequest.java b/src/org/redkale/net/sncp/SncpRequest.java new file mode 100644 index 000000000..7fc9aad16 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpRequest.java @@ -0,0 +1,148 @@ +/* + * 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.net.sncp; + +import java.net.*; +import java.nio.*; +import org.redkale.convert.bson.*; +import org.redkale.net.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class SncpRequest extends Request { + + public static final int HEADER_SIZE = 64; + + public static final byte[] DEFAULT_HEADER = new byte[HEADER_SIZE]; + + protected final BsonConvert convert; + + private long seqid; + + private long nameid; + + private long serviceid; + + private DLong actionid; + + private int bodylength; + + private int bodyoffset; + + private int framelength; + + private boolean ping; + + private byte[] body; + + private byte[] bufferbytes = new byte[6]; + + protected SncpRequest(Context context) { + super(context); + this.convert = context.getBsonConvert(); + } + + @Override + protected int readHeader(ByteBuffer buffer) { + if (buffer.remaining() < HEADER_SIZE) { + this.ping = true; + return 0; + } + //---------------------head---------------------------------- + this.seqid = buffer.getLong(); + if (buffer.getChar() != HEADER_SIZE) { + context.getLogger().finest("sncp buffer header.length not " + HEADER_SIZE); + return -1; + } + this.serviceid = buffer.getLong(); + this.nameid = buffer.getLong(); + this.actionid = new DLong(buffer.getLong(), buffer.getLong()); + buffer.get(bufferbytes); + this.bodylength = buffer.getInt(); + this.bodyoffset = buffer.getInt(); + this.framelength = buffer.getInt(); + + if (buffer.getInt() != 0) { + context.getLogger().finest("sncp buffer header.retcode not 0"); + return -1; + } + //---------------------body---------------------------------- + this.body = new byte[this.bodylength]; + int len = Math.min(this.bodylength, buffer.remaining()); + buffer.get(body, 0, len); + this.bodyoffset = len; + return bodylength - len; + } + + @Override + protected int readBody(ByteBuffer buffer) { + final int framelen = buffer.remaining(); + buffer.get(this.body, this.bodyoffset, framelen); + this.bodyoffset += framelen; + return framelen; + } + + @Override + protected void prepare() { + this.keepAlive = true; + } + + @Override + public String toString() { + return SncpRequest.class.getSimpleName() + "{seqid=" + this.seqid + + ",serviceid=" + this.serviceid + ",actionid=" + this.actionid + + ",bodylength=" + this.bodylength + ",bodyoffset=" + this.bodyoffset + + ",framelength=" + this.framelength + ",remoteAddress=" + getRemoteAddress() + "}"; + } + + @Override + protected void recycle() { + this.seqid = 0; + this.framelength = 0; + this.serviceid = 0; + this.actionid = null; + this.bodylength = 0; + this.bodyoffset = 0; + this.body = null; + this.ping = false; + this.bufferbytes[0] = 0; + super.recycle(); + } + + protected boolean isPing() { + return ping; + } + + public byte[] getBody() { + return body; + } + + public long getSeqid() { + return seqid; + } + + public long getServiceid() { + return serviceid; + } + + public long getNameid() { + return nameid; + } + + public DLong getActionid() { + return actionid; + } + + public InetSocketAddress getRemoteAddress() { + if (bufferbytes[0] == 0) return null; + return new InetSocketAddress((0xff & bufferbytes[0]) + "." + (0xff & bufferbytes[1]) + "." + (0xff & bufferbytes[2]) + "." + (0xff & bufferbytes[3]), + ((0xff00 & (bufferbytes[4] << 8)) | (0xff & bufferbytes[5]))); + } + +} diff --git a/src/org/redkale/net/sncp/SncpResponse.java b/src/org/redkale/net/sncp/SncpResponse.java new file mode 100644 index 000000000..48c1ffc4e --- /dev/null +++ b/src/org/redkale/net/sncp/SncpResponse.java @@ -0,0 +1,83 @@ +/* + * 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.net.sncp; + +import static org.redkale.net.sncp.SncpRequest.HEADER_SIZE; +import java.nio.*; +import java.util.concurrent.atomic.*; +import org.redkale.convert.bson.*; +import org.redkale.net.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class SncpResponse extends Response { + + public static final int RETCODE_ILLSERVICEID = 10001; //无效serviceid + + public static final int RETCODE_ILLNAMEID = 10002; //无效nameid + + public static final int RETCODE_ILLACTIONID = 10003; //无效actionid + + public static final int RETCODE_THROWEXCEPTION = 10011; //内部异常 + + public static ObjectPool createPool(AtomicLong creatCounter, AtomicLong cycleCounter, int max, Creator creator) { + return new ObjectPool<>(creatCounter, cycleCounter, max, creator, (x) -> ((SncpResponse) x).prepare(), (x) -> ((SncpResponse) x).recycle()); + } + + private final byte[] addrBytes; + + private final int addrPort; + + public static String getRetCodeInfo(int retcode) { + if (retcode == RETCODE_ILLSERVICEID) return "serviceid is invalid"; + if (retcode == RETCODE_ILLNAMEID) return "nameid is invalid"; + if (retcode == RETCODE_ILLACTIONID) return "actionid is invalid"; + if (retcode == RETCODE_THROWEXCEPTION) return "Inner exception"; + return null; + } + + protected SncpResponse(Context context, SncpRequest request) { + super(context, request); + this.addrBytes = context.getServerAddress().getAddress().getAddress(); + this.addrPort = context.getServerAddress().getPort(); + } + + public void finish(final int retcode, final BsonWriter out) { + if (out == null) { + final ByteBuffer buffer = context.pollBuffer(); + fillHeader(buffer, 0, 0, 0, retcode); + finish(buffer); + return; + } + final int respBodyLength = out.count(); //body总长度 + final ByteBuffer[] buffers = out.toBuffers(); + fillHeader(buffers[0], respBodyLength - HEADER_SIZE, 0, respBodyLength - HEADER_SIZE, retcode); + finish(buffers); + } + + private void fillHeader(ByteBuffer buffer, int bodyLength, int bodyOffset, int framelength, int retcode) { + //---------------------head---------------------------------- + final int currentpos = buffer.position(); + buffer.position(0); + buffer.putLong(request.getSeqid()); + buffer.putChar((char) SncpRequest.HEADER_SIZE); + buffer.putLong(request.getServiceid()); + buffer.putLong(request.getNameid()); + DLong actionid = request.getActionid(); + buffer.putLong(actionid.getFirst()); + buffer.putLong(actionid.getSecond()); + buffer.put(addrBytes); + buffer.putChar((char) this.addrPort); + buffer.putInt(bodyLength); + buffer.putInt(bodyOffset); + buffer.putInt(framelength); + buffer.putInt(retcode); + buffer.position(currentpos); + } +} diff --git a/src/org/redkale/net/sncp/SncpServer.java b/src/org/redkale/net/sncp/SncpServer.java new file mode 100644 index 000000000..c8fc55147 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpServer.java @@ -0,0 +1,66 @@ +/* + * 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.net.sncp; + +import java.nio.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import org.redkale.convert.bson.*; +import org.redkale.net.*; +import org.redkale.util.*; +import org.redkale.watch.*; + +/** + * Service Node Communicate Protocol + * + * @author zhangjx + */ +public final class SncpServer extends Server { + + public SncpServer() { + this(System.currentTimeMillis(), null); + } + + public SncpServer(long serverStartTime, final WatchFactory watch) { + super(serverStartTime, "TCP", new SncpPrepareServlet(), watch); + } + + @Override + public void init(AnyValue config) throws Exception { + super.init(config); + } + + public void addService(ServiceWrapper entry) { + ((SncpPrepareServlet) this.prepare).addSncpServlet(new SncpDynServlet(BsonFactory.root().getConvert(), entry.getName(), entry.getService(), entry.getConf())); + } + + public List getSncpServlets() { + return ((SncpPrepareServlet) this.prepare).getSncpServlets(); + } + + @Override + @SuppressWarnings("unchecked") + protected Context createContext() { + final int port = this.address.getPort(); + AtomicLong createBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SNCP_" + port + ".Buffer.creatCounter"); + AtomicLong cycleBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SNCP_" + port + ".Buffer.cycleCounter"); + final int rcapacity = Math.max(this.capacity, 4 * 1024); + ObjectPool bufferPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, this.bufferPoolSize, + (Object... params) -> ByteBuffer.allocateDirect(rcapacity), null, (e) -> { + if (e == null || e.isReadOnly() || e.capacity() != rcapacity) return false; + e.clear(); + return true; + }); + AtomicLong createResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SNCP_" + port + ".Response.creatCounter"); + AtomicLong cycleResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("SNCP_" + port + ".Response.cycleCounter"); + ObjectPool responsePool = SncpResponse.createPool(createResponseCounter, cycleResponseCounter, this.responsePoolSize, null); + Context sncpcontext = new Context(this.serverStartTime, this.logger, executor, rcapacity, bufferPool, responsePool, + this.maxbody, this.charset, this.address, this.prepare, this.watch, this.readTimeoutSecond, this.writeTimeoutSecond); + responsePool.setCreator((Object... params) -> new SncpResponse(sncpcontext, new SncpRequest(sncpcontext))); + return sncpcontext; + } + +} diff --git a/src/org/redkale/net/sncp/SncpServlet.java b/src/org/redkale/net/sncp/SncpServlet.java new file mode 100644 index 000000000..e1cf57af0 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpServlet.java @@ -0,0 +1,32 @@ +/* + * 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.net.sncp; + +import org.redkale.net.Servlet; +import org.redkale.util.AnyValue; + +/** + * + * @author zhangjx + */ +public abstract class SncpServlet implements Servlet { + + AnyValue conf; + + public abstract long getNameid(); + + public abstract long getServiceid(); + + @Override + public final boolean equals(Object obj) { + return obj != null && obj.getClass() == this.getClass(); + } + + @Override + public final int hashCode() { + return this.getClass().hashCode(); + } +} diff --git a/src/org/redkale/net/sncp/SncpTargetAddress.java b/src/org/redkale/net/sncp/SncpTargetAddress.java new file mode 100644 index 000000000..53c98202e --- /dev/null +++ b/src/org/redkale/net/sncp/SncpTargetAddress.java @@ -0,0 +1,24 @@ +/* + * 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.net.sncp; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * SNCP协议中标记为目标地址参数, 该注解只能标记在类型为SocketAddress或其之类的参数上。 + * + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({PARAMETER}) +@Retention(RUNTIME) +public @interface SncpTargetAddress { + +} diff --git a/src/org/redkale/service/DataCacheListenerService.java b/src/org/redkale/service/DataCacheListenerService.java new file mode 100644 index 000000000..665717239 --- /dev/null +++ b/src/org/redkale/service/DataCacheListenerService.java @@ -0,0 +1,41 @@ +/* + * 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.service; + +import java.io.*; +import javax.annotation.*; +import org.redkale.source.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +@AutoLoad(false) +public class DataCacheListenerService implements DataCacheListener, Service { + + @Resource(name = "$") + private DataSource source; + + @Override + @MultiRun(selfrun = false, async = true) + public void insertCache(Class clazz, T... entitys) { + ((DataDefaultSource) source).insertCache(clazz, entitys); + } + + @Override + @MultiRun(selfrun = false, async = true) + public void updateCache(Class clazz, T... entitys) { + ((DataDefaultSource) source).updateCache(clazz, entitys); + } + + @Override + @MultiRun(selfrun = false, async = true) + public void deleteCache(Class clazz, Serializable... ids) { + ((DataDefaultSource) source).deleteCache(clazz, ids); + } + +} diff --git a/src/org/redkale/service/DataSQLListenerService.java b/src/org/redkale/service/DataSQLListenerService.java new file mode 100644 index 000000000..b37a1b0ee --- /dev/null +++ b/src/org/redkale/service/DataSQLListenerService.java @@ -0,0 +1,118 @@ +/* + * 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.service; + +import org.redkale.source.DataSQLListener; +import org.redkale.source.DataSource; +import org.redkale.source.DataDefaultSource; +import org.redkale.util.AnyValue; +import org.redkale.util.AutoLoad; +import java.io.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.logging.*; +import javax.annotation.Resource; + +/** + * 暂时不实现 + * + * @author zhangjx + */ +@AutoLoad(false) +public class DataSQLListenerService implements DataSQLListener, Service { + + private static final String format = "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%tL"; + + protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName()); + + private final boolean finest = logger.isLoggable(Level.FINEST); + + @Resource(name = "APP_HOME") + private File home; + + @Resource(name = "$") + private DataSource source; + + private final BlockingQueue queue = new ArrayBlockingQueue<>(1024 * 1024); + + private PrintStream syncfile; + + @Override + public void init(AnyValue config) { + new Thread() { + { + setName(DataSQLListener.class.getSimpleName() + "-Thread"); + setDaemon(true); + } + + @Override + public void run() { + while (true) { + try { + String sql = queue.take(); + send(sql); + } catch (Exception e) { + logger.log(Level.SEVERE, this.getName() + " occur error"); + } + } + } + }.start(); + + } + + @Override + public void destroy(AnyValue config) { + if (syncfile != null) syncfile.close(); + } + + private void write(String... sqls) { + try { + if (syncfile == null) { + File root = new File(home, "dbsync"); + root.mkdirs(); + syncfile = new PrintStream(new FileOutputStream(new File(root, "sql-" + name() + ".sql"), true), false, "UTF-8"); + } + for (String sql : sqls) { + syncfile.print(sql + ";\r\n"); + } + syncfile.flush(); + } catch (Exception e) { + logger.log(Level.WARNING, "write sql file error. (" + name() + ", " + Arrays.toString(sqls) + ")", e); + } + } + + @Override + public void insert(String... sqls) { + put(sqls); + } + + @Override + public void update(String... sqls) { + put(sqls); + } + + @Override + public void delete(String... sqls) { + put(sqls); + } + + private void put(String... sqls) { + String date = String.format(format, System.currentTimeMillis()); + for (String sql : sqls) { + try { + queue.put("/* " + date + " */ " + sql); + } catch (Exception e) { + write(sql); + } + } + } + + @MultiRun + public void send(String... sqls) { + ((DataDefaultSource) source).execute(sqls); + } + +} diff --git a/src/org/redkale/service/DataSourceService.java b/src/org/redkale/service/DataSourceService.java new file mode 100644 index 000000000..5cb3815c6 --- /dev/null +++ b/src/org/redkale/service/DataSourceService.java @@ -0,0 +1,342 @@ +/* + * 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.service; + +import java.io.*; +import java.util.*; +import javax.annotation.*; +import org.redkale.net.sncp.*; +import org.redkale.source.DataSource.Reckon; +import org.redkale.source.*; +import org.redkale.util.*; + +/** + * DataSource对应的Service类, 该类主要特点是将所有含FilterBean参数的方法重载成FilterNode对应的方法。 + * + * @author zhangjx + */ +@AutoLoad(false) +public class DataSourceService implements DataSource, Service { + + @Resource(name = "$") + private DataSource source; + + @Override + public DataConnection createReadConnection() { + return source.createReadConnection(); + } + + @Override + public DataConnection createWriteConnection() { + return source.createWriteConnection(); + } + + @Override + public void insert(@SncpCall(DataCallArrayAttribute.class) T... values) { + source.insert(values); + } + + @Override + public void insert(DataConnection conn, @SncpCall(DataCallArrayAttribute.class) T... values) { + source.insert(conn, values); + } + + @Override + public void refreshCache(Class clazz) { + source.refreshCache(clazz); + } + + @Override + public void delete(T... values) { + source.delete(values); + } + + @Override + public void delete(DataConnection conn, T... values) { + source.delete(conn, values); + } + + @Override + public void delete(Class clazz, Serializable... ids) { + source.delete(clazz, ids); + } + + @Override + public void delete(DataConnection conn, Class clazz, Serializable... ids) { + source.delete(conn, clazz, ids); + } + + @Override + public void delete(Class clazz, FilterNode node) { + source.delete(clazz, node); + } + + @Override + public void delete(DataConnection conn, Class clazz, FilterNode node) { + source.delete(conn, clazz, node); + } + + @Override + public void update(T... values) { + source.update(values); + } + + @Override + public void update(DataConnection conn, T... values) { + source.update(conn, values); + } + + @Override + public void updateColumn(Class clazz, Serializable id, String column, Serializable value) { + source.updateColumn(clazz, id, column, value); + } + + @Override + public void updateColumn(DataConnection conn, Class clazz, Serializable id, String column, Serializable value) { + source.updateColumn(conn, clazz, id, column, value); + } + + @Override + public void updateColumns(T value, String... columns) { + source.updateColumns(value, columns); + } + + @Override + public void updateColumns(DataConnection conn, T value, String... columns) { + source.updateColumns(conn, value, columns); + } + + @Override + public void updateColumnIncrement(Class clazz, Serializable id, String column, long incvalue) { + source.updateColumnIncrement(clazz, id, column, incvalue); + } + + @Override + public void updateColumnIncrement(DataConnection conn, Class clazz, Serializable id, String column, long incvalue) { + source.updateColumnIncrement(conn, clazz, id, column, incvalue); + } + + @Override + public void updateColumnAnd(Class clazz, Serializable id, String column, long incvalue) { + source.updateColumnAnd(clazz, id, column, incvalue); + } + + @Override + public void updateColumnAnd(DataConnection conn, Class clazz, Serializable id, String column, long incvalue) { + source.updateColumnAnd(conn, clazz, id, column, incvalue); + } + + @Override + public void updateColumnOr(Class clazz, Serializable id, String column, long incvalue) { + source.updateColumnOr(clazz, id, column, incvalue); + } + + @Override + public void updateColumnOr(DataConnection conn, Class clazz, Serializable id, String column, long incvalue) { + source.updateColumnOr(conn, clazz, id, column, incvalue); + } + + @Override + public Number getNumberResult(Class entityClass, Reckon reckon, String column) { + return source.getNumberResult(entityClass, reckon, column); + } + + @Override + public final Number getNumberResult(Class entityClass, Reckon reckon, String column, FilterBean bean) { + return getNumberResult(entityClass, reckon, column, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Number getNumberResult(Class entityClass, Reckon reckon, String column, FilterNode node) { + return source.getNumberResult(entityClass, reckon, column, node); + } + + @Override + public Map getMapResult(Class entityClass, String keyColumn, Reckon reckon, String reckonColumn) { + return source.getMapResult(entityClass, keyColumn, reckon, reckonColumn); + } + + @Override + public final Map getMapResult(Class entityClass, String keyColumn, Reckon reckon, String reckonColumn, FilterBean bean) { + return getMapResult(entityClass, keyColumn, reckon, reckonColumn, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Map getMapResult(Class entityClass, String keyColumn, Reckon reckon, String reckonColumn, FilterNode node) { + return source.getMapResult(entityClass, keyColumn, reckon, reckonColumn, node); + } + + @Override + public T find(Class clazz, Serializable pk) { + return source.find(clazz, pk); + } + + @Override + public T find(Class clazz, SelectColumn selects, Serializable pk) { + return source.find(clazz, selects, pk); + } + + @Override + public T findByColumn(Class clazz, String column, Serializable key) { + return source.findByColumn(clazz, column, key); + } + + @Override + public T find(Class clazz, FilterNode node) { + return source.find(clazz, node); + } + + @Override + public final T find(Class clazz, FilterBean bean) { + return find(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public boolean exists(Class clazz, Serializable pk) { + return source.exists(clazz, pk); + } + + @Override + public boolean exists(Class clazz, FilterNode node) { + return source.exists(clazz, node); + } + + @Override + public final boolean exists(Class clazz, FilterBean bean) { + return exists(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public HashSet queryColumnSet(String selectedColumn, Class clazz, String column, Serializable key) { + return source.queryColumnSet(selectedColumn, clazz, column, key); + } + + @Override + public HashSet queryColumnSet(String selectedColumn, Class clazz, FilterNode node) { + return source.queryColumnSet(selectedColumn, clazz, node); + } + + @Override + public final HashSet queryColumnSet(String selectedColumn, Class clazz, FilterBean bean) { + return queryColumnSet(selectedColumn, clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryColumnList(String selectedColumn, Class clazz, String column, Serializable key) { + return source.queryColumnList(selectedColumn, clazz, column, key); + } + + @Override + public List queryColumnList(String selectedColumn, Class clazz, FilterNode node) { + return source.queryColumnList(selectedColumn, clazz, node); + } + + @Override + public final List queryColumnList(String selectedColumn, Class clazz, FilterBean bean) { + return queryColumnList(selectedColumn, clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Map queryMap(Class clazz, FilterNode node) { + return source.queryMap(clazz, node); + } + + @Override + public final Map queryMap(Class clazz, FilterBean bean) { + return queryMap(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Map queryMap(Class clazz, SelectColumn selects, FilterNode node) { + return source.queryMap(clazz, selects, node); + } + + @Override + public final Map queryMap(Class clazz, SelectColumn selects, FilterBean bean) { + return queryMap(clazz, selects, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryList(Class clazz, String column, Serializable key) { + return source.queryList(clazz, column, key); + } + + @Override + public List queryList(Class clazz, FilterNode node) { + return source.queryList(clazz, node); + } + + @Override + public final List queryList(Class clazz, FilterBean bean) { + return queryList(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryList(Class clazz, SelectColumn selects, FilterNode node) { + return source.queryList(clazz, selects, node); + } + + @Override + public final List queryList(Class clazz, SelectColumn selects, FilterBean bean) { + return queryList(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryList(Class clazz, Flipper flipper, String column, Serializable key) { + return source.queryList(clazz, flipper, column, key); + } + + @Override + public List queryList(Class clazz, Flipper flipper, FilterNode node) { + return source.queryList(clazz, flipper, node); + } + + @Override + public final List queryList(Class clazz, Flipper flipper, FilterBean bean) { + return queryList(clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryList(Class clazz, SelectColumn selects, Flipper flipper, FilterNode node) { + return source.queryList(clazz, selects, flipper, node); + } + + @Override + public final List queryList(Class clazz, SelectColumn selects, Flipper flipper, FilterBean bean) { + return queryList(clazz, selects, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final Sheet queryColumnSheet(String selectedColumn, Class clazz, Flipper flipper, FilterBean bean) { + return queryColumnSheet(selectedColumn, clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Sheet queryColumnSheet(String selectedColumn, Class clazz, Flipper flipper, FilterNode node) { + return source.queryColumnSheet(selectedColumn, clazz, flipper, node); + } + + @Override + public final Sheet querySheet(Class clazz, Flipper flipper, FilterBean bean) { + return querySheet(clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Sheet querySheet(Class clazz, Flipper flipper, FilterNode node) { + return source.querySheet(clazz, flipper, node); + } + + @Override + public final Sheet querySheet(Class clazz, SelectColumn selects, Flipper flipper, FilterBean bean) { + return querySheet(clazz, selects, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Sheet querySheet(Class clazz, SelectColumn selects, Flipper flipper, FilterNode node) { + return source.querySheet(clazz, selects, flipper, node); + } + +} diff --git a/src/org/redkale/service/LocalService.java b/src/org/redkale/service/LocalService.java new file mode 100644 index 000000000..fbcf25ae2 --- /dev/null +++ b/src/org/redkale/service/LocalService.java @@ -0,0 +1,23 @@ +/* + * 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.service; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 声明为LocalService的Service将不会变成远程模式, 无论配置文件中是否配置成远程模式。 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({TYPE}) +@Retention(RUNTIME) +public @interface LocalService { + +} diff --git a/src/org/redkale/service/MultiRun.java b/src/org/redkale/service/MultiRun.java new file mode 100644 index 000000000..65753da0a --- /dev/null +++ b/src/org/redkale/service/MultiRun.java @@ -0,0 +1,30 @@ +/* + * 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.service; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * MultiRun 只对本地模式Service有效 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({METHOD}) +@Retention(RUNTIME) +public @interface MultiRun { + + boolean selfrun() default true; //当前本地实例是否运行指定操作;只有当指定操作的方法的返回值为void时,该值才有效。 + + boolean samerun() default true; //是否同组节点也运行指定操作 + + boolean diffrun() default true; //是否不同组节点也运行指定操作 + + boolean async() default true; //分布式运行是否采用异步模式 +} diff --git a/src/org/redkale/service/RetResult.java b/src/org/redkale/service/RetResult.java new file mode 100644 index 000000000..915ed21ee --- /dev/null +++ b/src/org/redkale/service/RetResult.java @@ -0,0 +1,113 @@ +/* + * 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.service; + +import org.redkale.convert.json.*; + +/** + * 通用的结果对象,在常见的HTTP+JSON接口中返回的结果需要含结果码,错误信息,和实体对象。 + * + * @author zhangjx + * @param + */ +public class RetResult { + + protected static final class RetSuccessResult extends RetResult { + + public RetSuccessResult() { + } + + @Override + public void setRetcode(int retcode) { + } + + @Override + public void setRetinfo(String retinfo) { + } + + @Override + public void setResult(T result) { + } + } + + public static final RetResult SUCCESS = new RetSuccessResult(); + + protected int retcode; + + protected String retinfo; + + private T result; + + public RetResult() { + } + + public RetResult(T result) { + this.result = result; + } + + public RetResult(int retcode) { + this.retcode = retcode; + } + + public RetResult(int retcode, String retinfo) { + this.retcode = retcode; + this.retinfo = retinfo; + } + + public RetResult(int retcode, String retinfo, T result) { + this.retcode = retcode; + this.retinfo = retinfo; + this.result = result; + } + + /** + * 判断结果是否成功返回, retcode = 0 视为成功, 否则视为错误码 + * + * @return + */ + public boolean isSuccess() { + return retcode == 0; + } + + /** + * 结果码 0表示成功、 非0表示错误 + * + * @return + */ + public int getRetcode() { + return retcode; + } + + public void setRetcode(int retcode) { + this.retcode = retcode; + } + + public String getRetinfo() { + return retinfo; + } + + public void setRetinfo(String retinfo) { + this.retinfo = retinfo; + } + + /** + * 结果对象, 通常只有在retcode = 0时值才有效 + * + * @return + */ + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } + + @Override + public String toString() { + return JsonFactory.root().getConvert().convertTo(this); + } +} diff --git a/src/org/redkale/service/Service.java b/src/org/redkale/service/Service.java new file mode 100644 index 000000000..d1e8058b3 --- /dev/null +++ b/src/org/redkale/service/Service.java @@ -0,0 +1,43 @@ +/* + * 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.service; + +import org.redkale.util.*; + +/** + * 所有Service的实现类不得声明为final, 允许远程模式的public方法和public String name()方法都不能声明为final。 + *

+ * @Resource(name = ".*") + * private HashMap nodemap; + * 被注入的多个XXXService实例 但不会包含自身的XXXService。 + * + * @author zhangjx + */ +public interface Service extends Nameable { + + /** + * 该方法必须是可以重复调用, 当reload时需要重复调用init方法 + * + * @param config + */ + default void init(AnyValue config) { + + } + + default void destroy(AnyValue config) { + + } + + /** + * Service的name, 一个Service在同一进程内可以包含多个实例, 使用name区分 + *

+ * @return + */ + @Override + default String name() { + return ""; + } +} diff --git a/src/org/redkale/service/WebSocketNodeService.java b/src/org/redkale/service/WebSocketNodeService.java new file mode 100644 index 000000000..2227a25c6 --- /dev/null +++ b/src/org/redkale/service/WebSocketNodeService.java @@ -0,0 +1,76 @@ +/* + * 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.service; + +import static org.redkale.net.http.WebSocket.*; +import java.io.*; +import java.net.*; +import java.util.*; +import org.redkale.net.http.*; +import org.redkale.net.sncp.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +@AutoLoad(false) +public class WebSocketNodeService extends WebSocketNode implements Service { + + @Override + public void init(AnyValue conf) { + super.init(conf); + } + + @Override + public void destroy(AnyValue conf) { + super.destroy(conf); + } + + @Override + public int sendMessage(@SncpTargetAddress InetSocketAddress addr, Serializable groupid, boolean recent, Serializable message, boolean last) { + final Set engineids = localNodes.get(groupid); + if (engineids == null || engineids.isEmpty()) return RETCODE_GROUP_EMPTY; + int code = RETCODE_GROUP_EMPTY; + for (String engineid : engineids) { + final WebSocketEngine engine = engines.get(engineid); + if (engine != null) { //在本地 + final WebSocketGroup group = engine.getWebSocketGroup(groupid); + if (group == null || group.isEmpty()) { + if (finest) logger.finest("receive websocket message {engineid:'" + engineid + "', groupid:" + groupid + ", content:'" + message + "'} but result is " + RETCODE_GROUP_EMPTY); + return RETCODE_GROUP_EMPTY; + } + code = group.send(recent, message, last); + if (finest) logger.finest("websocket node send message (" + message + ") result is " + code); + } + } + return code; + } + + @Override + @MultiRun + public void connect(Serializable groupid, InetSocketAddress addr) { + LinkedHashSet addrs = dataNodes.get(groupid); + if (addrs == null) { + addrs = new LinkedHashSet<>(); + dataNodes.put(groupid, addrs); + } + addrs.add(addr); + if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + groupid + " connect from " + addr); + } + + @Override + @MultiRun + public void disconnect(Serializable groupid, InetSocketAddress addr) { + Set addrs = dataNodes.get(groupid); + if (addrs == null) return; + synchronized (addrs) { + addrs.remove(addr); + } + if (addrs.isEmpty()) dataNodes.remove(groupid); + if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + groupid + " disconnect from " + addr); + } +} diff --git a/src/org/redkale/source/DataCacheListener.java b/src/org/redkale/source/DataCacheListener.java new file mode 100644 index 000000000..e612486b9 --- /dev/null +++ b/src/org/redkale/source/DataCacheListener.java @@ -0,0 +1,21 @@ +/* + * 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.source; + +import java.io.Serializable; + +/** + * + * @author zhangjx + */ +public interface DataCacheListener { + + public void insertCache(Class clazz, T... entitys); + + public void updateCache(Class clazz, T... entitys); + + public void deleteCache(Class clazz, Serializable... ids); +} diff --git a/src/org/redkale/source/DataCallArrayAttribute.java b/src/org/redkale/source/DataCallArrayAttribute.java new file mode 100644 index 000000000..276540126 --- /dev/null +++ b/src/org/redkale/source/DataCallArrayAttribute.java @@ -0,0 +1,57 @@ +/* + * 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.source; + +import java.io.*; +import java.lang.reflect.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + * @param + * @param + */ +public final class DataCallArrayAttribute implements Attribute { + + public static final DataCallArrayAttribute instance = new DataCallArrayAttribute(); + + @Override + public Class type() { + return (Class) Object.class; + } + + @Override + public Class declaringClass() { + return (Class) (Class) Object[].class; + } + + @Override + public String field() { + return ""; + } + + @Override + public F get(final T[] objs) { + if (objs == null || objs.length == 0) return null; + final Attribute attr = DataCallAttribute.load(objs[0].getClass()); + final Object keys = Array.newInstance(attr.type(), objs.length); + for (int i = 0; i < objs.length; i++) { + Array.set(keys, i, attr.get(objs[i])); + } + return (F) keys; + } + + @Override + public void set(final T[] objs, final F keys) { + if (objs == null || objs.length == 0) return; + final Attribute attr = DataCallAttribute.load(objs[0].getClass()); + for (int i = 0; i < objs.length; i++) { + attr.set(objs[i], (Serializable) Array.get(keys, i)); + } + } + +} diff --git a/src/org/redkale/source/DataCallAttribute.java b/src/org/redkale/source/DataCallAttribute.java new file mode 100644 index 000000000..9873f063d --- /dev/null +++ b/src/org/redkale/source/DataCallAttribute.java @@ -0,0 +1,72 @@ +/* + * 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.source; + +import java.io.*; +import java.lang.reflect.*; +import java.util.concurrent.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public class DataCallAttribute implements Attribute { + + public static final DataCallAttribute instance = new DataCallAttribute(); + + private static final ConcurrentHashMap attributes = new ConcurrentHashMap<>(); + + static Attribute load(final Class clazz) { + Attribute rs = attributes.get(clazz); + if (rs != null) return rs; + synchronized (attributes) { + rs = attributes.get(clazz); + if (rs == null) { + Class cltmp = clazz; + do { + for (Field field : cltmp.getDeclaredFields()) { + if (field.getAnnotation(javax.persistence.Id.class) == null) continue; + try { + rs = Attribute.create(cltmp, field); + attributes.put(clazz, rs); + return rs; + } catch (RuntimeException e) { + } + } + } while ((cltmp = cltmp.getSuperclass()) != Object.class); + } + return rs; + } + } + + @Override + public Class type() { + return Serializable.class; + } + + @Override + public Class declaringClass() { + return Object.class; + } + + @Override + public String field() { + return ""; + } + + @Override + public Serializable get(final Object obj) { + if (obj == null) return null; + return load(obj.getClass()).get(obj); + } + + @Override + public void set(final Object obj, final Serializable key) { + if (obj == null) return; + load(obj.getClass()).set(obj, key); + } +} diff --git a/src/org/redkale/source/DataConnection.java b/src/org/redkale/source/DataConnection.java new file mode 100644 index 000000000..950b297d8 --- /dev/null +++ b/src/org/redkale/source/DataConnection.java @@ -0,0 +1,29 @@ +/* + * 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.source; + +/** + * + * @author zhangjx + */ +public abstract class DataConnection { + + private final Object connection; + + protected DataConnection(Object connection) { + this.connection = connection; + } + + protected T getConnection() { + return (T) this.connection; + } + + public abstract boolean commit(); + + public abstract void rollback(); + + public abstract void close(); +} diff --git a/src/org/redkale/source/DataDefaultSource.java b/src/org/redkale/source/DataDefaultSource.java new file mode 100644 index 000000000..0c3bcb1a4 --- /dev/null +++ b/src/org/redkale/source/DataDefaultSource.java @@ -0,0 +1,1554 @@ +/* + * 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.source; + +import static org.redkale.source.FilterNode.*; +import java.io.*; +import java.lang.reflect.*; +import java.net.*; +import java.sql.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import java.util.function.*; +import java.util.logging.*; +import javax.annotation.*; +import javax.sql.*; +import javax.xml.stream.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public final class DataDefaultSource implements DataSource, Nameable, Function { + + public static final String DATASOURCE_CONFPATH = "DATASOURCE_CONFPATH"; + + static final String JDBC_CONNECTIONMAX = "javax.persistence.connection.limit"; + + static final String JDBC_URL = "javax.persistence.jdbc.url"; + + static final String JDBC_USER = "javax.persistence.jdbc.user"; + + static final String JDBC_PWD = "javax.persistence.jdbc.password"; + + static final String JDBC_DRIVER = "javax.persistence.jdbc.driver"; + + static final String JDBC_SOURCE = "javax.persistence.jdbc.source"; + + private static final Flipper FLIPPER_ONE = new Flipper(1); + + final Logger logger = Logger.getLogger(DataDefaultSource.class.getSimpleName()); + + final AtomicBoolean debug = new AtomicBoolean(logger.isLoggable(Level.FINEST)); + + final String name; + + final URL conf; + + final boolean cacheForbidden; + + private final boolean mysql; + + private final JDBCPoolSource readPool; + + private final JDBCPoolSource writePool; + + @Resource(name = "property.datasource.nodeid") + private int nodeid; + + @Resource(name = "$") + private DataSQLListener writeListener; + + @Resource(name = "$") + private DataCacheListener cacheListener; + + private static class DataJDBCConnection extends DataConnection { + + private final Connection sqlconn; + + private DataJDBCConnection(Connection c) { + super(c); + this.sqlconn = c; + try { + this.sqlconn.setAutoCommit(true); + } catch (Exception e) { + //do nothing + } + } + + @Override + public void close() { + try { + sqlconn.close(); + } catch (Exception e) { + //do nothing + } + } + + @Override + public boolean commit() { + try { + sqlconn.commit(); + return true; + } catch (Exception e) { + return false; + } + } + + @Override + public void rollback() { + try { + sqlconn.rollback(); + } catch (Exception e) { + //do nothing + } + } + } + + private final Function fullloader = (t) -> querySheet(false, false, t, null, null, (FilterNode) null).list(true); + + public DataDefaultSource() throws IOException { + this(""); + } + + public DataDefaultSource(final String unitName) throws IOException { + this(unitName, System.getProperty(DATASOURCE_CONFPATH) == null + ? DataDefaultSource.class.getResource("/META-INF/persistence.xml") + : new File(System.getProperty(DATASOURCE_CONFPATH)).toURI().toURL()); + } + + public DataDefaultSource(final String unitName, URL url) throws IOException { + if (url == null) url = this.getClass().getResource("/persistence.xml"); + InputStream in = url.openStream(); + Map map = loadProperties(in); + Properties readprop = null; + Properties writeprop = null; + if (unitName != null) { + readprop = map.get(unitName); + writeprop = readprop; + if (readprop == null) { + readprop = map.get(unitName + ".read"); + writeprop = map.get(unitName + ".write"); + } + } + if ((unitName == null || unitName.isEmpty()) || readprop == null) { + String key = null; + for (Map.Entry en : map.entrySet()) { + key = en.getKey(); + readprop = en.getValue(); + writeprop = readprop; + break; + } + if (key != null && (key.endsWith(".read") || key.endsWith(".write"))) { + if (key.endsWith(".read")) { + writeprop = map.get(key.substring(0, key.lastIndexOf('.')) + ".write"); + } else { + readprop = map.get(key.substring(0, key.lastIndexOf('.')) + ".read"); + } + } + } + if (readprop == null) throw new RuntimeException("not found persistence properties (unit:" + unitName + ")"); + this.name = unitName; + this.conf = url; + this.readPool = new JDBCPoolSource(this, "read", readprop); + this.writePool = new JDBCPoolSource(this, "write", writeprop); + this.mysql = this.writePool.isMysql(); + this.cacheForbidden = "NONE".equalsIgnoreCase(readprop.getProperty("shared-cache-mode")); + } + + public DataDefaultSource(String unitName, Properties readprop, Properties writeprop) { + this.name = unitName; + this.conf = null; + this.readPool = new JDBCPoolSource(this, "read", readprop); + this.writePool = new JDBCPoolSource(this, "write", writeprop); + this.mysql = this.writePool.isMysql(); + this.cacheForbidden = "NONE".equalsIgnoreCase(readprop.getProperty("shared-cache-mode")); + } + + public static Map create(final InputStream in) { + Map map = loadProperties(in); + Map maps = new HashMap<>(); + map.entrySet().stream().forEach((en) -> { + if (en.getKey().endsWith(".read") || en.getKey().endsWith(".write")) { + String key = en.getKey().substring(0, en.getKey().lastIndexOf('.')); + if (maps.containsKey(key)) return; + boolean read = en.getKey().endsWith(".read"); + Properties rp = read ? en.getValue() : map.get(key + ".read"); + Properties wp = read ? map.get(key + ".write") : en.getValue(); + maps.put(key, new Properties[]{rp, wp}); + } else { + maps.put(en.getKey(), new Properties[]{en.getValue(), en.getValue()}); + } + }); + Map result = new HashMap<>(); + maps.entrySet().stream().forEach((en) -> { + result.put(en.getKey(), new DataDefaultSource(en.getKey(), en.getValue()[0], en.getValue()[1])); + }); + return result; + } + + static Map loadProperties(final InputStream in0) { + final Map map = new LinkedHashMap(); + Properties result = new Properties(); + boolean flag = false; + try (final InputStream in = in0) { + XMLStreamReader reader = XMLInputFactory.newFactory().createXMLStreamReader(in); + while (reader.hasNext()) { + int event = reader.next(); + if (event == XMLStreamConstants.START_ELEMENT) { + if ("persistence-unit".equalsIgnoreCase(reader.getLocalName())) { + if (!result.isEmpty()) result = new Properties(); + map.put(reader.getAttributeValue(null, "name"), result); + flag = true; + } else if (flag && "property".equalsIgnoreCase(reader.getLocalName())) { + String name = reader.getAttributeValue(null, "name"); + String value = reader.getAttributeValue(null, "value"); + if (name == null) continue; + result.put(name, value); + } else if (flag && "shared-cache-mode".equalsIgnoreCase(reader.getLocalName())) { + result.put(reader.getLocalName(), reader.getElementText()); + } + } + } + in.close(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + return map; + } + + static ConnectionPoolDataSource createDataSource(Properties property) { + try { + return createDataSource(property.getProperty(JDBC_SOURCE, property.getProperty(JDBC_DRIVER)), + property.getProperty(JDBC_URL), property.getProperty(JDBC_USER), property.getProperty(JDBC_PWD)); + } catch (Exception ex) { + throw new RuntimeException("(" + property + ") have no jdbc parameters", ex); + } + } + + static ConnectionPoolDataSource createDataSource(final String source0, String url, String user, String password) throws Exception { + String source = source0; + if (source0.contains("Driver")) { //为了兼容JPA的配置文件 + switch (source0) { + case "org.mariadb.jdbc.Driver": + source = "org.mariadb.jdbc.MySQLDataSource"; + break; + case "com.mysql.jdbc.Driver": + source = "com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource"; + break; + case "oracle.jdbc.driver.OracleDriver": + source = "oracle.jdbc.pool.OracleConnectionPoolDataSource"; + break; + case "com.microsoft.sqlserver.jdbc.SQLServerDriver": + source = "com.microsoft.sqlserver.jdbc.SQLServerConnectionPoolDataSource"; + break; + } + } + final Class clazz = Class.forName(source); + Object pdsource = clazz.newInstance(); + Method seturlm; + try { + seturlm = clazz.getMethod("setUrl", String.class); + } catch (Exception e) { + seturlm = clazz.getMethod("setURL", String.class); + } + seturlm.invoke(pdsource, url); + clazz.getMethod("setUser", String.class).invoke(pdsource, user); + clazz.getMethod("setPassword", String.class).invoke(pdsource, password); + return (ConnectionPoolDataSource) pdsource; + } + + @Override + public final String name() { + return name; + } + + @Override + public DataConnection createReadConnection() { + return new DataJDBCConnection(createReadSQLConnection()); + } + + @Override + public DataConnection createWriteConnection() { + return new DataJDBCConnection(createWriteSQLConnection()); + } + + public void close() { + readPool.close(); + writePool.close(); + } + + public String getName() { + return name; + } + + private Connection createReadSQLConnection() { + return readPool.poll(); + } + + private Connection createWriteSQLConnection() { + return writePool.poll(); + } + + private void closeSQLConnection(final Connection sqlconn) { + if (sqlconn == null) return; + try { + sqlconn.close(); + } catch (Exception e) { + logger.log(Level.WARNING, "closeSQLConnection abort", e); + } + } + + public void execute(String... sqls) { + Connection conn = createWriteSQLConnection(); + try { + execute(conn, sqls); + } finally { + closeSQLConnection(conn); + } + } + + public void execute(final DataConnection conn, String... sqls) { + execute((Connection) conn.getConnection(), sqls); + } + + private void execute(final Connection conn, String... sqls) { + if (sqls.length == 0) return; + try { + final Statement stmt = conn.createStatement(); + for (String sql : sqls) { + stmt.execute(sql); + } + stmt.close(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Override + public EntityInfo apply(Class t) { + return loadEntityInfo(t); + } + + private EntityInfo loadEntityInfo(Class clazz) { + return EntityInfo.load(clazz, this.nodeid, this.cacheForbidden, fullloader); + } + + /** + * 将entity的对象全部加载到Cache中去,如果clazz没有被@javax.persistence.Cacheable注解则不做任何事 + *

+ * @param + * @param clazz + */ + @Override + public void refreshCache(Class clazz) { + EntityInfo info = loadEntityInfo(clazz); + EntityCache cache = info.getCache(); + if (cache == null) return; + cache.fullLoad(queryList(clazz, (FilterNode) null)); + } + + //----------------------insertCache----------------------------- + /** + * 新增对象, 必须是Entity对象 + * + * @param + * @param values + */ + @Override + public void insert(T... values) { + if (values.length == 0) return; + final EntityInfo info = loadEntityInfo((Class) values[0].getClass()); + if (info.isVirtualEntity()) { + insert(null, info, values); + return; + } + Connection conn = createWriteSQLConnection(); + try { + insert(conn, info, values); + } finally { + closeSQLConnection(conn); + } + } + + /** + * 新增对象, 必须是Entity对象 + * + * @param + * @param conn + * @param values + */ + @Override + public void insert(final DataConnection conn, T... values) { + if (values.length == 0) return; + final EntityInfo info = loadEntityInfo((Class) values[0].getClass()); + insert((Connection) conn.getConnection(), info, values); + } + + private void insert(final Connection conn, final EntityInfo info, T... values) { + if (values.length == 0) return; + try { + final EntityCache cache = info.getCache(); + if (!info.isVirtualEntity()) { + final String sql = info.insertSQL; + final PreparedStatement prestmt = info.autoGenerated + ? conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) : conn.prepareStatement(sql); + final Class primaryType = info.getPrimary().type(); + final Attribute primary = info.getPrimary(); + final boolean distributed = info.distributed; + Attribute[] attrs = info.insertAttributes; + String[] sqls = null; + if (distributed && !info.initedPrimaryValue && primaryType.isPrimitive()) { + synchronized (info) { + if (!info.initedPrimaryValue) { + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT MAX(" + info.getPrimarySQLColumn() + ") FROM " + info.getTable()); + if (rs.next()) { + if (primaryType == int.class) { + int v = rs.getInt(1) / info.allocationSize; + if (v > info.primaryValue.get()) info.primaryValue.set(v); + } else { + long v = rs.getLong(1) / info.allocationSize; + if (v > info.primaryValue.get()) info.primaryValue.set(v); + } + } + rs.close(); + stmt.close(); + if (info.distributeTables != null) { + for (final Class t : info.distributeTables) { + EntityInfo infox = loadEntityInfo(t); + stmt = conn.createStatement(); + rs = stmt.executeQuery("SELECT MAX(" + info.getPrimarySQLColumn() + ") FROM " + infox.getTable()); // 必须是同一字段名 + if (rs.next()) { + if (primaryType == int.class) { + int v = rs.getInt(1) / info.allocationSize; + if (v > info.primaryValue.get()) info.primaryValue.set(v); + } else { + long v = rs.getLong(1) / info.allocationSize; + if (v > info.primaryValue.get()) info.primaryValue.set(v); + } + } + rs.close(); + stmt.close(); + } + } + info.initedPrimaryValue = true; + } + } + } + if (writeListener == null) { + for (final T value : values) { + int i = 0; + if (distributed) info.createPrimaryValue(value); + for (Attribute attr : attrs) { + prestmt.setObject(++i, attr.get(value)); + } + prestmt.addBatch(); + } + } else { + char[] sqlchars = sql.toCharArray(); + sqls = new String[values.length]; + CharSequence[] ps = new CharSequence[attrs.length]; + int index = 0; + for (final T value : values) { + int i = 0; + if (distributed) info.createPrimaryValue(value); + for (Attribute attr : attrs) { + Object a = attr.get(value); + ps[i] = formatToString(a); + prestmt.setObject(++i, a); + } + prestmt.addBatch(); + //----------------------------- + StringBuilder sb = new StringBuilder(128); + i = 0; + for (char ch : sqlchars) { + if (ch == '?') { + sb.append(ps[i++]); + } else { + sb.append(ch); + } + } + sqls[index++] = sb.toString(); + } + } + prestmt.executeBatch(); + if (writeListener != null) writeListener.insert(sqls); + if (info.autoGenerated) { + ResultSet set = prestmt.getGeneratedKeys(); + int i = -1; + while (set.next()) { + if (primaryType == int.class) { + primary.set(values[++i], set.getInt(1)); + } else if (primaryType == long.class) { + primary.set(values[++i], set.getLong(1)); + } else { + primary.set(values[++i], set.getObject(1)); + } + } + set.close(); + } + prestmt.close(); + //------------------------------------------------------------ + if (debug.get()) { + char[] sqlchars = sql.toCharArray(); + for (final T value : values) { + //----------------------------- + StringBuilder sb = new StringBuilder(128); + int i = 0; + for (char ch : sqlchars) { + if (ch == '?') { + Object obj = attrs[i++].get(value); + if (obj != null && obj.getClass().isArray()) { + sb.append("'[length=").append(java.lang.reflect.Array.getLength(obj)).append("]'"); + } else { + sb.append(formatToString(obj)); + } + } else { + sb.append(ch); + } + } + logger.finest(info.getType().getSimpleName() + " insert sql=" + sb.toString().replaceAll("(\r|\n)", "\\n")); + } + } + } + if (cache != null) { + for (final T value : values) { + cache.insert(value); + } + if (cacheListener != null) cacheListener.insertCache(info.getType(), values); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void insertCache(Class clazz, T... values) { + if (values.length == 0) return; + final EntityInfo info = loadEntityInfo(clazz); + final EntityCache cache = info.getCache(); + if (cache == null) return; + for (T value : values) { + cache.insert(value); + } + } + + //-------------------------deleteCache-------------------------- + /** + * 删除对象, 必须是Entity对象 + * + * @param + * @param values + */ + @Override + public void delete(T... values) { + if (values.length == 0) return; + final EntityInfo info = loadEntityInfo((Class) values[0].getClass()); + if (info.isVirtualEntity()) { + delete(null, info, values); + return; + } + Connection conn = createWriteSQLConnection(); + try { + delete(conn, info, values); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public void delete(final DataConnection conn, T... values) { + if (values.length == 0) return; + final EntityInfo info = loadEntityInfo((Class) values[0].getClass()); + delete((Connection) conn.getConnection(), info, values); + } + + private void delete(final Connection conn, final EntityInfo info, T... values) { + if (values.length == 0) return; + final Attribute primary = info.getPrimary(); + Serializable[] ids = new Serializable[values.length]; + int i = 0; + for (final T value : values) { + ids[i++] = (Serializable) primary.get(value); + } + delete(conn, info, ids); + } + + @Override + public void delete(Class clazz, Serializable... ids) { + final EntityInfo info = loadEntityInfo(clazz); + if (info.isVirtualEntity()) { + delete(null, info, ids); + return; + } + Connection conn = createWriteSQLConnection(); + try { + delete(conn, info, ids); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public void delete(final DataConnection conn, Class clazz, Serializable... ids) { + delete((Connection) conn.getConnection(), loadEntityInfo(clazz), ids); + } + + private void delete(final Connection conn, final EntityInfo info, Serializable... keys) { + if (keys.length == 0) return; + try { + if (!info.isVirtualEntity()) { + String sql = "DELETE FROM " + info.getTable() + " WHERE " + info.getPrimarySQLColumn() + " IN " + formatToString(keys); + if (debug.get()) logger.finest(info.getType().getSimpleName() + " delete sql=" + sql); + final Statement stmt = conn.createStatement(); + stmt.execute(sql); + stmt.close(); + if (writeListener != null) writeListener.delete(sql); + } + //------------------------------------ + final EntityCache cache = info.getCache(); + if (cache == null) return; + for (Serializable key : keys) { + cache.delete(key); + } + if (cacheListener != null) cacheListener.deleteCache(info.getType(), keys); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Override + public void delete(Class clazz, FilterNode node) { + final EntityInfo info = loadEntityInfo(clazz); + if (info.isVirtualEntity()) { + delete(null, info, node); + return; + } + Connection conn = createWriteSQLConnection(); + try { + delete(conn, info, node); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public void delete(final DataConnection conn, Class clazz, FilterNode node) { + delete((Connection) conn.getConnection(), loadEntityInfo(clazz), node); + } + + private void delete(final Connection conn, final EntityInfo info, final FilterNode node) { + try { + if (!info.isVirtualEntity()) { + Map joinTabalis = node.getJoinTabalis(); + CharSequence join = node.createSQLJoin(this, joinTabalis, info); + CharSequence where = node.createSQLExpress(info, joinTabalis); + String sql = "DELETE " + (mysql ? "a" : "") + " FROM " + info.getTable() + " a" + (join == null ? "" : join) + ((where == null || where.length() == 0) ? "" : (" WHERE " + where)); + if (debug.get()) logger.finest(info.getType().getSimpleName() + " delete sql=" + sql); + final Statement stmt = conn.createStatement(); + stmt.execute(sql); + stmt.close(); + if (writeListener != null) writeListener.delete(sql); + } + //------------------------------------ + final EntityCache cache = info.getCache(); + if (cache == null) return; + Serializable[] ids = cache.delete(node); + if (cacheListener != null) cacheListener.deleteCache(info.getType(), ids); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void deleteCache(Class clazz, Serializable... ids) { + if (ids.length == 0) return; + final EntityInfo info = loadEntityInfo(clazz); + final EntityCache cache = info.getCache(); + if (cache == null) return; + for (Serializable id : ids) { + cache.delete(id); + } + } + + //------------------------update--------------------------- + /** + * 更新对象, 必须是Entity对象 + * + * @param + * @param values + */ + @Override + public void update(T... values) { + if (values.length == 0) return; + final EntityInfo info = loadEntityInfo((Class) values[0].getClass()); + if (info.isVirtualEntity()) { + update(null, info, values); + return; + } + Connection conn = createWriteSQLConnection(); + try { + update(conn, info, values); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public void update(final DataConnection conn, T... values) { + if (values.length == 0) return; + final EntityInfo info = loadEntityInfo((Class) values[0].getClass()); + update((Connection) conn.getConnection(), info, values); + } + + private void update(final Connection conn, final EntityInfo info, T... values) { + try { + Class clazz = info.getType(); + if (!info.isVirtualEntity()) { + if (debug.get()) logger.finest(clazz.getSimpleName() + " update sql=" + info.updateSQL); + final Attribute primary = info.getPrimary(); + final PreparedStatement prestmt = conn.prepareStatement(info.updateSQL); + Attribute[] attrs = info.updateAttributes; + String[] sqls = null; + if (writeListener == null) { + for (final T value : values) { + int i = 0; + for (Attribute attr : attrs) { + prestmt.setObject(++i, attr.get(value)); + } + prestmt.setObject(++i, primary.get(value)); + prestmt.addBatch(); + } + } else { + char[] sqlchars = info.updateSQL.toCharArray(); + sqls = new String[values.length]; + CharSequence[] ps = new CharSequence[attrs.length]; + int index = 0; + for (final T value : values) { + int i = 0; + for (Attribute attr : attrs) { + Object a = attr.get(value); + ps[i] = formatToString(a); + prestmt.setObject(++i, a); + } + prestmt.setObject(++i, primary.get(value)); + prestmt.addBatch(); + //----------------------------- + StringBuilder sb = new StringBuilder(128); + i = 0; + for (char ch : sqlchars) { + if (ch == '?') { + sb.append(ps[i++]); + } else { + sb.append(ch); + } + } + sqls[index++] = sb.toString(); + } + } + prestmt.executeBatch(); + prestmt.close(); + if (writeListener != null) writeListener.update(sqls); + } + //--------------------------------------------------- + final EntityCache cache = info.getCache(); + if (cache == null) return; + for (final T value : values) { + cache.update(value); + } + if (cacheListener != null) cacheListener.updateCache(clazz, values); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + /** + * 根据主键值更新对象的column对应的值, 必须是Entity Class + * + * @param + * @param clazz + * @param id + * @param column + * @param value + */ + @Override + public void updateColumn(Class clazz, Serializable id, String column, Serializable value) { + final EntityInfo info = loadEntityInfo(clazz); + if (info.isVirtualEntity()) { + updateColumn(null, info, id, column, value); + return; + } + Connection conn = createWriteSQLConnection(); + try { + updateColumn(conn, info, id, column, value); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public void updateColumn(DataConnection conn, Class clazz, Serializable id, String column, Serializable value) { + updateColumn((Connection) conn.getConnection(), loadEntityInfo(clazz), id, column, value); + } + + private void updateColumn(Connection conn, final EntityInfo info, Serializable id, String column, Serializable value) { + try { + if (!info.isVirtualEntity()) { + String sql = "UPDATE " + info.getTable() + " SET " + info.getSQLColumn(null, column) + " = " + + formatToString(value) + " WHERE " + info.getPrimarySQLColumn() + " = " + formatToString(id); + if (debug.get()) logger.finest(info.getType().getSimpleName() + " update sql=" + sql); + final Statement stmt = conn.createStatement(); + stmt.execute(sql); + stmt.close(); + if (writeListener != null) writeListener.update(sql); + } + //--------------------------------------------------- + final EntityCache cache = info.getCache(); + if (cache == null) return; + T rs = cache.update(id, info.getAttribute(column), value); + if (cacheListener != null) cacheListener.updateCache(info.getType(), rs); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + if (conn != null) closeSQLConnection(conn); + } + } + + /** + * 根据主键值给对象的column对应的值+incvalue, 必须是Entity Class + * + * @param + * @param clazz + * @param id + * @param column + * @param incvalue + */ + @Override + public void updateColumnIncrement(Class clazz, Serializable id, String column, long incvalue) { + final EntityInfo info = loadEntityInfo(clazz); + if (info.isVirtualEntity()) { + updateColumnIncrement(null, info, id, column, incvalue); + return; + } + Connection conn = createWriteSQLConnection(); + try { + updateColumnIncrement(conn, info, id, column, incvalue); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public void updateColumnIncrement(DataConnection conn, Class clazz, Serializable id, String column, long incvalue) { + updateColumnIncrement((Connection) conn.getConnection(), loadEntityInfo(clazz), id, column, incvalue); + } + + private void updateColumnIncrement(Connection conn, final EntityInfo info, Serializable id, String column, long incvalue) { + try { + if (!info.isVirtualEntity()) { + String col = info.getSQLColumn(null, column); + String sql = "UPDATE " + info.getTable() + " SET " + col + " = " + col + " + (" + incvalue + + ") WHERE " + info.getPrimarySQLColumn() + " = " + formatToString(id); + if (debug.get()) logger.finest(info.getType().getSimpleName() + " update sql=" + sql); + final Statement stmt = conn.createStatement(); + stmt.execute(sql); + stmt.close(); + if (writeListener != null) writeListener.update(sql); + } + //--------------------------------------------------- + final EntityCache cache = info.getCache(); + if (cache == null) return; + Attribute attr = info.getAttribute(column); + T value = cache.updateColumnIncrement(id, attr, incvalue); + if (value != null && cacheListener != null) cacheListener.updateCache(info.getType(), value); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + if (conn != null) closeSQLConnection(conn); + } + } + + /** + * 根据主键值给对象的column对应的值 & andvalue, 必须是Entity Class + * + * @param + * @param clazz + * @param id + * @param column + * @param andvalue + */ + @Override + public void updateColumnAnd(Class clazz, Serializable id, String column, long andvalue) { + final EntityInfo info = loadEntityInfo(clazz); + if (info.isVirtualEntity()) { + updateColumnAnd(null, info, id, column, andvalue); + return; + } + Connection conn = createWriteSQLConnection(); + try { + updateColumnAnd(conn, info, id, column, andvalue); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public void updateColumnAnd(DataConnection conn, Class clazz, Serializable id, String column, long andvalue) { + updateColumnAnd((Connection) conn.getConnection(), loadEntityInfo(clazz), id, column, andvalue); + } + + private void updateColumnAnd(Connection conn, final EntityInfo info, Serializable id, String column, long andvalue) { + try { + if (!info.isVirtualEntity()) { + String col = info.getSQLColumn(null, column); + String sql = "UPDATE " + info.getTable() + " SET " + col + " = " + col + " & (" + andvalue + + ") WHERE " + info.getPrimarySQLColumn() + " = " + formatToString(id); + if (debug.get()) logger.finest(info.getType().getSimpleName() + " update sql=" + sql); + final Statement stmt = conn.createStatement(); + stmt.execute(sql); + stmt.close(); + if (writeListener != null) writeListener.update(sql); + } + //--------------------------------------------------- + final EntityCache cache = info.getCache(); + if (cache == null) return; + Attribute attr = info.getAttribute(column); + T value = cache.updateColumnAnd(id, attr, andvalue); + if (value != null && cacheListener != null) cacheListener.updateCache(info.getType(), value); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + if (conn != null) closeSQLConnection(conn); + } + } + + /** + * 根据主键值给对象的column对应的值 | andvalue, 必须是Entity Class + * + * @param + * @param clazz + * @param id + * @param column + * @param orvalue + */ + @Override + public void updateColumnOr(Class clazz, Serializable id, String column, long orvalue) { + final EntityInfo info = loadEntityInfo(clazz); + if (info.isVirtualEntity()) { + updateColumnOr(null, info, id, column, orvalue); + return; + } + Connection conn = createWriteSQLConnection(); + try { + updateColumnOr(conn, info, id, column, orvalue); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public void updateColumnOr(DataConnection conn, Class clazz, Serializable id, String column, long orvalue) { + updateColumnOr((Connection) conn.getConnection(), loadEntityInfo(clazz), id, column, orvalue); + } + + private void updateColumnOr(Connection conn, final EntityInfo info, Serializable id, String column, long orvalue) { + try { + if (!info.isVirtualEntity()) { + String col = info.getSQLColumn(null, column); + String sql = "UPDATE " + info.getTable() + " SET " + col + " = " + col + " | (" + orvalue + + ") WHERE " + info.getPrimarySQLColumn() + " = " + formatToString(id); + if (debug.get()) logger.finest(info.getType().getSimpleName() + " update sql=" + sql); + final Statement stmt = conn.createStatement(); + stmt.execute(sql); + stmt.close(); + if (writeListener != null) writeListener.update(sql); + } + //--------------------------------------------------- + final EntityCache cache = info.getCache(); + if (cache == null) return; + Attribute attr = info.getAttribute(column); + T value = cache.updateColumnOr(id, attr, orvalue); + if (value != null && cacheListener != null) cacheListener.updateCache(info.getType(), value); + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + if (conn != null) closeSQLConnection(conn); + } + } + + /** + * 更新对象指定的一些字段, 必须是Entity对象 + * + * @param + * @param value + * @param columns + */ + @Override + public void updateColumns(final T value, final String... columns) { + final EntityInfo info = loadEntityInfo((Class) value.getClass()); + if (info.isVirtualEntity()) { + updateColumns(null, info, value, columns); + return; + } + Connection conn = createWriteSQLConnection(); + try { + updateColumns(conn, info, value, columns); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public void updateColumns(final DataConnection conn, final T value, final String... columns) { + updateColumns((Connection) conn.getConnection(), loadEntityInfo((Class) value.getClass()), value, columns); + } + + private void updateColumns(final Connection conn, final EntityInfo info, final T value, final String... columns) { + if (value == null || columns.length < 1) return; + try { + final Class clazz = (Class) value.getClass(); + StringBuilder setsql = new StringBuilder(); + final Serializable id = info.getPrimary().get(value); + final List> attrs = new ArrayList<>(); + final boolean virtual = info.isVirtualEntity(); + for (String col : columns) { + Attribute attr = info.getUpdateAttribute(col); + if (attr == null) continue; + attrs.add(attr); + if (!virtual) { + if (setsql.length() > 0) setsql.append(','); + setsql.append(info.getSQLColumn(null, col)).append(" = ").append(formatToString(attr.get(value))); + } + } + if (!virtual) { + String sql = "UPDATE " + info.getTable() + " SET " + setsql + " WHERE " + info.getPrimarySQLColumn() + " = " + formatToString(id); + if (debug.get()) logger.finest(value.getClass().getSimpleName() + ": " + sql); + final Statement stmt = conn.createStatement(); + stmt.execute(sql); + stmt.close(); + if (writeListener != null) writeListener.update(sql); + } + //--------------------------------------------------- + final EntityCache cache = info.getCache(); + if (cache == null) return; + T rs = cache.update(value, attrs); + if (cacheListener != null) cacheListener.updateCache(clazz, rs); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void updateCache(Class clazz, T... values) { + if (values.length == 0) return; + final EntityInfo info = loadEntityInfo(clazz); + final EntityCache cache = info.getCache(); + if (cache == null) return; + for (T value : values) { + cache.update(value); + } + } + + public void reloadCache(Class clazz, Serializable... ids) { + final EntityInfo info = loadEntityInfo(clazz); + final EntityCache cache = info.getCache(); + if (cache == null) return; + String column = info.getPrimary().field(); + for (Serializable id : ids) { + Sheet sheet = querySheet(false, true, clazz, null, FLIPPER_ONE, FilterNode.create(column, id)); + T value = sheet.isEmpty() ? null : sheet.list().get(0); + if (value != null) cache.update(value); + } + } + + //-----------------------getNumberResult----------------------------- + @Override + public Number getNumberResult(final Class entityClass, final Reckon reckon, final String column) { + return getNumberResult(entityClass, reckon, column, (FilterNode) null); + } + + @Override + public Number getNumberResult(final Class entityClass, final Reckon reckon, final String column, FilterBean bean) { + return getNumberResult(entityClass, reckon, column, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Number getNumberResult(final Class entityClass, final Reckon reckon, final String column, final FilterNode node) { + final Connection conn = createReadSQLConnection(); + try { + final EntityInfo info = loadEntityInfo(entityClass); + final EntityCache cache = info.getCache(); + if (cache != null && (info.isVirtualEntity() || cache.isFullLoaded())) { + if (node == null || node.isCacheUseable(this)) { + return cache.getNumberResult(reckon, column, node); + } + } + final Map joinTabalis = node == null ? null : node.getJoinTabalis(); + final CharSequence join = node == null ? null : node.createSQLJoin(this, joinTabalis, info); + final CharSequence where = node == null ? null : node.createSQLExpress(info, joinTabalis); + final String sql = "SELECT " + reckon.getColumn((column == null || column.isEmpty() ? "*" : ("a." + column))) + " FROM " + info.getTable() + " a" + + (join == null ? "" : join) + ((where == null || where.length() == 0) ? "" : (" WHERE " + where)); + if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(entityClass.getSimpleName() + " single sql=" + sql); + final PreparedStatement prestmt = conn.prepareStatement(sql); + Number rs = null; + ResultSet set = prestmt.executeQuery(); + if (set.next()) { + rs = (Number) set.getObject(1); + } + set.close(); + prestmt.close(); + return rs; + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + if (conn != null) closeSQLConnection(conn); + } + } + + //-----------------------getMapResult----------------------------- + @Override + public Map getMapResult(Class entityClass, final String keyColumn, Reckon reckon, final String reckonColumn) { + return getMapResult(entityClass, keyColumn, reckon, reckonColumn, (FilterNode) null); + } + + @Override + public Map getMapResult(Class entityClass, final String keyColumn, Reckon reckon, final String reckonColumn, FilterBean bean) { + return getMapResult(entityClass, keyColumn, reckon, reckonColumn, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Map getMapResult(final Class entityClass, final String keyColumn, final Reckon reckon, final String reckonColumn, FilterNode node) { + final Connection conn = createReadSQLConnection(); + try { + final EntityInfo info = loadEntityInfo(entityClass); + final EntityCache cache = info.getCache(); + if (cache != null && (info.isVirtualEntity() || cache.isFullLoaded())) { + if (node == null || node.isCacheUseable(this)) { + return cache.getMapResult(keyColumn, reckon, reckonColumn, node); + } + } + final String sqlkey = info.getSQLColumn(null, keyColumn); + final Map joinTabalis = node == null ? null : node.getJoinTabalis(); + final CharSequence join = node == null ? null : node.createSQLJoin(this, joinTabalis, info); + final CharSequence where = node == null ? null : node.createSQLExpress(info, joinTabalis); + final String sql = "SELECT a." + sqlkey + ", " + reckon.getColumn((reckonColumn == null || reckonColumn.isEmpty() ? "*" : ("a." + reckonColumn))) + + " FROM " + info.getTable() + " a" + (join == null ? "" : join) + ((where == null || where.length() == 0) ? "" : (" WHERE " + where)) + " GROUP BY a." + sqlkey; + if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(entityClass.getSimpleName() + " single sql=" + sql); + final PreparedStatement prestmt = conn.prepareStatement(sql); + Map rs = new LinkedHashMap<>(); + ResultSet set = prestmt.executeQuery(); + ResultSetMetaData rsd = set.getMetaData(); + boolean smallint = rsd.getColumnType(1) == Types.SMALLINT; + while (set.next()) { + rs.put((K) (smallint ? set.getShort(1) : set.getObject(1)), (V) set.getObject(2)); + } + set.close(); + prestmt.close(); + return rs; + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + if (conn != null) closeSQLConnection(conn); + } + } + + //-----------------------find---------------------------- + /** + * 根据主键获取对象 + * + * @param + * @param clazz + * @param pk + * @return + */ + @Override + public T find(Class clazz, Serializable pk) { + return find(clazz, (SelectColumn) null, pk); + } + + @Override + public T find(Class clazz, final SelectColumn selects, Serializable pk) { + final EntityInfo info = loadEntityInfo(clazz); + final EntityCache cache = info.getCache(); + if (cache != null && cache.isFullLoaded()) return cache.find(selects, pk); + + final Connection conn = createReadSQLConnection(); + try { + final SelectColumn sels = selects; + final String sql = "SELECT * FROM " + info.getTable() + " WHERE " + info.getPrimarySQLColumn() + " = " + formatToString(pk); + if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(clazz.getSimpleName() + " find sql=" + sql); + final PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + final ResultSet set = ps.executeQuery(); + T rs = set.next() ? info.getValue(sels, set) : null; + set.close(); + ps.close(); + return rs; + } catch (Exception ex) { + throw new RuntimeException(ex); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public T findByColumn(final Class clazz, final String column, final Serializable key) { + return find(clazz, null, FilterNode.create(column, key)); + } + + @Override + public T find(final Class clazz, final FilterBean bean) { + return find(clazz, null, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public T find(final Class clazz, final FilterNode node) { + return find(clazz, null, node); + } + + public T find(final Class clazz, final SelectColumn selects, final FilterNode node) { + final EntityInfo info = loadEntityInfo(clazz); + final EntityCache cache = info.getCache(); + if (cache != null && cache.isFullLoaded() && (node == null || node.isCacheUseable(this))) return cache.find(selects, node); + + final Connection conn = createReadSQLConnection(); + try { + final SelectColumn sels = selects; + final Map joinTabalis = node == null ? null : node.getJoinTabalis(); + final CharSequence join = node == null ? null : node.createSQLJoin(this, joinTabalis, info); + final CharSequence where = node == null ? null : node.createSQLExpress(info, joinTabalis); + final String sql = "SELECT a.* FROM " + info.getTable() + " a" + (join == null ? "" : join) + ((where == null || where.length() == 0) ? "" : (" WHERE " + where)); + if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(clazz.getSimpleName() + " find sql=" + sql); + final PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + final ResultSet set = ps.executeQuery(); + T rs = set.next() ? info.getValue(sels, set) : null; + set.close(); + ps.close(); + return rs; + } catch (Exception ex) { + throw new RuntimeException(ex); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public boolean exists(Class clazz, Serializable pk) { + final EntityInfo info = loadEntityInfo(clazz); + final EntityCache cache = info.getCache(); + if (cache != null && cache.isFullLoaded()) return cache.exists(pk); + + final Connection conn = createReadSQLConnection(); + try { + final String sql = "SELECT COUNT(*) FROM " + info.getTable() + " WHERE " + info.getPrimarySQLColumn() + " = " + formatToString(pk); + if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(clazz.getSimpleName() + " exists sql=" + sql); + final PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + final ResultSet set = ps.executeQuery(); + boolean rs = set.next() ? (set.getInt(1) > 0) : false; + set.close(); + ps.close(); + return rs; + } catch (Exception ex) { + throw new RuntimeException(ex); + } finally { + closeSQLConnection(conn); + } + } + + @Override + public boolean exists(final Class clazz, final FilterBean bean) { + return exists(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public boolean exists(final Class clazz, final FilterNode node) { + final EntityInfo info = loadEntityInfo(clazz); + final EntityCache cache = info.getCache(); + if (cache != null && cache.isFullLoaded() && (node == null || node.isCacheUseable(this))) return cache.exists(node); + + final Connection conn = createReadSQLConnection(); + try { + final Map joinTabalis = node == null ? null : node.getJoinTabalis(); + final CharSequence join = node == null ? null : node.createSQLJoin(this, joinTabalis, info); + final CharSequence where = node == null ? null : node.createSQLExpress(info, joinTabalis); + final String sql = "SELECT COUNT(" + info.getPrimarySQLColumn("a") + ") FROM " + info.getTable() + " a" + (join == null ? "" : join) + ((where == null || where.length() == 0) ? "" : (" WHERE " + where)); + if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(clazz.getSimpleName() + " exists sql=" + sql); + final PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + final ResultSet set = ps.executeQuery(); + boolean rs = set.next() ? (set.getInt(1) > 0) : false; + set.close(); + ps.close(); + return rs; + } catch (Exception ex) { + throw new RuntimeException(ex); + } finally { + closeSQLConnection(conn); + } + } + + //-----------------------list set---------------------------- + @Override + public HashSet queryColumnSet(String selectedColumn, Class clazz, String column, Serializable key) { + return queryColumnSet(selectedColumn, clazz, FilterNode.create(column, key)); + } + + @Override + public HashSet queryColumnSet(String selectedColumn, Class clazz, FilterBean bean) { + return new LinkedHashSet<>(queryColumnList(selectedColumn, clazz, bean)); + } + + @Override + public HashSet queryColumnSet(String selectedColumn, Class clazz, FilterNode node) { + return new LinkedHashSet<>(queryColumnList(selectedColumn, clazz, node)); + } + + @Override + public List queryColumnList(String selectedColumn, Class clazz, String column, Serializable key) { + return queryColumnList(selectedColumn, clazz, FilterNode.create(column, key)); + } + + @Override + public List queryColumnList(String selectedColumn, Class clazz, FilterBean bean) { + return (List) queryColumnSheet(selectedColumn, clazz, null, bean).list(true); + } + + @Override + public List queryColumnList(String selectedColumn, Class clazz, FilterNode node) { + return (List) queryColumnSheet(selectedColumn, clazz, null, node).list(true); + } + + /** + * 根据过滤对象FilterBean查询对象集合 + * + * @param + * @param + * @param clazz + * @param bean + * @return + */ + @Override + public Map queryMap(final Class clazz, final FilterBean bean) { + return queryMap(clazz, null, bean); + } + + @Override + public Map queryMap(final Class clazz, final FilterNode node) { + return queryMap(clazz, null, node); + } + + @Override + public Map queryMap(final Class clazz, final SelectColumn selects, final FilterBean bean) { + return formatMap(clazz, queryList(clazz, selects, null, bean)); + } + + @Override + public Map queryMap(final Class clazz, final SelectColumn selects, final FilterNode node) { + return formatMap(clazz, queryList(clazz, selects, null, node)); + } + + private Map formatMap(final Class clazz, Collection list) { + Map map = new LinkedHashMap<>(); + if (list == null || list.isEmpty()) return map; + final Attribute attr = (Attribute) loadEntityInfo(clazz).getPrimary(); + for (T t : list) { + map.put(attr.get(t), t); + } + return map; + } + + /** + * 根据指定字段值查询对象集合 + * + * @param + * @param clazz + * @param column + * @param key + * @return + */ + @Override + public List queryList(Class clazz, String column, Serializable key) { + return queryList(clazz, FilterNode.create(column, key)); + } + + /** + * 根据过滤对象FilterBean查询对象集合 + * + * @param + * @param clazz + * @param bean + * @return + */ + @Override + public List queryList(final Class clazz, final FilterBean bean) { + return queryList(clazz, (SelectColumn) null, bean); + } + + @Override + public List queryList(final Class clazz, final FilterNode node) { + return queryList(clazz, (SelectColumn) null, node); + } + + /** + * 根据过滤对象FilterBean查询对象集合, 对象只填充或排除SelectField指定的字段 + * + * @param + * @param clazz + * @param selects + * @param bean + * @return + */ + @Override + public List queryList(final Class clazz, final SelectColumn selects, final FilterBean bean) { + return queryList(clazz, selects, (Flipper) null, bean); + } + + @Override + public List queryList(final Class clazz, final SelectColumn selects, final FilterNode node) { + return queryList(clazz, selects, (Flipper) null, node); + } + + @Override + public List queryList(Class clazz, final Flipper flipper, String column, Serializable key) { + return queryList(clazz, flipper, FilterNode.create(column, key)); + } + + @Override + public List queryList(final Class clazz, final Flipper flipper, final FilterNode node) { + return queryList(clazz, null, flipper, node); + } + + @Override + public List queryList(final Class clazz, final Flipper flipper, final FilterBean bean) { + return queryList(clazz, null, flipper, bean); + } + + @Override + public List queryList(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) { + return querySheet(true, false, clazz, selects, flipper, node).list(true); + } + + @Override + public List queryList(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean) { + return querySheet(true, false, clazz, selects, flipper, FilterNodeBean.createFilterNode(bean)).list(true); + } + + //-----------------------sheet---------------------------- + /** + * 根据指定参数查询对象某个字段的集合 + *

+ * @param + * @param + * @param selectedColumn + * @param clazz + * @param flipper + * @param bean + * @return + */ + @Override + public Sheet queryColumnSheet(String selectedColumn, Class clazz, final Flipper flipper, final FilterBean bean) { + return queryColumnSheet(selectedColumn, clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Sheet queryColumnSheet(String selectedColumn, Class clazz, final Flipper flipper, final FilterNode node) { + Sheet sheet = querySheet(true, true, clazz, SelectColumn.createIncludes(selectedColumn), flipper, node); + final Sheet rs = new Sheet<>(); + if (sheet.isEmpty()) return rs; + rs.setTotal(sheet.getTotal()); + final EntityInfo info = loadEntityInfo(clazz); + final Attribute selected = (Attribute) info.getAttribute(selectedColumn); + final List list = new ArrayList<>(); + for (T t : sheet.getRows()) { + list.add(selected.get(t)); + } + rs.setRows(list); + return rs; + } + + /** + * 根据过滤对象FilterBean和翻页对象Flipper查询一页的数据 + * + * @param + * @param clazz + * @param flipper + * @param bean + * @return + */ + @Override + public Sheet querySheet(Class clazz, final Flipper flipper, final FilterBean bean) { + return querySheet(clazz, null, flipper, bean); + } + + @Override + public Sheet querySheet(Class clazz, final Flipper flipper, final FilterNode node) { + return querySheet(clazz, null, flipper, node); + } + + /** + * 根据过滤对象FilterBean和翻页对象Flipper查询一页的数据, 对象只填充或排除SelectField指定的字段 + * + * @param + * @param clazz + * @param selects + * @param flipper + * @param bean + * @return + */ + @Override + public Sheet querySheet(Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean) { + return querySheet(true, true, clazz, selects, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Sheet querySheet(Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) { + return querySheet(true, true, clazz, selects, flipper, node); + } + + private Sheet querySheet(boolean readcache, boolean needtotal, Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) { + final EntityInfo info = loadEntityInfo(clazz); + final EntityCache cache = info.getCache(); + if (readcache && cache != null) { + if (node == null || node.isCacheUseable(this)) { + if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(clazz.getSimpleName() + " cache query predicate = " + (node == null ? null : node.createPredicate(cache))); + Sheet sheet = cache.querySheet(needtotal, selects, flipper, node); + if (!sheet.isEmpty() || info.isVirtualEntity() || cache.isFullLoaded()) return sheet; + } + } + final Connection conn = createReadSQLConnection(); + try { + final SelectColumn sels = selects; + final List list = new ArrayList(); + final Map joinTabalis = node == null ? null : node.getJoinTabalis(); + final CharSequence join = node == null ? null : node.createSQLJoin(this, joinTabalis, info); + final CharSequence where = node == null ? null : node.createSQLExpress(info, joinTabalis); + final String sql = "SELECT a.* FROM " + info.getTable() + " a" + (join == null ? "" : join) + + ((where == null || where.length() == 0) ? "" : (" WHERE " + where)) + info.createSQLOrderby(flipper); + if (debug.get() && info.isLoggable(Level.FINEST)) + logger.finest(clazz.getSimpleName() + " query sql=" + sql + (flipper == null ? "" : (" LIMIT " + flipper.index() + "," + flipper.getSize()))); + final PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + final ResultSet set = ps.executeQuery(); + if (flipper != null && flipper.index() > 0) set.absolute(flipper.index()); + final int limit = flipper == null ? Integer.MAX_VALUE : flipper.getSize(); + int i = 0; + while (set.next()) { + i++; + list.add(info.getValue(sels, set)); + if (limit <= i) break; + } + long total = list.size(); + if (needtotal && flipper != null) { + set.last(); + total = set.getRow(); + } + set.close(); + ps.close(); + return new Sheet<>(total, list); + } catch (Exception ex) { + throw new RuntimeException(ex); + } finally { + closeSQLConnection(conn); + } + } +} diff --git a/src/org/redkale/source/DataSQLListener.java b/src/org/redkale/source/DataSQLListener.java new file mode 100644 index 000000000..8dbaca9c6 --- /dev/null +++ b/src/org/redkale/source/DataSQLListener.java @@ -0,0 +1,20 @@ +/* + * 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.source; + +/** + * @Resource(name = "property.datasource.nodeid") + * + * @author zhangjx + */ +public interface DataSQLListener { + + public void insert(String... sqls); + + public void update(String... sqls); + + public void delete(String... sqls); +} diff --git a/src/org/redkale/source/DataSource.java b/src/org/redkale/source/DataSource.java new file mode 100644 index 000000000..94813b223 --- /dev/null +++ b/src/org/redkale/source/DataSource.java @@ -0,0 +1,244 @@ +/* + * 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.source; + +import java.io.*; +import java.util.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public interface DataSource { + + public static enum Reckon { + + AVG, COUNT, DISTINCTCOUNT, MAX, MIN, SUM; + + public String getColumn(String col) { + if (this == DISTINCTCOUNT) return "COUNT(DISTINCT " + col + ")"; + return this.name() + "(" + col + ")"; + } + } + + /** + * 创建读连接 + * + * @return + */ + public DataConnection createReadConnection(); + + /** + * 创建写连接 + * + * @return + */ + public DataConnection createWriteConnection(); + + //----------------------insert----------------------------- + /** + * 新增对象, 必须是Entity对象 + * + * @param + * @param values + */ + public void insert(T... values); + + public void insert(final DataConnection conn, T... values); + + /** + * 将entity的对象全部加载到Cache中去,如果clazz没有被@javax.persistence.Cacheable注解则不做任何事 + *

+ * @param + * @param clazz + */ + public void refreshCache(Class clazz); + + //-------------------------delete-------------------------- + /** + * 删除对象, 必须是Entity对象 + * + * @param + * @param values + */ + public void delete(T... values); + + public void delete(final DataConnection conn, T... values); + + public void delete(Class clazz, Serializable... ids); + + public void delete(final DataConnection conn, Class clazz, Serializable... ids); + + public void delete(Class clazz, FilterNode node); + + public void delete(final DataConnection conn, Class clazz, FilterNode node); + + //------------------------update--------------------------- + /** + * 更新对象, 必须是Entity对象 + * + * @param + * @param values + */ + public void update(T... values); + + public void update(final DataConnection conn, T... values); + + public void updateColumn(Class clazz, Serializable id, String column, Serializable value); + + public void updateColumn(DataConnection conn, Class clazz, Serializable id, String column, Serializable value); + + public void updateColumns(final T value, final String... columns); + + public void updateColumns(final DataConnection conn, final T value, final String... columns); + + public void updateColumnIncrement(final Class clazz, Serializable id, String column, long incvalue); + + public void updateColumnIncrement(final DataConnection conn, Class clazz, Serializable id, String column, long incvalue); + + public void updateColumnAnd(final Class clazz, Serializable id, String column, long incvalue); + + public void updateColumnAnd(final DataConnection conn, Class clazz, Serializable id, String column, long incvalue); + + public void updateColumnOr(final Class clazz, Serializable id, String column, long incvalue); + + public void updateColumnOr(final DataConnection conn, Class clazz, Serializable id, String column, long incvalue); + + //-----------------------getXXXXResult----------------------------- + public Number getNumberResult(final Class entityClass, final Reckon reckon, final String column); + + public Number getNumberResult(final Class entityClass, final Reckon reckon, final String column, FilterBean bean); + + public Number getNumberResult(final Class entityClass, final Reckon reckon, final String column, FilterNode node); + + public Map getMapResult(Class entityClass, final String keyColumn, Reckon reckon, final String reckonColumn); + + public Map getMapResult(Class entityClass, final String keyColumn, Reckon reckon, final String reckonColumn, FilterBean bean); + + public Map getMapResult(Class entityClass, final String keyColumn, Reckon reckon, final String reckonColumn, FilterNode node); + + //-----------------------find---------------------------- + /** + * 根据主键获取对象 + * + * @param + * @param clazz + * @param pk + * @return + */ + public T find(Class clazz, Serializable pk); + + public T find(Class clazz, final SelectColumn selects, Serializable pk); + + public T findByColumn(Class clazz, String column, Serializable key); + + public T find(final Class clazz, final FilterNode node); + + public T find(final Class clazz, final FilterBean bean); + + public boolean exists(Class clazz, Serializable pk); + + public boolean exists(final Class clazz, final FilterNode node); + + public boolean exists(final Class clazz, final FilterBean bean); + + //-----------------------list set---------------------------- + /** + * 根据指定字段值查询对象某个字段的集合 + * + * @param + * @param + * @param selectedColumn + * @param clazz + * @param column + * @param key + * @return + */ + public HashSet queryColumnSet(String selectedColumn, Class clazz, String column, Serializable key); + + public HashSet queryColumnSet(String selectedColumn, Class clazz, FilterNode node); + + public HashSet queryColumnSet(String selectedColumn, Class clazz, FilterBean bean); + + public List queryColumnList(String selectedColumn, Class clazz, String column, Serializable key); + + public List queryColumnList(String selectedColumn, Class clazz, FilterNode node); + + public List queryColumnList(String selectedColumn, Class clazz, FilterBean bean); + + /** + * Map 接口 + *

+ * @param + * @param + * @param clazz + * @param node + * @return + */ + public Map queryMap(final Class clazz, final FilterNode node); + + public Map queryMap(final Class clazz, final FilterBean bean); + + public Map queryMap(final Class clazz, final SelectColumn selects, final FilterNode node); + + public Map queryMap(final Class clazz, final SelectColumn selects, final FilterBean bean); + + /** + * 根据指定字段值查询对象集合 + * + * @param + * @param clazz + * @param column + * @param key + * @return + */ + public List queryList(final Class clazz, final String column, final Serializable key); + + public List queryList(final Class clazz, final FilterNode node); + + public List queryList(final Class clazz, final FilterBean bean); + + public List queryList(final Class clazz, final SelectColumn selects, final FilterNode node); + + public List queryList(final Class clazz, final SelectColumn selects, final FilterBean bean); + + public List queryList(final Class clazz, final Flipper flipper, final String column, final Serializable key); + + public List queryList(final Class clazz, final Flipper flipper, final FilterNode node); + + public List queryList(final Class clazz, final Flipper flipper, final FilterBean bean); + + public List queryList(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node); + + public List queryList(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean); + + //-----------------------sheet---------------------------- + /** + * 根据指定参数查询对象某个字段的集合 + *

+ * @param + * @param + * @param selectedColumn + * @param clazz + * @param flipper + * @param bean + * @return + */ + public Sheet queryColumnSheet(final String selectedColumn, final Class clazz, final Flipper flipper, final FilterBean bean); + + public Sheet queryColumnSheet(final String selectedColumn, final Class clazz, final Flipper flipper, final FilterNode node); + + public Sheet querySheet(final Class clazz, final Flipper flipper, final FilterBean bean); + + public Sheet querySheet(final Class clazz, final Flipper flipper, final FilterNode node); + + public Sheet querySheet(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean); + + public Sheet querySheet(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node); + +} diff --git a/src/org/redkale/source/DistributeGenerator.java b/src/org/redkale/source/DistributeGenerator.java new file mode 100644 index 000000000..020bcc6c7 --- /dev/null +++ b/src/org/redkale/source/DistributeGenerator.java @@ -0,0 +1,44 @@ +/* + * 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.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * @author zhangjx + */ +@Target({FIELD}) +@Retention(RUNTIME) +public @interface DistributeGenerator { + + /** + * 当使用DistributeGenerator控制主键值时, 如果表A删除的数据迁移到表B时, 就需要将表A的class标记: + * DistributeTables({B.class}) + * public class A { + * } + * 这样DistributeGenerator将从A、B表中取最大值来初始化主键值。 + * + * @author zhangjx + */ + @Target({TYPE}) + @Retention(RUNTIME) + public @interface DistributeTables { + + Class[] value(); + } + + long initialValue() default 1; + + /** + * 如果allocationSize的值小于或等于1,则主键不会加上nodeid + *

+ * @return + */ + int allocationSize() default 1000; +} diff --git a/src/org/redkale/source/EntityCache.java b/src/org/redkale/source/EntityCache.java new file mode 100644 index 000000000..8ee93bec4 --- /dev/null +++ b/src/org/redkale/source/EntityCache.java @@ -0,0 +1,654 @@ +/* + * 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.source; + +import java.io.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.function.*; +import java.util.logging.*; +import java.util.stream.Stream; +import javax.persistence.Transient; +import org.redkale.source.DataSource.Reckon; +import static org.redkale.source.DataSource.Reckon.*; +import java.util.stream.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + * @param + */ +public final class EntityCache { + + private static final Logger logger = Logger.getLogger(EntityCache.class.getName()); + + private final ConcurrentHashMap map = new ConcurrentHashMap(); + + private final Collection list = new ConcurrentLinkedQueue(); // CopyOnWriteArrayList 插入慢、查询快; 10w数据插入需要3.2秒; ConcurrentLinkedQueue 插入快、查询慢;10w数据查询需要 0.062秒, 查询慢40%; + + private final Map> sortComparators = new ConcurrentHashMap<>(); + + private final Class type; + + private final boolean needcopy; + + private final Creator creator; + + private final Attribute primary; + + private final Reproduce reproduce; + + private boolean fullloaded; + + final EntityInfo info; + + public EntityCache(final EntityInfo info) { + this.info = info; + this.type = info.getType(); + this.creator = info.getCreator(); + this.primary = info.primary; + this.needcopy = true; + this.reproduce = Reproduce.create(type, type, (m) -> { + try { + return type.getDeclaredField(m).getAnnotation(Transient.class) == null; + } catch (Exception e) { + return true; + } + }); + } + + public void fullLoad(List all) { + if (all == null) return; + clear(); + all.stream().filter(x -> x != null).forEach(x -> { + this.map.put(this.primary.get(x), x); + }); + this.list.addAll(all); + this.fullloaded = true; + } + + public Class getType() { + return type; + } + + public void clear() { + this.fullloaded = false; + this.list.clear(); + this.map.clear(); + } + + public boolean isFullLoaded() { + return fullloaded; + } + + public T find(Serializable id) { + if (id == null) return null; + T rs = map.get(id); + return rs == null ? null : (needcopy ? reproduce.copy(this.creator.create(), rs) : rs); + } + + public T find(final SelectColumn selects, final Serializable id) { + if (id == null) return null; + T rs = map.get(id); + if (rs == null) return null; + if (selects == null) return (needcopy ? reproduce.copy(this.creator.create(), rs) : rs); + T t = this.creator.create(); + for (Attribute attr : this.info.attributes) { + if (selects.test(attr.field())) attr.set(t, attr.get(rs)); + } + return t; + } + + public T find(final SelectColumn selects, FilterNode node) { + final Predicate filter = node == null ? null : node.createPredicate(this); + Stream stream = this.list.stream(); + if (filter != null) stream = stream.filter(filter); + Optional opt = stream.findFirst(); + if (!opt.isPresent()) return null; + if (selects == null) return (needcopy ? reproduce.copy(this.creator.create(), opt.get()) : opt.get()); + T rs = opt.get(); + T t = this.creator.create(); + for (Attribute attr : this.info.attributes) { + if (selects.test(attr.field())) attr.set(t, attr.get(rs)); + } + return t; + } + + public boolean exists(Serializable id) { + if (id == null) return false; + final Class atype = this.primary.type(); + if (id.getClass() != atype && id instanceof Number) { + if (atype == int.class || atype == Integer.class) { + id = ((Number) id).intValue(); + } else if (atype == long.class || atype == Long.class) { + id = ((Number) id).longValue(); + } else if (atype == short.class || atype == Short.class) { + id = ((Number) id).shortValue(); + } else if (atype == float.class || atype == Float.class) { + id = ((Number) id).floatValue(); + } else if (atype == byte.class || atype == Byte.class) { + id = ((Number) id).byteValue(); + } else if (atype == double.class || atype == Double.class) { + id = ((Number) id).doubleValue(); + } + } + return this.map.containsKey(id); + } + + public boolean exists(FilterNode node) { + final Predicate filter = node == null ? null : node.createPredicate(this); + Stream stream = this.list.stream(); + if (filter != null) stream = stream.filter(filter); + return stream.findFirst().isPresent(); + } + + public boolean exists(final Predicate filter) { + return (filter != null) && this.list.stream().filter(filter).findFirst().isPresent(); + } + + public Map getMapResult(final String keyColumn, final Reckon reckon, final String reckonColumn, FilterNode node) { + final Attribute keyAttr = info.getAttribute(keyColumn); + final Predicate filter = node == null ? null : node.createPredicate(this); + final Attribute reckonAttr = reckonColumn == null ? null : info.getAttribute(reckonColumn); + Stream stream = this.list.stream(); + if (filter != null) stream = stream.filter(filter); + Collector collector = null; + final Class valtype = reckonAttr == null ? null : reckonAttr.type(); + switch (reckon) { + case AVG: + if (valtype == float.class || valtype == Float.class || valtype == double.class || valtype == Double.class) { + collector = (Collector) Collectors.averagingDouble((T t) -> ((Number) reckonAttr.get(t)).doubleValue()); + } else { + collector = (Collector) Collectors.averagingLong((T t) -> ((Number) reckonAttr.get(t)).longValue()); + } + break; + case COUNT: collector = (Collector) Collectors.counting(); + break; + case DISTINCTCOUNT: + collector = (Collector) Collectors.mapping((t) -> reckonAttr.get(t), Collectors.toSet()); + break; + case MAX: + case MIN: + Comparator comp = (o1, o2) -> o1 == null ? (o2 == null ? 0 : -1) : ((Comparable) reckonAttr.get(o1)).compareTo(reckonAttr.get(o2)); + collector = (Collector) ((reckon == MAX) ? Collectors.maxBy(comp) : Collectors.minBy(comp)); + break; + case SUM: + if (valtype == float.class || valtype == Float.class || valtype == double.class || valtype == Double.class) { + collector = (Collector) Collectors.summingDouble((T t) -> ((Number) reckonAttr.get(t)).doubleValue()); + } else { + collector = (Collector) Collectors.summingLong((T t) -> ((Number) reckonAttr.get(t)).longValue()); + } + break; + } + Map rs = stream.collect(Collectors.groupingBy(t -> keyAttr.get(t), LinkedHashMap::new, collector)); + if (reckon == MAX || reckon == MIN) { + Map rs2 = new LinkedHashMap(); + rs.forEach((x, y) -> { + if (((Optional) y).isPresent()) rs2.put(x, reckonAttr.get((T) ((Optional) y).get())); + }); + rs = rs2; + } else if (reckon == DISTINCTCOUNT) { + Map rs2 = new LinkedHashMap(); + rs.forEach((x, y) -> rs2.put(x, ((Set) y).size())); + rs = rs2; + } + return rs; + } + + public Number getNumberResult(final Reckon reckon, final String column, FilterNode node) { + final Attribute attr = column == null ? null : info.getAttribute(column); + final Predicate filter = node == null ? null : node.createPredicate(this); + Stream stream = this.list.stream(); + if (filter != null) stream = stream.filter(filter); + switch (reckon) { + case AVG: + if (attr.type() == int.class || attr.type() == Integer.class) { + return (int) stream.mapToInt(x -> (Integer) attr.get(x)).average().orElse(0); + } else if (attr.type() == long.class || attr.type() == Long.class) { + return (long) stream.mapToLong(x -> (Long) attr.get(x)).average().orElse(0); + } else if (attr.type() == short.class || attr.type() == Short.class) { + return (short) stream.mapToInt(x -> ((Short) attr.get(x)).intValue()).average().orElse(0); + } else if (attr.type() == float.class || attr.type() == Float.class) { + return (float) stream.mapToDouble(x -> ((Float) attr.get(x)).doubleValue()).average().orElse(0); + } else if (attr.type() == double.class || attr.type() == Double.class) { + return stream.mapToDouble(x -> (Double) attr.get(x)).average().orElse(0); + } + throw new RuntimeException("getNumberResult error(type:" + type + ", attr.declaringClass: " + attr.declaringClass() + ", attr.field: " + attr.field() + ", attr.type: " + attr.type()); + case COUNT: return stream.count(); + case DISTINCTCOUNT: return stream.map(x -> attr.get(x)).distinct().count(); + + case MAX: + if (attr.type() == int.class || attr.type() == Integer.class) { + return stream.mapToInt(x -> (Integer) attr.get(x)).max().orElse(0); + } else if (attr.type() == long.class || attr.type() == Long.class) { + return stream.mapToLong(x -> (Long) attr.get(x)).max().orElse(0); + } else if (attr.type() == short.class || attr.type() == Short.class) { + return (short) stream.mapToInt(x -> ((Short) attr.get(x)).intValue()).max().orElse(0); + } else if (attr.type() == float.class || attr.type() == Float.class) { + return (float) stream.mapToDouble(x -> ((Float) attr.get(x)).doubleValue()).max().orElse(0); + } else if (attr.type() == double.class || attr.type() == Double.class) { + return stream.mapToDouble(x -> (Double) attr.get(x)).max().orElse(0); + } + throw new RuntimeException("getNumberResult error(type:" + type + ", attr.declaringClass: " + attr.declaringClass() + ", attr.field: " + attr.field() + ", attr.type: " + attr.type()); + + case MIN: + if (attr.type() == int.class || attr.type() == Integer.class) { + return stream.mapToInt(x -> (Integer) attr.get(x)).min().orElse(0); + } else if (attr.type() == long.class || attr.type() == Long.class) { + return stream.mapToLong(x -> (Long) attr.get(x)).min().orElse(0); + } else if (attr.type() == short.class || attr.type() == Short.class) { + return (short) stream.mapToInt(x -> ((Short) attr.get(x)).intValue()).min().orElse(0); + } else if (attr.type() == float.class || attr.type() == Float.class) { + return (float) stream.mapToDouble(x -> ((Float) attr.get(x)).doubleValue()).min().orElse(0); + } else if (attr.type() == double.class || attr.type() == Double.class) { + return stream.mapToDouble(x -> (Double) attr.get(x)).min().orElse(0); + } + throw new RuntimeException("getNumberResult error(type:" + type + ", attr.declaringClass: " + attr.declaringClass() + ", attr.field: " + attr.field() + ", attr.type: " + attr.type()); + + case SUM: + if (attr.type() == int.class || attr.type() == Integer.class) { + return stream.mapToInt(x -> (Integer) attr.get(x)).sum(); + } else if (attr.type() == long.class || attr.type() == Long.class) { + return stream.mapToLong(x -> (Long) attr.get(x)).sum(); + } else if (attr.type() == short.class || attr.type() == Short.class) { + return (short) stream.mapToInt(x -> ((Short) attr.get(x)).intValue()).sum(); + } else if (attr.type() == float.class || attr.type() == Float.class) { + return (float) stream.mapToDouble(x -> ((Float) attr.get(x)).doubleValue()).sum(); + } else if (attr.type() == double.class || attr.type() == Double.class) { + return stream.mapToDouble(x -> (Double) attr.get(x)).sum(); + } + throw new RuntimeException("getNumberResult error(type:" + type + ", attr.declaringClass: " + attr.declaringClass() + ", attr.field: " + attr.field() + ", attr.type: " + attr.type()); + } + return -1; + } + + public Sheet querySheet(final SelectColumn selects, final Flipper flipper, final FilterNode node) { + return querySheet(true, selects, flipper, node); + } + + public Sheet querySheet(final boolean needtotal, final SelectColumn selects, final Flipper flipper, FilterNode node) { + final Predicate filter = node == null ? null : node.createPredicate(this); + final Comparator comparator = createComparator(flipper); + long total = 0; + if (needtotal) { + Stream stream = this.list.stream(); + if (filter != null) stream = stream.filter(filter); + total = stream.count(); + } + if (needtotal && total == 0) return new Sheet<>(); + Stream stream = this.list.stream(); + if (filter != null) stream = stream.filter(filter); + if (comparator != null) stream = stream.sorted(comparator); + if (flipper != null) stream = stream.skip(flipper.index()).limit(flipper.getSize()); + final List rs = new ArrayList<>(); + if (selects == null) { + Consumer action = x -> rs.add(needcopy ? reproduce.copy(creator.create(), x) : x); + if (comparator != null) { + stream.forEachOrdered(action); + } else { + stream.forEach(action); + } + } else { + final List> attrs = new ArrayList<>(); + info.forEachAttribute((k, v) -> { + if (selects.test(k)) attrs.add(v); + }); + Consumer action = x -> { + final T item = creator.create(); + for (Attribute attr : attrs) { + attr.set(item, attr.get(x)); + } + rs.add(item); + }; + if (comparator != null) { + stream.forEachOrdered(action); + } else { + stream.forEach(action); + } + } + if (!needtotal) total = rs.size(); + return new Sheet<>(total, rs); + } + + public void insert(T value) { + if (value == null) return; + final T rs = reproduce.copy(this.creator.create(), value); //确保同一主键值的map与list中的对象必须共用。 + T old = this.map.put(this.primary.get(rs), rs); + if (old == null) { + this.list.add(rs); + } else { + logger.log(Level.WARNING, "cache repeat insert data: " + value); + } + } + + public void delete(final Serializable id) { + if (id == null) return; + final T rs = this.map.remove(id); + if (rs != null) this.list.remove(rs); + } + + public Serializable[] delete(final FilterNode node) { + if (node == null || this.list.isEmpty()) return new Serializable[0]; + Object[] rms = this.list.stream().filter(node.createPredicate(this)).toArray(); + Serializable[] ids = new Serializable[rms.length]; + int i = -1; + for (Object o : rms) { + final T t = (T) o; + ids[++i] = this.primary.get(t); + this.map.remove(ids[i]); + this.list.remove(t); + } + return ids; + } + + public void update(final T value) { + if (value == null) return; + T rs = this.map.get(this.primary.get(value)); + if (rs == null) return; + this.reproduce.copy(rs, value); + } + + public T update(final T value, Collection> attrs) { + if (value == null) return value; + T rs = this.map.get(this.primary.get(value)); + if (rs == null) return rs; + for (Attribute attr : attrs) { + attr.set(rs, attr.get(value)); + } + return rs; + } + + public T update(final Serializable id, Attribute attr, final V fieldValue) { + if (id == null) return null; + T rs = this.map.get(id); + if (rs != null) attr.set(rs, fieldValue); + return rs; + } + + public T updateColumnOr(final Serializable id, Attribute attr, final long orvalue) { + if (id == null) return null; + T rs = this.map.get(id); + if (rs == null) return rs; + Number numb = (Number) attr.get(rs); + return updateColumnIncrAndOr(attr, rs, (numb == null) ? orvalue : (numb.longValue() | orvalue)); + } + + public T updateColumnAnd(final Serializable id, Attribute attr, final long andvalue) { + if (id == null) return null; + T rs = this.map.get(id); + if (rs == null) return rs; + Number numb = (Number) attr.get(rs); + return updateColumnIncrAndOr(attr, rs, (numb == null) ? 0 : (numb.longValue() & andvalue)); + } + + public T updateColumnIncrement(final Serializable id, Attribute attr, final long incvalue) { + if (id == null) return null; + T rs = this.map.get(id); + if (rs == null) return rs; + Number numb = (Number) attr.get(rs); + return updateColumnIncrAndOr(attr, rs, (numb == null) ? incvalue : (numb.longValue() + incvalue)); + } + + private T updateColumnIncrAndOr(Attribute attr, final T rs, Number numb) { + final Class ft = attr.type(); + if (ft == int.class || ft == Integer.class) { + numb = numb.intValue(); + } else if (ft == long.class || ft == Long.class) { + numb = numb.longValue(); + } else if (ft == short.class || ft == Short.class) { + numb = numb.shortValue(); + } else if (ft == float.class || ft == Float.class) { + numb = numb.floatValue(); + } else if (ft == double.class || ft == Double.class) { + numb = numb.doubleValue(); + } else if (ft == byte.class || ft == Byte.class) { + numb = numb.byteValue(); + } + attr.set(rs, (V) numb); + return rs; + } + + public Attribute getAttribute(String fieldname) { + return info.getAttribute(fieldname); + } + + //------------------------------------------------------------------------------------------------------------------------------- + protected Comparator createComparator(Flipper flipper) { + if (flipper == null || flipper.getSort() == null || flipper.getSort().isEmpty()) return null; + final String sort = flipper.getSort(); + Comparator comparator = this.sortComparators.get(sort); + if (comparator != null) return comparator; + for (String item : sort.split(",")) { + if (item.trim().isEmpty()) continue; + String[] sub = item.trim().split("\\s+"); + int pos = sub[0].indexOf('('); + Attribute attr; + if (pos <= 0) { + attr = getAttribute(sub[0]); + } else { //含SQL函数 + int pos2 = sub[0].lastIndexOf(')'); + final Attribute pattr = getAttribute(sub[0].substring(pos + 1, pos2)); + final String func = sub[0].substring(0, pos); + if ("ABS".equalsIgnoreCase(func)) { + if (pattr.type() == int.class || pattr.type() == Integer.class) { + attr = new Attribute() { + + @Override + public Class type() { + return pattr.type(); + } + + @Override + public Class declaringClass() { + return pattr.declaringClass(); + } + + @Override + public String field() { + return pattr.field(); + } + + @Override + public Serializable get(T obj) { + return Math.abs(((Number) pattr.get(obj)).intValue()); + } + + @Override + public void set(T obj, Serializable value) { + pattr.set(obj, value); + } + }; + } else if (pattr.type() == long.class || pattr.type() == Long.class) { + attr = new Attribute() { + + @Override + public Class type() { + return pattr.type(); + } + + @Override + public Class declaringClass() { + return pattr.declaringClass(); + } + + @Override + public String field() { + return pattr.field(); + } + + @Override + public Serializable get(T obj) { + return Math.abs(((Number) pattr.get(obj)).longValue()); + } + + @Override + public void set(T obj, Serializable value) { + pattr.set(obj, value); + } + }; + } else if (pattr.type() == float.class || pattr.type() == Float.class) { + attr = new Attribute() { + + @Override + public Class type() { + return pattr.type(); + } + + @Override + public Class declaringClass() { + return pattr.declaringClass(); + } + + @Override + public String field() { + return pattr.field(); + } + + @Override + public Serializable get(T obj) { + return Math.abs(((Number) pattr.get(obj)).floatValue()); + } + + @Override + public void set(T obj, Serializable value) { + pattr.set(obj, value); + } + }; + } else if (pattr.type() == double.class || pattr.type() == Double.class) { + attr = new Attribute() { + + @Override + public Class type() { + return pattr.type(); + } + + @Override + public Class declaringClass() { + return pattr.declaringClass(); + } + + @Override + public String field() { + return pattr.field(); + } + + @Override + public Serializable get(T obj) { + return Math.abs(((Number) pattr.get(obj)).doubleValue()); + } + + @Override + public void set(T obj, Serializable value) { + pattr.set(obj, value); + } + }; + } else { + throw new RuntimeException("Flipper not supported sort illegal type by ABS (" + flipper.getSort() + ")"); + } + } else if (func.isEmpty()) { + attr = pattr; + } else { + throw new RuntimeException("Flipper not supported sort illegal function (" + flipper.getSort() + ")"); + } + } + Comparator c = (sub.length > 1 && sub[1].equalsIgnoreCase("DESC")) ? (T o1, T o2) -> { + Comparable c1 = (Comparable) attr.get(o1); + Comparable c2 = (Comparable) attr.get(o2); + return c2 == null ? -1 : c2.compareTo(c1); + } : (T o1, T o2) -> { + Comparable c1 = (Comparable) attr.get(o1); + Comparable c2 = (Comparable) attr.get(o2); + return c1 == null ? -1 : c1.compareTo(c2); + }; + + if (comparator == null) { + comparator = c; + } else { + comparator = comparator.thenComparing(c); + } + } + this.sortComparators.put(sort, comparator); + return comparator; + } + + private static class UniqueSequence implements Serializable { + + private final Serializable[] value; + + public UniqueSequence(Serializable[] val) { + this.value = val; + } + + @Override + public int hashCode() { + return Arrays.deepHashCode(this.value); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final UniqueSequence other = (UniqueSequence) obj; + if (value.length != other.value.length) return false; + for (int i = 0; i < value.length; i++) { + if (!value[i].equals(other.value[i])) return false; + } + return true; + } + + } + + private static interface UniqueAttribute extends Predicate { + + public Serializable getValue(T bean); + + @Override + public boolean test(FilterNode node); + + public static UniqueAttribute create(final Attribute[] attributes) { + if (attributes.length == 1) { + final Attribute attribute = attributes[0]; + return new UniqueAttribute() { + + @Override + public Serializable getValue(T bean) { + return attribute.get(bean); + } + + @Override + public boolean test(FilterNode node) { + if (node == null || node.isOr()) return false; + if (!attribute.field().equals(node.column)) return false; + if (node.nodes == null) return true; + for (FilterNode n : node.nodes) { + if (!test(n)) return false; + } + return true; + } + }; + } else { + return new UniqueAttribute() { + + @Override + public Serializable getValue(T bean) { + final Serializable[] rs = new Serializable[attributes.length]; + for (int i = 0; i < rs.length; i++) { + rs[i] = attributes[i].get(bean); + } + return new UniqueSequence(rs); + } + + @Override + public boolean test(FilterNode node) { + return true; + } + }; + } + } + } + +} diff --git a/src/org/redkale/source/EntityInfo.java b/src/org/redkale/source/EntityInfo.java new file mode 100644 index 000000000..82eaf2df6 --- /dev/null +++ b/src/org/redkale/source/EntityInfo.java @@ -0,0 +1,371 @@ +/* + * 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.source; + +import com.sun.istack.internal.logging.Logger; +import java.io.Serializable; +import java.lang.reflect.*; +import java.sql.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.*; +import java.util.logging.*; +import javax.persistence.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + * @param + */ +@SuppressWarnings("unchecked") +public final class EntityInfo { + + private static final ConcurrentHashMap entityInfos = new ConcurrentHashMap<>(); + + private static final Logger logger = Logger.getLogger(EntityInfo.class); + + //Entity类的类名 + private final Class type; + + //类对应的数据表名, 如果是VirtualEntity 类, 则该字段为null + private final String table; + + private final Creator creator; + + //主键 + final Attribute primary; + + private final EntityCache cache; + + //key是field的name, 不是sql字段。 + //存放所有与数据库对应的字段, 包括主键 + private final HashMap> attributeMap = new HashMap<>(); + + final Attribute[] attributes; + + //key是field的name, value是Column的别名,即数据库表的字段名 + //只有field.name 与 Column.name不同才存放在aliasmap里. + private final Map aliasmap; + + private final Map> updateAttributeMap = new HashMap<>(); + + final String querySQL; + + private final Attribute[] queryAttributes; //数据库中所有字段 + + final String insertSQL; + + final Attribute[] insertAttributes; //数据库中所有可新增字段 + + final String updateSQL; + + final Attribute[] updateAttributes; //数据库中所有可更新字段 + + final String deleteSQL; + + private final int logLevel; + + private final Map sortOrderbySqls = new ConcurrentHashMap<>(); + + //---------------------计算主键值---------------------------- + private final int nodeid; + + final Class[] distributeTables; + + final boolean autoGenerated; + + final boolean distributed; + + boolean initedPrimaryValue = false; + + final AtomicLong primaryValue = new AtomicLong(0); + + final int allocationSize; + //------------------------------------------------------------ + + public static EntityInfo load(Class clazz, final int nodeid, final boolean cacheForbidden, + Function fullloader) { + EntityInfo rs = entityInfos.get(clazz); + if (rs != null) return rs; + synchronized (entityInfos) { + rs = entityInfos.get(clazz); + if (rs == null) { + if (nodeid < 0) throw new IllegalArgumentException("nodeid(" + nodeid + ") is illegal"); + rs = new EntityInfo(clazz, nodeid, cacheForbidden); + entityInfos.put(clazz, rs); + AutoLoad auto = clazz.getAnnotation(AutoLoad.class); + if (rs.cache != null && auto != null && auto.value()) { + if (fullloader == null) throw new IllegalArgumentException(clazz.getName() + " auto loader is illegal"); + rs.cache.fullLoad(fullloader.apply(clazz)); + } + } + return rs; + } + } + + static EntityInfo get(Class clazz) { + return entityInfos.get(clazz); + } + + private EntityInfo(Class type, int nodeid, final boolean cacheForbidden) { + this.type = type; + //--------------------------------------------- + this.nodeid = nodeid >= 0 ? nodeid : 0; + DistributeGenerator.DistributeTables dt = type.getAnnotation(DistributeGenerator.DistributeTables.class); + this.distributeTables = dt == null ? null : dt.value(); + + LogLevel ll = type.getAnnotation(LogLevel.class); + this.logLevel = ll == null ? Integer.MIN_VALUE : Level.parse(ll.value()).intValue(); + //--------------------------------------------- + Table t = type.getAnnotation(Table.class); + if (type.getAnnotation(VirtualEntity.class) != null) { + this.table = null; + } else { + this.table = (t == null) ? type.getSimpleName().toLowerCase() : (t.catalog().isEmpty()) ? t.name() : (t.catalog() + '.' + t.name()); + } + this.creator = Creator.create(type); + Attribute idAttr0 = null; + Map aliasmap0 = null; + Class cltmp = type; + Set fields = new HashSet<>(); + List> queryattrs = new ArrayList<>(); + List insertcols = new ArrayList<>(); + List> insertattrs = new ArrayList<>(); + List updatecols = new ArrayList<>(); + List> updateattrs = new ArrayList<>(); + boolean auto = false; + boolean sqldistribute = false; + int allocationSize0 = 0; + + do { + for (Field field : cltmp.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + if (Modifier.isFinal(field.getModifiers())) continue; + if (field.getAnnotation(Transient.class) != null) continue; + if (fields.contains(field.getName())) continue; + final String fieldname = field.getName(); + final Column col = field.getAnnotation(Column.class); + final String sqlfield = col == null || col.name().isEmpty() ? fieldname : col.name(); + if (!fieldname.equals(sqlfield)) { + if (aliasmap0 == null) aliasmap0 = new HashMap<>(); + aliasmap0.put(fieldname, sqlfield); + } + Attribute attr; + try { + attr = Attribute.create(cltmp, field); + } catch (RuntimeException e) { + continue; + } + if (field.getAnnotation(javax.persistence.Id.class) != null && idAttr0 == null) { + idAttr0 = attr; + GeneratedValue gv = field.getAnnotation(GeneratedValue.class); + auto = gv != null; + if (gv != null && gv.strategy() != GenerationType.IDENTITY) { + throw new RuntimeException(cltmp.getName() + "'s @ID primary not a GenerationType.IDENTITY"); + } + DistributeGenerator dg = field.getAnnotation(DistributeGenerator.class); + if (dg != null) { + if (!field.getType().isPrimitive()) throw new RuntimeException(cltmp.getName() + "'s @DistributeGenerator primary must be primitive class type field"); + sqldistribute = true; + auto = false; + allocationSize0 = dg.allocationSize(); + primaryValue.set(dg.initialValue()); + } + if (!auto) { + insertcols.add(sqlfield); + insertattrs.add(attr); + } + } else { + if (col == null || col.insertable()) { + insertcols.add(sqlfield); + insertattrs.add(attr); + } + if (col == null || col.updatable()) { + updatecols.add(sqlfield); + updateattrs.add(attr); + updateAttributeMap.put(fieldname, attr); + } + } + queryattrs.add(attr); + fields.add(fieldname); + attributeMap.put(fieldname, attr); + } + } while ((cltmp = cltmp.getSuperclass()) != Object.class); + this.primary = idAttr0; + this.aliasmap = aliasmap0; + this.attributes = attributeMap.values().toArray(new Attribute[attributeMap.size()]); + this.queryAttributes = queryattrs.toArray(new Attribute[queryattrs.size()]); + this.insertAttributes = insertattrs.toArray(new Attribute[insertattrs.size()]); + this.updateAttributes = updateattrs.toArray(new Attribute[updateattrs.size()]); + if (table != null) { + StringBuilder insertsb = new StringBuilder(); + StringBuilder insertsb2 = new StringBuilder(); + for (String col : insertcols) { + if (insertsb.length() > 0) insertsb.append(','); + insertsb.append(col); + if (insertsb2.length() > 0) insertsb2.append(','); + insertsb2.append('?'); + } + this.insertSQL = "INSERT INTO " + table + "(" + insertsb + ") VALUES(" + insertsb2 + ")"; + StringBuilder updatesb = new StringBuilder(); + for (String col : updatecols) { + if (updatesb.length() > 0) updatesb.append(','); + updatesb.append(col).append(" = ?"); + } + this.updateSQL = "UPDATE " + table + " SET " + updatesb + " WHERE " + getPrimarySQLColumn(null) + " = ?"; + this.deleteSQL = "DELETE FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = ?"; + this.querySQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = ?"; + } else { + this.insertSQL = null; + this.updateSQL = null; + this.deleteSQL = null; + this.querySQL = null; + } + this.autoGenerated = auto; + this.distributed = sqldistribute; + this.allocationSize = allocationSize0; + //----------------cache-------------- + Cacheable c = type.getAnnotation(Cacheable.class); + if (this.table == null || (!cacheForbidden && c != null && c.value())) { + this.cache = new EntityCache<>(this); + } else { + this.cache = null; + } + } + + public void createPrimaryValue(T src) { + long v = allocationSize > 1 ? (primaryValue.incrementAndGet() * allocationSize + nodeid) : primaryValue.incrementAndGet(); + if (primary.type() == int.class || primary.type() == Integer.class) { + getPrimary().set(src, (Integer) ((Long) v).intValue()); + } else { + getPrimary().set(src, v); + } + } + + public EntityCache getCache() { + return cache; + } + + public boolean isCacheFullLoaded() { + return cache != null && cache.isFullLoaded(); + } + + public Creator getCreator() { + return creator; + } + + public Class getType() { + return type; + } + + /** + * 是否虚拟类 + *

+ * @return + */ + public boolean isVirtualEntity() { + return table == null; + } + + public String getTable() { + return table; + } + + public Attribute getPrimary() { + return this.primary; + } + + public void forEachAttribute(BiConsumer> action) { + this.attributeMap.forEach(action); + } + + public Attribute getAttribute(String fieldname) { + if (fieldname == null) return null; + return this.attributeMap.get(fieldname); + } + + public Attribute getUpdateAttribute(String fieldname) { + return this.updateAttributeMap.get(fieldname); + } + + public boolean isNoAlias() { + return this.aliasmap == null; + } + + protected String createSQLOrderby(Flipper flipper) { + if (flipper == null || flipper.getSort() == null || flipper.getSort().isEmpty()) return ""; + final String sort = flipper.getSort(); + String sql = this.sortOrderbySqls.get(sort); + if (sql != null) return sql; + final StringBuilder sb = new StringBuilder(); + sb.append(" ORDER BY "); + if (isNoAlias()) { + sb.append(sort); + } else { + boolean flag = false; + for (String item : sort.split(",")) { + if (item.isEmpty()) continue; + String[] sub = item.split("\\s+"); + if (flag) sb.append(','); + if (sub.length < 2 || sub[1].equalsIgnoreCase("ASC")) { + sb.append(getSQLColumn("a", sub[0])).append(" ASC"); + } else { + sb.append(getSQLColumn("a", sub[0])).append(" DESC"); + } + flag = true; + } + } + sql = sb.toString(); + this.sortOrderbySqls.put(sort, sql); + return sql; + } + + //根据field字段名获取数据库对应的字段名 + public String getSQLColumn(String tabalis, String fieldname) { + return this.aliasmap == null ? (tabalis == null ? fieldname : (tabalis + '.' + fieldname)) + : (tabalis == null ? aliasmap.getOrDefault(fieldname, fieldname) : (tabalis + '.' + aliasmap.getOrDefault(fieldname, fieldname))); + } + + public String getPrimarySQLColumn() { + return getSQLColumn(null, this.primary.field()); + } + + //数据库字段名 + public String getPrimarySQLColumn(String tabalis) { + return getSQLColumn(tabalis, this.primary.field()); + } + + protected Map> getAttributes() { + return attributeMap; + } + + public boolean isLoggable(Level l) { + return l.intValue() >= this.logLevel; + } + + protected T getValue(final SelectColumn sels, final ResultSet set) throws SQLException { + T obj = creator.create(); + for (Attribute attr : queryAttributes) { + if (sels == null || sels.test(attr.field())) { + Serializable o = (Serializable) set.getObject(this.getSQLColumn(null, attr.field())); + if (o != null) { + Class t = attr.type(); + if (t == short.class) { + o = ((Number) o).shortValue(); + } else if (t == long.class) { + o = ((Number) o).longValue(); + } else if (t == int.class) { + o = ((Number) o).intValue(); + } + } + attr.set(obj, o); + } + } + return obj; + } +} diff --git a/src/org/redkale/source/FilterBean.java b/src/org/redkale/source/FilterBean.java new file mode 100644 index 000000000..de929007d --- /dev/null +++ b/src/org/redkale/source/FilterBean.java @@ -0,0 +1,15 @@ +/* + * 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.source; + +/** + * + * @author zhangjx + */ +public interface FilterBean { + +} diff --git a/src/org/redkale/source/FilterColumn.java b/src/org/redkale/source/FilterColumn.java new file mode 100644 index 000000000..d0815c2f9 --- /dev/null +++ b/src/org/redkale/source/FilterColumn.java @@ -0,0 +1,46 @@ +/* + * 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.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface FilterColumn { + + /** + * 对应Entity Class中字段的名称, 而不是SQL字段名称 + * + * @return + */ + String name() default ""; + + /** + * 当字段类型是Number时, 如果值>=least() 则需要过滤, 否则跳过该字段 + * + * @return + */ + long least() default 1; + + /** + * express的默认值根据字段类型的不同而不同: + * 数组 --> IN + * Range --> Between + * 其他 --> = + * + * @return + */ + FilterExpress express() default FilterExpress.EQUAL; + +} diff --git a/src/org/redkale/source/FilterExpress.java b/src/org/redkale/source/FilterExpress.java new file mode 100644 index 000000000..528970fb5 --- /dev/null +++ b/src/org/redkale/source/FilterExpress.java @@ -0,0 +1,46 @@ +/* + * 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.source; + +/** + * + * @author zhangjx + */ +public enum FilterExpress { + + EQUAL("="), + NOTEQUAL("<>"), + GREATERTHAN(">"), + LESSTHAN("<"), + GREATERTHANOREQUALTO(">="), + LESSTHANOREQUALTO("<="), + LIKE("LIKE"), + NOTLIKE("NOT LIKE"), + IGNORECASELIKE("LIKE"), //不区分大小写的 LIKE + IGNORECASENOTLIKE("NOT LIKE"), //不区分大小写的 NOT LIKE + BETWEEN("BETWEEN"), + NOTBETWEEN("NOT BETWEEN"), + IN("IN"), + NOTIN("NOT IN"), + ISNULL("IS NULL"), + ISNOTNULL("IS NOT NULL"), + OPAND("&"), //与运算 > 0 + OPOR("|"), //或运算 > 0 + OPANDNO("&"), //与运算 == 0 + AND("AND"), + OR("OR"); + + private final String value; + + private FilterExpress(String v) { + this.value = v; + } + + public String value() { + return value; + } + +} diff --git a/src/org/redkale/source/FilterGroup.java b/src/org/redkale/source/FilterGroup.java new file mode 100644 index 000000000..d65000ba1 --- /dev/null +++ b/src/org/redkale/source/FilterGroup.java @@ -0,0 +1,62 @@ +/* + * 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.source; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static java.lang.annotation.ElementType.FIELD; +import java.lang.annotation.*; + +/* + * 默认情况下FilterBean下的过滤字段之间是AND关系。 + * 当需要使用OR或AND OR组合过滤查询时需要使用 FilterGroup。 + * FilterGroup 的value 必须是[OR]或者[AND]开头, 多级需要用点.分隔。 (注: 暂时不支持多级) + * 示例一: + * public class TestFilterBean implements FilterBean { + * + * private int id; + * + * @FilterGroup("[OR]g1") + * private String desc; + * + * @FilterGroup("[OR]g1") + * private String name; + * } + * 转换的SQL语句为: WHERE id = ? AND (desc = ? OR name = ?) + * + * 示例二: + * public class TestFilterBean implements FilterBean { + * + * private int id; + * + * @FilterGroup("[OR]g1.[AND]subg1") + * @FilterColumn(express = LIKE) + * private String desc; + * + * @FilterGroup("[OR]g1.[AND]subg1") + * @FilterColumn(express = LIKE) + * private String name; + * + * @FilterGroup("[OR]g1.[OR]subg2") + * private int age; + * + * @FilterGroup("[OR]g1.[OR]subg2") + * private int birthday; + * } + * 转换的SQL语句为: WHERE id = ? AND ((desc LIKE ? AND name LIKE ?) OR (age = ? OR birthday = ?)) + * 因为默认是AND关系, @FilterGroup("") 等价于 @FilterGroup("[AND]") + * 所以示例二的@FilterGroup("[OR]g1.[AND]subg1") 可以简化为 @FilterGroup("[OR]g1") + */ +/** + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface FilterGroup { + + String value() default "[AND]"; +} diff --git a/src/org/redkale/source/FilterGroups.java b/src/org/redkale/source/FilterGroups.java new file mode 100644 index 000000000..9a50885b5 --- /dev/null +++ b/src/org/redkale/source/FilterGroups.java @@ -0,0 +1,23 @@ +/* + * 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.source; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.*; + +/** + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface FilterGroups { + + FilterGroup[] value(); +} diff --git a/src/org/redkale/source/FilterJoinColumn.java b/src/org/redkale/source/FilterJoinColumn.java new file mode 100644 index 000000000..5c2f04661 --- /dev/null +++ b/src/org/redkale/source/FilterJoinColumn.java @@ -0,0 +1,50 @@ +/* + * 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.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface FilterJoinColumn { + + /** + * 关联表 通常join表默认别名为b/c/d/...自增, 被join表默认别名为a + * + * @return + */ + Class table(); + + /** + * + * 单个关联字段, 默认使用join表(b)的主键, join表与被join表(a)的字段必须一样 + * 例如: SELECT a.* FROM user a INNER JOIN record b ON a.userid = b.userid + * 那么注解为: @FilterJoinColumn(table = Record.class, column = "userid") + *

+ * @deprecated 使用columns 代替 + * + * @return + */ + String column() default ""; + + /** + * + * 多个关联字段, 默认使用join表(b)的主键, join表与被join表(a)的字段必须一样 + * 例如: SELECT a.* FROM user a INNER JOIN record b ON a.userid = b.userid AND a.usertype = b.usertype + * 那么注解为: @FilterJoinColumn(table = Record.class, columns = {"userid", "usertype"}) + *

+ * @return + */ + String[] columns() default {}; +} diff --git a/src/org/redkale/source/FilterJoinNode.java b/src/org/redkale/source/FilterJoinNode.java new file mode 100644 index 000000000..19661fba7 --- /dev/null +++ b/src/org/redkale/source/FilterJoinNode.java @@ -0,0 +1,315 @@ +/* + * 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.source; + +import java.io.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import java.util.function.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public class FilterJoinNode extends FilterNode { + + private Class joinClass; + + private EntityInfo joinEntity; //在调用 createSQLJoin 和 isCacheUseable 时会注入 + + private String[] joinColumns; + + public FilterJoinNode() { + } + + protected FilterJoinNode(Class joinClass, String[] joinColumns, String column, Serializable value) { + this(joinClass, joinColumns, column, null, value); + } + + protected FilterJoinNode(Class joinClass, String[] joinColumns, String column, FilterExpress express, Serializable value) { + Objects.requireNonNull(joinClass); + Objects.requireNonNull(joinColumns); + this.joinClass = joinClass; + this.joinColumns = joinColumns; + this.column = column; + this.express = express; + this.value = value; + } + + protected FilterJoinNode(FilterJoinNode node) { + this(node.joinClass, node.joinColumns, node.column, node.express, node.value); + this.joinEntity = node.joinEntity; + this.or = node.or; + this.nodes = node.nodes; + } + + public static FilterJoinNode create(Class joinClass, String joinColumn, String column, Serializable value) { + return new FilterJoinNode(joinClass, new String[]{joinColumn}, column, value); + } + + public static FilterJoinNode create(Class joinClass, String joinColumn, String column, FilterExpress express, Serializable value) { + return new FilterJoinNode(joinClass, new String[]{joinColumn}, column, express, value); + } + + public static FilterJoinNode create(Class joinClass, String[] joinColumns, String column, Serializable value) { + return new FilterJoinNode(joinClass, joinColumns, column, value); + } + + public static FilterJoinNode create(Class joinClass, String[] joinColumns, String column, FilterExpress express, Serializable value) { + return new FilterJoinNode(joinClass, joinColumns, column, express, value); + } + + @Override + protected FilterNode any(final FilterNode node0, boolean signor) { + Objects.requireNonNull(node0); + if (!(node0 instanceof FilterJoinNode)) { + throw new IllegalArgumentException(this + (signor ? " or " : " and ") + " a node but " + String.valueOf(node0) + "is not a " + FilterJoinNode.class.getSimpleName()); + } + final FilterJoinNode node = (FilterJoinNode) node0; + if (this.nodes == null) { + this.nodes = new FilterNode[]{node}; + this.or = signor; + return this; + } + if (or == signor || this.column == null) { + FilterNode[] newsiblings = new FilterNode[nodes.length + 1]; + System.arraycopy(nodes, 0, newsiblings, 0, nodes.length); + newsiblings[nodes.length] = node; + this.nodes = newsiblings; + if (this.column == null) this.or = signor; + return this; + } + this.nodes = new FilterNode[]{new FilterJoinNode(node), node}; + this.column = null; + this.express = null; + this.value = null; + this.joinClass = null; + this.joinEntity = null; + this.joinColumns = null; + this.or = signor; + return this; + } + + @Override + protected CharSequence createSQLExpress(final EntityInfo info, final Map joinTabalis) { + return super.createSQLExpress(this.joinEntity == null ? info : this.joinEntity, joinTabalis); + } + + @Override + protected Predicate createPredicate(final EntityCache cache) { + if (column == null && this.nodes == null) return null; + final EntityCache joinCache = this.joinEntity.getCache(); + final AtomicBoolean more = new AtomicBoolean(); + Predicate filter = createJoinPredicate(more); + Predicate rs = null; + if (filter == null && !more.get()) return rs; + if (filter != null) { + final Predicate inner = filter; + rs = new Predicate() { + + @Override + public boolean test(final T t) { + Predicate joinPredicate = null; + for (String joinColumn : joinColumns) { + final Serializable key = cache.getAttribute(joinColumn).get(t); + final Attribute joinAttr = joinCache.getAttribute(joinColumn); + Predicate p = (E e) -> key.equals(joinAttr.get(e)); + joinPredicate = joinPredicate == null ? p : joinPredicate.and(p); + } + return joinCache.exists(inner.and(joinPredicate)); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(" #-- ON ").append(joinColumns[0]).append("=").append(joinClass == null ? "null" : joinClass.getSimpleName()).append(".").append(joinColumns[0]); + for (int i = 1; i < joinColumns.length; i++) { + sb.append(" AND ").append(joinColumns[i]).append("=").append(joinClass == null ? "null" : joinClass.getSimpleName()).append(".").append(joinColumns[i]); + } + sb.append(" --# ").append(inner.toString()); + return sb.toString(); + } + }; + } + if (more.get()) { //存在不同Class的关联表 + if (this.nodes != null) { + for (FilterNode node : this.nodes) { + if (((FilterJoinNode) node).joinClass == this.joinClass) continue; + Predicate f = node.createPredicate(cache); + if (f == null) continue; + final Predicate one = rs; + final Predicate two = f; + rs = (rs == null) ? f : (or ? new Predicate() { + + @Override + public boolean test(T t) { + return one.test(t) || two.test(t); + } + + @Override + public String toString() { + return "(" + one + " OR " + two + ")"; + } + } : new Predicate() { + + @Override + public boolean test(T t) { + return one.test(t) && two.test(t); + } + + @Override + public String toString() { + return "(" + one + " AND " + two + ")"; + } + }); + } + } + } + return rs; + } + + private Predicate createJoinPredicate(final AtomicBoolean more) { + if (column == null && this.nodes == null) return null; + final EntityCache joinCache = this.joinEntity.getCache(); + Predicate filter = createElementPredicate(joinCache, true); + if (this.nodes != null) { + for (FilterNode node : this.nodes) { + if (((FilterJoinNode) node).joinClass != this.joinClass) { + more.set(true); + continue; + } + Predicate f = ((FilterJoinNode) node).createJoinPredicate(more); + if (f == null) continue; + final Predicate one = filter; + final Predicate two = f; + filter = (filter == null) ? f : (or ? new Predicate() { + + @Override + public boolean test(E t) { + return one.test(t) || two.test(t); + } + + @Override + public String toString() { + return "(" + one + " OR " + two + ")"; + } + } : new Predicate() { + + @Override + public boolean test(E t) { + return one.test(t) && two.test(t); + } + + @Override + public String toString() { + return "(" + one + " AND " + two + ")"; + } + }); + } + } + return filter; + } + + @Override + protected CharSequence createSQLJoin(final Function func, final Map joinTabalis, final EntityInfo info) { + boolean morejoin = false; + if (this.joinEntity == null) { + if (this.joinClass != null) this.joinEntity = func.apply(this.joinClass); + if (this.nodes != null) { + for (FilterNode node : this.nodes) { + if (node instanceof FilterJoinNode) { + FilterJoinNode joinNode = ((FilterJoinNode) node); + if (joinNode.joinClass != null) { + joinNode.joinEntity = func.apply(joinNode.joinClass); + if (this.joinClass != null && this.joinClass != joinNode.joinClass) morejoin = true; + } + } + } + } + } + StringBuilder sb = new StringBuilder(); + if (this.joinClass != null) { + sb.append(createElementSQLJoin(joinTabalis, info, this)); + } + if (morejoin) { + Set set = new HashSet<>(); + if (this.joinClass != null) set.add(this.joinClass); + for (FilterNode node : this.nodes) { + if (node instanceof FilterJoinNode) { + FilterJoinNode joinNode = ((FilterJoinNode) node); + if (!set.contains(joinNode.joinClass)) { + CharSequence cs = createElementSQLJoin(joinTabalis, info, joinNode); + if (cs != null) { + sb.append(cs); + set.add(joinNode.joinClass); + } + } + } + } + } + return sb; + } + + private static CharSequence createElementSQLJoin(final Map joinTabalis, final EntityInfo info, final FilterJoinNode node) { + if (node.joinClass == null) return null; + StringBuilder sb = new StringBuilder(); + String[] joinColumns = node.joinColumns; + sb.append(" INNER JOIN ").append(node.joinEntity.getTable()).append(" ").append(joinTabalis.get(node.joinClass)) + .append(" ON ").append(info.getSQLColumn("a", joinColumns[0])).append(" = ").append(node.joinEntity.getSQLColumn(joinTabalis.get(node.joinClass), joinColumns[0])); + for (int i = 1; i < joinColumns.length; i++) { + sb.append(" AND ").append(info.getSQLColumn("a", joinColumns[i])).append(" = ").append(node.joinEntity.getSQLColumn(joinTabalis.get(node.joinClass), joinColumns[i])); + } + return sb; + } + + @Override + protected boolean isCacheUseable(final Function entityApplyer) { + if (this.joinEntity == null) this.joinEntity = entityApplyer.apply(this.joinClass); + if (!this.joinEntity.isCacheFullLoaded()) return false; + if (this.nodes == null) return true; + for (FilterNode node : this.nodes) { + if (!node.isCacheUseable(entityApplyer)) return false; + } + return true; + } + + @Override + protected void putJoinTabalis(Map map) { + if (this.joinClass != null && !map.containsKey(this.joinClass)) map.put(joinClass, String.valueOf((char) ('b' + map.size()))); + if (this.nodes == null) return; + for (FilterNode node : this.nodes) { + node.putJoinTabalis(map); + } + } + + @Override + protected final boolean isjoin() { + return true; + } + + @Override + public String toString() { + return toString(joinClass == null ? null : joinClass.getSimpleName()).toString(); + } + + public Class getJoinClass() { + return joinClass; + } + + public void setJoinClass(Class joinClass) { + this.joinClass = joinClass; + } + + public String[] getJoinColumns() { + return joinColumns; + } + + public void setJoinColumns(String[] joinColumns) { + this.joinColumns = joinColumns; + } + +} diff --git a/src/org/redkale/source/FilterNode.java b/src/org/redkale/source/FilterNode.java new file mode 100644 index 000000000..7da2e47c9 --- /dev/null +++ b/src/org/redkale/source/FilterNode.java @@ -0,0 +1,958 @@ +/* + * 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.source; + +import static org.redkale.source.FilterExpress.*; +import java.io.*; +import java.lang.reflect.*; +import java.util.*; +import java.util.function.*; +import org.redkale.util.*; + +/** + * 注意: + * 在调用 createSQLExpress 之前必须先调用 createSQLJoin + * 在调用 createPredicate 之前必须先调用 isCacheUseable + * + * @author zhangjx + */ +public class FilterNode { + + protected String column; + + protected FilterExpress express; + + protected Serializable value; + + //---------------------------------------------- + protected boolean or; + + protected FilterNode[] nodes; + + public FilterNode() { + } + + protected FilterNode(String col, FilterExpress exp, Serializable val) { + Objects.requireNonNull(col); + if (exp == null) { + if (val instanceof Range) { + exp = FilterExpress.BETWEEN; + } else if (val instanceof Collection) { + exp = FilterExpress.IN; + } else if (val != null && val.getClass().isArray()) { + exp = FilterExpress.IN; + } else { + exp = FilterExpress.EQUAL; + } + } + this.column = col; + this.express = exp; + this.value = val; + } + + public final FilterNode and(FilterNode node) { + return any(node, false); + } + + public final FilterNode and(String column, Serializable value) { + return and(column, null, value); + } + + public final FilterNode and(String column, FilterExpress express, Serializable value) { + return and(new FilterNode(column, express, value)); + } + + public final FilterNode or(FilterNode node) { + return any(node, true); + } + + public final FilterNode or(String column, Serializable value) { + return or(column, null, value); + } + + public final FilterNode or(String column, FilterExpress express, Serializable value) { + return or(new FilterNode(column, express, value)); + } + + protected FilterNode any(FilterNode node, boolean signor) { + Objects.requireNonNull(node); + if (this.column == null) { + this.column = node.column; + this.express = node.express; + this.value = node.value; + return this; + } + if (this.nodes == null) { + this.nodes = new FilterNode[]{node}; + this.or = signor; + return this; + } + if (or == signor) { + FilterNode[] newsiblings = new FilterNode[nodes.length + 1]; + System.arraycopy(nodes, 0, newsiblings, 0, nodes.length); + newsiblings[nodes.length] = node; + this.nodes = newsiblings; + return this; + } + FilterNode newnode = new FilterNode(this.column, this.express, this.value); + newnode.or = this.or; + newnode.nodes = this.nodes; + this.nodes = new FilterNode[]{newnode, node}; + this.column = null; + this.express = null; + this.or = signor; + this.value = null; + return this; + } + + /** + * 该方法需要重载 + * + * @param + * @param func + * @param joinTabalis + * @param info + * @return + */ + protected CharSequence createSQLJoin(final Function func, final Map joinTabalis, final EntityInfo info) { + if (joinTabalis == null || this.nodes == null) return null; + StringBuilder sb = null; + for (FilterNode node : this.nodes) { + CharSequence cs = node.createSQLJoin(func, joinTabalis, info); + if (cs == null) continue; + if (sb == null) sb = new StringBuilder(); + sb.append(cs); + } + return sb; + } + + /** + * 该方法需要重载 + * + * @return + */ + protected boolean isjoin() { + if (this.nodes == null) return false; + for (FilterNode node : this.nodes) { + if (node.isjoin()) return true; + } + return false; + } + + protected final Map getJoinTabalis() { + if (!isjoin()) return null; + Map map = new HashMap<>(); + putJoinTabalis(map); + return map; + } + + protected void putJoinTabalis(Map map) { + if (this.nodes == null) return; + for (FilterNode node : this.nodes) { + node.putJoinTabalis(map); + } + } + + /** + * 该方法需要重载 + * + * @param entityApplyer + * @return + */ + protected boolean isCacheUseable(final Function entityApplyer) { + if (this.nodes == null) return true; + for (FilterNode node : this.nodes) { + if (!node.isCacheUseable(entityApplyer)) return false; + } + return true; + } + + /** + * 该方法需要重载 + * + * @param + * @param joinTabalis + * @param info + * @return + */ + protected CharSequence createSQLExpress(final EntityInfo info, final Map joinTabalis) { + CharSequence sb0 = this.column == null || info == null ? null : createElementSQLExpress(info, joinTabalis == null ? null : joinTabalis.get(info.getType())); + if (this.nodes == null) return sb0; + final StringBuilder rs = new StringBuilder(); + rs.append('('); + boolean more = false; + if (sb0 != null && sb0.length() > 2) { + more = true; + rs.append(sb0); + } + for (FilterNode node : this.nodes) { + CharSequence f = node.createSQLExpress(info, joinTabalis); + if (f == null || f.length() < 3) continue; + if (more) rs.append(or ? " OR " : " AND "); + rs.append(f); + more = true; + } + rs.append(')'); + if (rs.length() < 5) return null; + return rs; + } + + public static FilterNode create(String column, Serializable value) { + return create(column, null, value); + } + + public static FilterNode create(String column, FilterExpress express, Serializable value) { + return new FilterNode(column, express, value); + } + + protected final CharSequence createElementSQLExpress(final EntityInfo info, String talis) { + if (column == null) return null; + if (talis == null) talis = "a"; + if (express == ISNULL || express == ISNOTNULL) { + return new StringBuilder().append(info.getSQLColumn(talis, column)).append(' ').append(express.value()); + } + final CharSequence val = formatToString(express, getValue()); + if (val == null) return null; + StringBuilder sb = new StringBuilder(32); + if (express == IGNORECASELIKE || express == IGNORECASENOTLIKE) { + sb.append("LOWER(").append(info.getSQLColumn(talis, column)).append(')'); + } else { + sb.append(info.getSQLColumn(talis, column)); + } + sb.append(' '); + switch (express) { + case OPAND: + case OPOR: + sb.append(express.value()).append(' ').append(val).append(" > 0"); + break; + case OPANDNO: + sb.append(express.value()).append(' ').append(val).append(" = 0"); + break; + default: + sb.append(express.value()).append(' ').append(val); + break; + } + return sb; + } + + protected Predicate createPredicate(final EntityCache cache) { + if (cache == null || (column == null && this.nodes == null)) return null; + Predicate filter = createElementPredicate(cache, false); + if (this.nodes == null) return filter; + for (FilterNode node : this.nodes) { + Predicate f = node.createPredicate(cache); + if (f == null) continue; + final Predicate one = filter; + final Predicate two = f; + filter = (filter == null) ? f : (or ? new Predicate() { + + @Override + public boolean test(T t) { + return one.test(t) || two.test(t); + } + + @Override + public String toString() { + return "(" + one + " OR " + two + ")"; + } + } : new Predicate() { + + @Override + public boolean test(T t) { + return one.test(t) && two.test(t); + } + + @Override + public String toString() { + return "(" + one + " AND " + two + ")"; + } + }); + } + return filter; + } + + protected final Predicate createElementPredicate(final EntityCache cache, final boolean join) { + if (column == null) return null; + return createElementPredicate(cache, join, cache.getAttribute(column)); + } + + protected final Predicate createElementPredicate(final EntityCache cache, final boolean join, final Attribute attr) { + if (attr == null) return null; + final String field = join ? (cache.getType().getSimpleName() + "." + attr.field()) : attr.field(); + if (express == ISNULL) return new Predicate() { + + @Override + public boolean test(T t) { + return attr.get(t) == null; + } + + @Override + public String toString() { + return field + " = null"; + } + }; + if (express == ISNOTNULL) return new Predicate() { + + @Override + public boolean test(T t) { + return attr.get(t) != null; + } + + @Override + public String toString() { + return field + " != null"; + } + }; + if (attr == null) return null; + Serializable val0 = getValue(); + if (val0 == null) return null; + + final Class atype = attr.type(); + final Class valtype = val0.getClass(); + if (atype != valtype && val0 instanceof Number) { + if (atype == int.class || atype == Integer.class) { + val0 = ((Number) val0).intValue(); + } else if (atype == long.class || atype == Long.class) { + val0 = ((Number) val0).longValue(); + } else if (atype == short.class || atype == Short.class) { + val0 = ((Number) val0).shortValue(); + } else if (atype == float.class || atype == Float.class) { + val0 = ((Number) val0).floatValue(); + } else if (atype == byte.class || atype == Byte.class) { + val0 = ((Number) val0).byteValue(); + } else if (atype == double.class || atype == Double.class) { + val0 = ((Number) val0).doubleValue(); + } + } else if (valtype.isArray()) { + final int len = Array.getLength(val0); + if (len == 0 && express == NOTIN) return null; + final Class compType = valtype.getComponentType(); + if (atype != compType && len > 0) { + if (!compType.isPrimitive() && Number.class.isAssignableFrom(compType)) throw new RuntimeException("param(" + val0 + ") type not match " + atype + " for column " + column); + if (atype == int.class || atype == Integer.class) { + int[] vs = new int[len]; + for (int i = 0; i < len; i++) { + vs[i] = ((Number) Array.get(val0, i)).intValue(); + } + val0 = vs; + } else if (atype == long.class || atype == Long.class) { + long[] vs = new long[len]; + for (int i = 0; i < len; i++) { + vs[i] = ((Number) Array.get(val0, i)).longValue(); + } + val0 = vs; + } else if (atype == short.class || atype == Short.class) { + short[] vs = new short[len]; + for (int i = 0; i < len; i++) { + vs[i] = ((Number) Array.get(val0, i)).shortValue(); + } + val0 = vs; + } else if (atype == float.class || atype == Float.class) { + float[] vs = new float[len]; + for (int i = 0; i < len; i++) { + vs[i] = ((Number) Array.get(val0, i)).floatValue(); + } + val0 = vs; + } else if (atype == byte.class || atype == Byte.class) { + byte[] vs = new byte[len]; + for (int i = 0; i < len; i++) { + vs[i] = ((Number) Array.get(val0, i)).byteValue(); + } + val0 = vs; + } else if (atype == double.class || atype == Double.class) { + double[] vs = new double[len]; + for (int i = 0; i < len; i++) { + vs[i] = ((Number) Array.get(val0, i)).doubleValue(); + } + val0 = vs; + } + } + } else if (val0 instanceof Collection) { + final Collection collection = (Collection) val0; + if (collection.isEmpty() && express == NOTIN) return null; + if (!collection.isEmpty()) { + Iterator it = collection.iterator(); + it.hasNext(); + Class fs = it.next().getClass(); + Class pfs = fs; + if (fs == Integer.class) { + pfs = int.class; + } else if (fs == Long.class) { + pfs = long.class; + } else if (fs == Short.class) { + pfs = short.class; + } else if (fs == Float.class) { + pfs = float.class; + } else if (fs == Byte.class) { + pfs = byte.class; + } else if (fs == Double.class) { + pfs = double.class; + } + if (Number.class.isAssignableFrom(fs) && atype != fs && atype != pfs) { //需要转换 + ArrayList list = new ArrayList(collection.size()); + if (atype == int.class || atype == Integer.class) { + for (Number num : (Collection) collection) { + list.add(num.intValue()); + } + } else if (atype == long.class || atype == Long.class) { + for (Number num : (Collection) collection) { + list.add(num.longValue()); + } + } else if (atype == short.class || atype == Short.class) { + for (Number num : (Collection) collection) { + list.add(num.shortValue()); + } + } else if (atype == float.class || atype == Float.class) { + for (Number num : (Collection) collection) { + list.add(num.floatValue()); + } + } else if (atype == byte.class || atype == Byte.class) { + for (Number num : (Collection) collection) { + list.add(num.byteValue()); + } + } else if (atype == double.class || atype == Double.class) { + for (Number num : (Collection) collection) { + list.add(num.doubleValue()); + } + } + val0 = list; + } + } + } + final Serializable val = val0; + switch (express) { + case EQUAL: return new Predicate() { + + @Override + public boolean test(T t) { + return val.equals(attr.get(t)); + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + formatToString(val); + } + }; + case NOTEQUAL: return new Predicate() { + + @Override + public boolean test(T t) { + return !val.equals(attr.get(t)); + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + formatToString(val); + } + }; + case GREATERTHAN: return new Predicate() { + + @Override + public boolean test(T t) { + return ((Number) attr.get(t)).longValue() > ((Number) val).longValue(); + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + val; + } + }; + case LESSTHAN: return new Predicate() { + + @Override + public boolean test(T t) { + return ((Number) attr.get(t)).longValue() < ((Number) val).longValue(); + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + val; + } + }; + case GREATERTHANOREQUALTO: return new Predicate() { + + @Override + public boolean test(T t) { + return ((Number) attr.get(t)).longValue() >= ((Number) val).longValue(); + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + val; + } + }; + case LESSTHANOREQUALTO: return new Predicate() { + + @Override + public boolean test(T t) { + return ((Number) attr.get(t)).longValue() <= ((Number) val).longValue(); + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + val; + } + }; + + case OPAND: return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() & ((Number) val).longValue()) > 0; + } + + @Override + public String toString() { + return field + " & " + val + " > 0"; + } + }; + case OPOR: return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() | ((Number) val).longValue()) > 0; + } + + @Override + public String toString() { + return field + " | " + val + " > 0"; + } + }; + case OPANDNO: return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() & ((Number) val).longValue()) == 0; + } + + @Override + public String toString() { + return field + " & " + val + " = 0"; + } + }; + case LIKE: + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs != null && rs.toString().contains(val.toString()); + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + formatToString(val); + } + }; + case IGNORECASELIKE: + final String valstr = val.toString().toLowerCase(); + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs != null && rs.toString().toLowerCase().contains(valstr); + } + + @Override + public String toString() { + return "LOWER(" + field + ") " + express.value() + ' ' + formatToString(valstr); + } + }; + case NOTLIKE: + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs == null || !rs.toString().contains(val.toString()); + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + formatToString(val); + } + }; + case IGNORECASENOTLIKE: + final String valstr2 = val.toString().toLowerCase(); + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs == null || !rs.toString().toLowerCase().contains(valstr2); + } + + @Override + public String toString() { + return "LOWER(" + field + ") " + express.value() + ' ' + formatToString(valstr2); + } + }; + case BETWEEN: + case NOTBETWEEN: + Range range = (Range) val; + final Comparable min = range.getMin(); + final Comparable max = range.getMax(); + if (express == BETWEEN) return new Predicate() { + + @Override + public boolean test(T t) { + Comparable rs = (Comparable) attr.get(t); + if (rs == null) return false; + if (min != null && min.compareTo(rs) >= 0) return false; + return !(max != null && max.compareTo(rs) <= 0); + } + + @Override + public String toString() { + return field + " BETWEEN " + min + " AND " + max; + } + }; + if (express == NOTBETWEEN) return new Predicate() { + + @Override + public boolean test(T t) { + Comparable rs = (Comparable) attr.get(t); + if (rs == null) return true; + if (min != null && min.compareTo(rs) >= 0) return true; + return (max != null && max.compareTo(rs) <= 0); + } + + @Override + public String toString() { + return field + " NOT BETWEEN " + min + " AND " + max; + } + }; + return null; + case IN: + case NOTIN: + Predicate filter; + if (val instanceof Collection) { + Collection array = (Collection) val; + if (array.isEmpty()) { //express 只会是 IN + filter = new Predicate() { + + @Override + public boolean test(T t) { + return false; + } + + @Override + public String toString() { + return field + ' ' + express.value() + " []"; + } + }; + } else { + filter = new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs != null && array.contains(rs); + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + val; + } + }; + } + } else { + Class type = val.getClass(); + if (Array.getLength(val) == 0) {//express 只会是 IN + filter = new Predicate() { + + @Override + public boolean test(T t) { + return false; + } + + @Override + public String toString() { + return field + ' ' + express.value() + " []"; + } + }; + } else if (type == int[].class) { + filter = new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + if (rs == null) return false; + int k = (int) rs; + for (int v : (int[]) val) { + if (v == k) return true; + } + return false; + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + Arrays.toString((int[]) val); + } + }; + } else if (type == short[].class) { + filter = new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + if (rs == null) return false; + short k = (short) rs; + for (short v : (short[]) val) { + if (v == k) return true; + } + return false; + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + Arrays.toString((short[]) val); + } + }; + } else if (type == long[].class) { + filter = new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + if (rs == null) return false; + long k = (long) rs; + for (long v : (long[]) val) { + if (v == k) return true; + } + return false; + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + Arrays.toString((long[]) val); + } + }; + } else if (type == float[].class) { + filter = new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + if (rs == null) return false; + float k = (float) rs; + for (float v : (float[]) val) { + if (v == k) return true; + } + return false; + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + Arrays.toString((float[]) val); + } + }; + } else if (type == double[].class) { + filter = new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + if (rs == null) return false; + double k = (double) rs; + for (double v : (double[]) val) { + if (v == k) return true; + } + return false; + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + Arrays.toString((double[]) val); + } + }; + } else { + filter = new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + if (rs == null) return false; + for (Object v : (Object[]) val) { + if (rs.equals(v)) return true; + } + return false; + } + + @Override + public String toString() { + return field + ' ' + express.value() + ' ' + Arrays.toString((Object[]) val); + } + }; + } + } + if (express == NOTIN) { + final Predicate filter2 = filter; + filter = new Predicate() { + + @Override + public boolean test(T t) { + return !filter2.test(t); + } + + @Override + public String toString() { + return filter2.toString(); + } + }; + } + return filter; + } + return null; + } + + @Override + public String toString() { + return toString(null).toString(); + } + + protected StringBuilder toString(final String prefix) { + StringBuilder sb = new StringBuilder(); + StringBuilder element = toElementString(prefix); + boolean more = element.length() > 0 && this.nodes != null; + if (more) sb.append('('); + sb.append(element); + if (this.nodes != null) { + for (FilterNode node : this.nodes) { + String s = node.toString(); + if (s.length() < 1) continue; + if (sb.length() > 1) sb.append(or ? " OR " : " AND "); + sb.append(s); + } + } + if (more) sb.append(')'); + return sb; + } + + protected final StringBuilder toElementString(final String prefix) { + StringBuilder sb = new StringBuilder(); + if (column != null) { + String col = prefix == null ? column : (prefix + "." + column); + Serializable ev = getValue(); + if (express == ISNULL || express == ISNOTNULL) { + sb.append(col).append(' ').append(express.value()); + } else if (ev != null) { + sb.append((express == IGNORECASELIKE || express == IGNORECASENOTLIKE) ? ("LOWER(" + col + ')') : col).append(' ').append(express.value()).append(' ').append(formatToString(express, ev)); + } + } + return sb; + } + + protected static CharSequence formatToString(Object value) { + CharSequence sb = formatToString(null, value); + return sb == null ? null : sb.toString(); + } + + private static CharSequence formatToString(FilterExpress express, Object value) { + if (value == null) return null; + if (value instanceof Number) return String.valueOf(value); + if (value instanceof CharSequence) { + if (express == LIKE || express == NOTLIKE) { + value = "%" + value + '%'; + } else if (express == IGNORECASELIKE || express == IGNORECASENOTLIKE) { + value = "%" + value.toString().toLowerCase() + '%'; + } + return new StringBuilder().append('\'').append(value.toString().replace("'", "\\'")).append('\''); + } else if (value instanceof Range) { + Range range = (Range) value; + boolean rangestring = range.getClass() == Range.StringRange.class; + StringBuilder sb = new StringBuilder(); + if (rangestring) { + sb.append('\'').append(range.getMin().toString().replace("'", "\\'")).append('\''); + } else { + sb.append(range.getMin()); + } + sb.append(" AND "); + if (rangestring) { + sb.append('\'').append(range.getMax().toString().replace("'", "\\'")).append('\''); + } else { + sb.append(range.getMax()); + } + return sb; + } else if (value.getClass().isArray()) { + int len = Array.getLength(value); + if (len == 0) return express == NOTIN ? null : new StringBuilder("(NULL)"); + if (len == 1) { + Object firstval = Array.get(value, 0); + if (firstval != null && firstval.getClass().isArray()) return formatToString(express, firstval); + } + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (int i = 0; i < len; i++) { + Object o = Array.get(value, i); + if (sb.length() > 1) sb.append(','); + if (o instanceof CharSequence) { + sb.append('\'').append(o.toString().replace("'", "\\'")).append('\''); + } else { + sb.append(o); + } + } + return sb.append(')'); + } else if (value instanceof Collection) { + Collection c = (Collection) value; + if (c.isEmpty()) return express == NOTIN ? null : new StringBuilder("(NULL)"); + StringBuilder sb = new StringBuilder(); + sb.append('('); + for (Object o : c) { + if (sb.length() > 1) sb.append(','); + if (o instanceof CharSequence) { + sb.append('\'').append(o.toString().replace("'", "\\'")).append('\''); + } else { + sb.append(o); + } + } + return sb.append(')'); + } + return String.valueOf(value); + } + + public final Serializable getValue() { + return value; + } + + public final void setValue(Serializable value) { + this.value = value; + } + + public final boolean isOr() { + return or; + } + + public final void setOr(boolean or) { + this.or = or; + } + + public final String getColumn() { + return column; + } + + public final void setColumn(String column) { + this.column = column; + } + + public final FilterExpress getExpress() { + return express; + } + + public final void setExpress(FilterExpress express) { + this.express = express; + } + + public final FilterNode[] getNodes() { + return nodes; + } + + public final void setNodes(FilterNode[] nodes) { + this.nodes = nodes; + } + +} diff --git a/src/org/redkale/source/FilterNodeBean.java b/src/org/redkale/source/FilterNodeBean.java new file mode 100644 index 000000000..86c4a52c4 --- /dev/null +++ b/src/org/redkale/source/FilterNodeBean.java @@ -0,0 +1,353 @@ +/* + * 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.source; + +import static org.redkale.source.FilterExpress.*; +import java.io.*; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.*; +import javax.persistence.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public final class FilterNodeBean implements Comparable> { + + private static final ConcurrentHashMap beanodes = new ConcurrentHashMap<>(); + + private Attribute beanAttr; + + private String column; + + private FilterExpress express; + + private boolean or; + + private FilterNodeBean[] nodeBeans; + + //-----------------join table-------------------------- + private Class joinClass; + + private String[] joinColumns; + + //---------------------------------------------------- + private long least; + + private boolean string; + + private boolean number; + + public FilterNodeBean(FilterNodeBean bean) { + this.beanAttr = bean.beanAttr; + this.column = bean.column; + this.express = bean.express; + this.joinClass = bean.joinClass; + this.joinColumns = bean.joinColumns; + this.least = bean.least; + this.string = bean.string; + this.number = bean.number; + this.or = bean.or; + this.nodeBeans = bean.nodeBeans; + } + + private FilterNodeBean(final FilterJoinColumn joinCol, final FilterColumn filterCol, final Attribute attr) { + this.beanAttr = attr; + this.joinClass = joinCol == null ? null : joinCol.table(); + this.joinColumns = joinCol == null ? null : (joinCol.columns().length == 0 ? new String[]{joinCol.column()} : joinCol.columns()); + + final Class type = attr.type(); + this.column = (filterCol != null && !filterCol.name().isEmpty()) ? filterCol.name() : attr.field(); + + FilterExpress exp = filterCol == null ? null : filterCol.express(); + if (type.isArray() || Collection.class.isAssignableFrom(type)) { + if (Range.class.isAssignableFrom(type.getComponentType())) { + if (AND != exp) exp = OR; + } else if (NOTIN != exp) exp = IN; + } else if (Range.class.isAssignableFrom(type)) { + if (NOTBETWEEN != exp) exp = BETWEEN; + } + if (exp == null) exp = EQUAL; + this.express = exp; + + this.least = filterCol == null ? 1 : filterCol.least(); + this.number = (type.isPrimitive() && type != boolean.class) || Number.class.isAssignableFrom(type); + this.string = CharSequence.class.isAssignableFrom(type); + } + + private FilterNodeBean or(FilterNodeBean node) { + return any(node, true); + } + + private FilterNodeBean and(FilterNodeBean node) { + return any(node, false); + } + + private FilterNodeBean any(FilterNodeBean node, boolean signor) { + Objects.requireNonNull(node); + if (this.column == null) { + this.beanAttr = node.beanAttr; + this.column = node.column; + this.express = node.express; + this.joinClass = node.joinClass; + this.joinColumns = node.joinColumns; + this.least = node.least; + this.string = node.string; + this.number = node.number; + return this; + } + if (this.nodeBeans == null) { + this.nodeBeans = new FilterNodeBean[]{node}; + this.or = signor; + return this; + } + if (or == signor) { + FilterNodeBean[] newsiblings = new FilterNodeBean[nodeBeans.length + 1]; + System.arraycopy(nodeBeans, 0, newsiblings, 0, nodeBeans.length); + newsiblings[nodeBeans.length] = node; + this.nodeBeans = newsiblings; + return this; + } + this.nodeBeans = new FilterNodeBean[]{new FilterNodeBean(this), node}; + this.column = null; + this.or = signor; + return this; + } + + public static FilterNode createFilterNode(final FilterBean bean) { + if (bean == null) return null; + return load(bean.getClass()).create(bean); + } + + private FilterNode create(final T bean) { + if (bean == null) return null; + FilterNode node = null; + final Serializable val = beanAttr.get(bean); + if (column != null && val != null) { + boolean skip = false; + if (string && ((CharSequence) val).length() == 0) { + skip = true; + } else if (number && ((Number) val).longValue() < least) { + skip = true; + } + if (!skip) { + if (this.joinClass == null) { + node = FilterNode.create(column, express, val); + } else { + node = FilterJoinNode.create(joinClass, joinColumns, column, express, val); + } + } + } + if (this.nodeBeans == null) return node; + for (final FilterNodeBean fnb : this.nodeBeans) { + FilterNode n = fnb.create(bean); + if (n == null) continue; + node = node == null ? n : ((!(n instanceof FilterJoinNode)) ? n.any(node, or) : node.any(n, or)); + } + return node; + } + + private static FilterNodeBean createFilterNodeBean(final Class clazz) { + final Set fields = new HashSet<>(); + final Map nodemap = new LinkedHashMap(); + Class cltmp = clazz; + do { + for (final Field field : cltmp.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + if (fields.contains(field.getName())) continue; + if (field.getAnnotation(Ignore.class) != null) continue; + if (field.getAnnotation(Transient.class) != null) continue; + + final boolean pubmod = Modifier.isPublic(field.getModifiers()); + + char[] chars = field.getName().toCharArray(); + chars[0] = Character.toUpperCase(chars[0]); + final Class t = field.getType(); + Method getter = null; + try { + getter = cltmp.getMethod(((t == boolean.class || t == Boolean.class) ? "is" : "get") + new String(chars)); + } catch (Exception ex) { + if (!pubmod) continue; + } + fields.add(field.getName()); + + final Attribute beanAttr = pubmod ? Attribute.create(field) : Attribute.create(getter, null); + FilterNodeBean nodeBean = new FilterNodeBean(field.getAnnotation(FilterJoinColumn.class), field.getAnnotation(FilterColumn.class), beanAttr); + + //------------------------------------ + { + FilterGroup[] refs = field.getAnnotationsByType(FilterGroup.class); + String[] groups = new String[refs.length]; + for (int i = 0; i < refs.length; i++) { + groups[i] = refs[i].value(); + } + if (groups.length == 0) groups = new String[]{"[AND]"}; + for (String key : groups) { + if (!key.startsWith("[AND]") && !key.startsWith("[OR]")) { + throw new RuntimeException(field + "'s FilterGroup.value(" + key + ") illegal, must be [AND] or [OR] startsWith"); + } + FilterNodeBean node = nodemap.get(key); + if (node == null) { + nodemap.put(key, nodeBean); + } else if (nodeBean.joinClass == null && node.joinClass != null) { //非joinNode 关联 joinNode + nodemap.put(key, nodeBean.any(node, key.substring(key.lastIndexOf('.') + 1).contains("[OR]"))); + } else { + node.any(nodeBean, key.substring(key.lastIndexOf('.') + 1).contains("[OR]")); + } + } + } + } + } while ((cltmp = cltmp.getSuperclass()) != Object.class); + final Map linkes = new LinkedHashMap<>(); + nodemap.forEach((k, v) -> { + String[] keys = k.split("\\."); + LinkNode link = linkes.get(keys[0]); + if (link == null) { + linkes.put(keys[0], new LinkNode(k, v)); + } else { + link.put(keys, 0, v); + } + }); + FilterNodeBean rs = null; + for (LinkNode link : linkes.values()) { + FilterNodeBean f = link.createFilterNodeBean(); + if (f == null) continue; + rs = rs == null ? f : rs.and(f); + } + if (rs.nodeBeans != null) Arrays.sort(rs.nodeBeans); + return rs; + } + + @Override + public int compareTo(FilterNodeBean o) { + if (this.joinClass == null && o.joinClass == null) return 0; + if (this.joinClass != null && o.joinClass != null) return 0; + return this.joinClass == null ? -1 : 1; + } + + private static class LinkNode { + + public final boolean or; + + public final String key; + + public final List beans = new ArrayList<>(); + + public final Map nexts = new LinkedHashMap<>(); + + public LinkNode(String keyString, FilterNodeBean node) { + String[] keys = keyString.split("\\."); + this.key = keys[0]; + this.or = this.key.contains("[OR]"); + put(keys, 0, node); + } + + public LinkNode(String[] keyStrings, int pos, FilterNodeBean node) { + this.key = keyStrings[pos]; + this.or = this.key.contains("[OR]"); + put(keyStrings, pos, node); + } + + public FilterNodeBean createFilterNodeBean() { + FilterNodeBean node = null; + for (FilterNodeBean bean : beans) { + node = node == null ? bean : node.any(bean, or); + } + for (LinkNode link : nexts.values()) { + FilterNodeBean f = link.createFilterNodeBean(); + if (f == null) continue; + node = node == null ? f : node.any(f, or); + } + return node; + } + + public final void put(final String[] keys, int pos, final FilterNodeBean node) { + if (keys.length == pos + 1 && this.key.equals(keys[pos])) { + this.beans.add(node); + return; + } + LinkNode link = nexts.get(keys[pos + 1]); + if (link == null) { + nexts.put(keys[pos + 1], new LinkNode(keys, pos + 1, node)); + } else { + link.put(keys, pos + 1, node); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("{key = '").append(key).append("', or = ").append(or); + if (!beans.isEmpty()) { + sb.append(", beans = [\r\n"); + for (FilterNodeBean bean : this.beans) { + sb.append(" ").append(bean).append("\r\n"); + } + sb.append("]"); + } + if (!nexts.isEmpty()) { + sb.append(", nexts = [\r\n"); + for (LinkNode link : this.nexts.values()) { + sb.append(" ").append(link).append("\r\n"); + } + sb.append("]"); + } + sb.append("}"); + return sb.toString(); + } + } + + private static FilterNodeBean load(Class clazz) { + FilterNodeBean rs = beanodes.get(clazz); + if (rs != null) return rs; + synchronized (beanodes) { + rs = beanodes.get(clazz); + if (rs == null) { + rs = createFilterNodeBean(clazz); + beanodes.put(clazz, rs); + } + return rs; + } + } + + @Override + public String toString() { + return toString(joinClass == null ? null : joinClass.getSimpleName()).toString(); + } + + protected StringBuilder toString(final String prefix) { + StringBuilder sb = new StringBuilder(); + StringBuilder element = toElementString(prefix); + boolean more = element.length() > 0 && this.nodeBeans != null; + if (more) sb.append('('); + sb.append(element); + if (this.nodeBeans != null) { + for (FilterNodeBean node : this.nodeBeans) { + String s = node.toString(); + if (s.length() < 1) continue; + if (sb.length() > 1) sb.append(or ? " OR " : " AND "); + sb.append(s); + } + } + if (more) sb.append(')'); + return sb; + } + + protected final StringBuilder toElementString(final String prefix) { + StringBuilder sb = new StringBuilder(); + if (column != null) { + String col = prefix == null ? column : (prefix + "." + column); + if (express == ISNULL || express == ISNOTNULL) { + sb.append(col).append(' ').append(express.value()); + } else { + sb.append((express == IGNORECASELIKE || express == IGNORECASENOTLIKE) ? ("LOWER(" + col + ')') : col).append(' ').append(express.value()).append(" ?"); + } + } + return sb; + } +} diff --git a/src/org/redkale/source/Flipper.java b/src/org/redkale/source/Flipper.java new file mode 100644 index 000000000..e4ddd78a3 --- /dev/null +++ b/src/org/redkale/source/Flipper.java @@ -0,0 +1,111 @@ +/* + * 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.source; + +import java.io.Serializable; + +/** + * + * @author zhangjx + */ +public final class Flipper implements Serializable { + + public static int DEFAULT_PAGESIZE = 20; + + private int size = DEFAULT_PAGESIZE; + + private int page = 1; + + private String sort = ""; + + public Flipper() { + } + + public Flipper(int pageSize) { + this.size = pageSize; + } + + public Flipper(String sortColumn) { + this.sort = sortColumn; + } + + public Flipper(int pageSize, int pageNo) { + this.size = pageSize; + this.page = pageNo; + } + + public Flipper(int pageSize, int pageNo, String sortColumn) { + this.size = pageSize; + this.page = pageNo; + this.sort = sortColumn; + } + + public void copyTo(Flipper copy) { + if (copy == null) return; + copy.page = this.page; + copy.size = this.size; + copy.sort = this.sort; + } + + public void copyFrom(Flipper copy) { + if (copy == null) return; + this.page = copy.page; + this.size = copy.size; + this.sort = copy.sort; + } + + public Flipper next() { + this.page++; + return this; + } + + public int index() { + return (getPage() - 1) * getSize(); + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "{page:" + this.page + ", size=" + this.size + ", sort=" + this.sort + "}"; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + if (size > 0) { + this.size = size; + } + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + if (page >= 0) { + this.page = page; + } + } + + public String getSort() { + return sort; + } + + public Flipper putSortIfEmpty(String sort) { + if (this.sort == null || this.sort.isEmpty()) { + this.sort = sort; + } + return this; + } + + public void setSort(String sort) { + if (sort != null) { + this.sort = sort.trim(); + } + } + +} diff --git a/src/org/redkale/source/JDBCPoolSource.java b/src/org/redkale/source/JDBCPoolSource.java new file mode 100644 index 000000000..bd32de456 --- /dev/null +++ b/src/org/redkale/source/JDBCPoolSource.java @@ -0,0 +1,238 @@ +/* + * 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.source; + +import static org.redkale.source.DataDefaultSource.*; +import java.io.*; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.nio.file.*; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; +import java.sql.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Level; +import javax.sql.*; + +/** + * + * @author zhangjx + */ +public class JDBCPoolSource { + + private static final Map>>> maps = new HashMap<>(); + + private final AtomicLong usingCounter = new AtomicLong(); + + private final AtomicLong creatCounter = new AtomicLong(); + + private final AtomicLong cycleCounter = new AtomicLong(); + + private final AtomicLong saveCounter = new AtomicLong(); + + private final ConnectionPoolDataSource source; + + private final ArrayBlockingQueue queue; + + private final ConnectionEventListener listener; + + private final DataDefaultSource dataSource; + + private final String stype; // "" 或 "read" 或 "write" + + private final int max; + + private String url; + + private String user; + + private String password; + + public JDBCPoolSource(DataDefaultSource source, String stype, Properties prop) { + this.dataSource = source; + this.stype = stype; + this.source = createDataSource(prop); + this.url = prop.getProperty(JDBC_URL); + this.user = prop.getProperty(JDBC_USER); + this.password = prop.getProperty(JDBC_PWD); + this.max = Integer.decode(prop.getProperty(JDBC_CONNECTIONMAX, "" + Runtime.getRuntime().availableProcessors() * 16)); + this.queue = new ArrayBlockingQueue<>(this.max); + this.listener = new ConnectionEventListener() { + + @Override + public void connectionClosed(ConnectionEvent event) { + PooledConnection pc = (PooledConnection) event.getSource(); + if (queue.offer(pc)) saveCounter.incrementAndGet(); + } + + @Override + public void connectionErrorOccurred(ConnectionEvent event) { + usingCounter.decrementAndGet(); + if ("08S01".equals(event.getSQLException().getSQLState())) return; //MySQL特性, 长时间连接没使用会抛出com.mysql.jdbc.exceptions.jdbc4.CommunicationsException + dataSource.logger.log(Level.WARNING, "connectionErronOccurred [" + event.getSQLException().getSQLState() + "]", event.getSQLException()); + } + }; + try { + this.watch(); + } catch (Exception e) { + dataSource.logger.log(Level.WARNING, DataSource.class.getSimpleName() + " watch " + dataSource.conf + " error", e); + } + } + + public boolean isMysql() { + return source != null && source.getClass().getName().contains(".mysql."); + } + + private void watch() throws IOException { + if (dataSource.conf == null || dataSource.name == null) return; + final String file = dataSource.conf.getFile(); + final File f = new File(file); + if (!f.isFile() || !f.canRead()) return; + synchronized (maps) { + AbstractMap.SimpleEntry>> entry = maps.get(file); + if (entry != null) { + entry.getValue().add(new WeakReference<>(this)); + return; + } + final WatchService watcher = f.toPath().getFileSystem().newWatchService(); + final List> list = new CopyOnWriteArrayList<>(); + Thread watchThread = new Thread() { + + @Override + public void run() { + try { + while (!this.isInterrupted()) { + final WatchKey key = watcher.take(); + Thread.sleep(3000); //防止文件正在更新过程中去读取 + final Map m = loadProperties(new FileInputStream(file)); + key.pollEvents().stream().forEach((event) -> { + if (event.kind() != ENTRY_MODIFY) return; + if (!((Path) event.context()).toFile().getName().equals(f.getName())) return; + for (WeakReference ref : list) { + JDBCPoolSource pool = ref.get(); + if (pool == null) continue; + try { + Properties property = m.get(pool.dataSource.name); + if (property == null) property = m.get(pool.dataSource.name + "." + pool.stype); + if (property != null) pool.change(property); + } catch (Exception ex) { + dataSource.logger.log(Level.INFO, event.context() + " occur error", ex); + } + } + }); + key.reset(); + } + } catch (Exception e) { + dataSource.logger.log(Level.WARNING, "DataSource watch " + file + " occur error", e); + } + } + }; + f.getParentFile().toPath().register(watcher, ENTRY_MODIFY); + watchThread.setName("DataSource-Watch-" + maps.size() + "-Thread"); + watchThread.setDaemon(true); + watchThread.start(); + dataSource.logger.log(Level.FINER, watchThread.getName() + " start watching " + file); + //----------------------------------------------------------- + list.add(new WeakReference<>(this)); + maps.put(file, new AbstractMap.SimpleEntry<>(watcher, list)); + } + } + + public void change(Properties property) { + Method seturlm; + Class clazz = source.getClass(); + String newurl = property.getProperty(JDBC_URL); + String newuser = property.getProperty(JDBC_USER); + String newpassword = property.getProperty(JDBC_PWD); + if (this.url.equals(newurl) && this.user.equals(newuser) && this.password.equals(newpassword)) return; + try { + try { + seturlm = clazz.getMethod("setUrl", String.class); + } catch (Exception e) { + seturlm = clazz.getMethod("setURL", String.class); + } + seturlm.invoke(source, newurl); + clazz.getMethod("setUser", String.class).invoke(source, newuser); + clazz.getMethod("setPassword", String.class).invoke(source, newpassword); + this.url = newurl; + this.user = newuser; + this.password = newpassword; + dataSource.logger.log(Level.INFO, DataSource.class.getSimpleName() + "(" + dataSource.name + "." + stype + ") change (" + property + ")"); + } catch (Exception e) { + dataSource.logger.log(Level.SEVERE, DataSource.class.getSimpleName() + " dynamic change JDBC (url userName password) error", e); + } + } + + public Connection poll() { + return poll(0, null); + } + + private Connection poll(final int count, SQLException e) { + if (count >= 3) { + dataSource.logger.log(Level.WARNING, "create pooled connection error", e); + throw new RuntimeException(e); + } + PooledConnection result = queue.poll(); + if (result == null) { + if (usingCounter.get() >= max) { + try { + result = queue.poll(6, TimeUnit.SECONDS); + } catch (Exception t) { + dataSource.logger.log(Level.WARNING, "take pooled connection error", t); + } + } + if (result == null) { + try { + result = source.getPooledConnection(); + result.addConnectionEventListener(listener); + usingCounter.incrementAndGet(); + } catch (SQLException ex) { + return poll(count + 1, ex); + } + creatCounter.incrementAndGet(); + } + } else { + cycleCounter.incrementAndGet(); + } + Connection conn; + try { + conn = result.getConnection(); + if (!conn.isValid(1)) { + dataSource.logger.info("sql connection is not vaild"); + usingCounter.decrementAndGet(); + return poll(0, null); + } + } catch (SQLException ex) { + if (!"08S01".equals(ex.getSQLState())) {//MySQL特性, 长时间连接没使用会抛出com.mysql.jdbc.exceptions.jdbc4.CommunicationsException + dataSource.logger.log(Level.FINER, "result.getConnection from pooled connection abort [" + ex.getSQLState() + "]", ex); + } + return poll(0, null); + } + return conn; + } + + public long getCreatCount() { + return creatCounter.longValue(); + } + + public long getCycleCount() { + return cycleCounter.longValue(); + } + + public long getSaveCount() { + return saveCounter.longValue(); + } + + public void close() { + queue.stream().forEach(x -> { + try { + x.close(); + } catch (Exception e) { + } + }); + } +} diff --git a/src/org/redkale/source/Range.java b/src/org/redkale/source/Range.java new file mode 100644 index 000000000..d97318d3b --- /dev/null +++ b/src/org/redkale/source/Range.java @@ -0,0 +1,284 @@ +/* + * 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.source; + +/** + * + * @author zhangjx + * @param + */ +public interface Range extends java.io.Serializable { + + public E getMin(); + + public E getMax(); + + public static final class ByteRange implements Range { + + private Byte min = Byte.MIN_VALUE; + + private Byte max = Byte.MAX_VALUE; + + public ByteRange() { + } + + public ByteRange(Byte min, Byte max) { + if (min != null) this.min = min; + if (max != null) this.max = max; + } + + @Override + public Byte getMin() { + return min; + } + + @Override + public Byte getMax() { + return max; + } + + public void setMin(Byte min) { + if (min != null) this.min = min; + } + + public void setMax(Byte max) { + if (max != null) this.max = max; + } + + @Override + public String toString() { + return "{min:" + min + ", max:" + max + "}"; + } + } + + public static final class ShortRange implements Range { + + private Short min = Short.MIN_VALUE; + + private Short max = Short.MAX_VALUE; + + public ShortRange() { + } + + public ShortRange(Short min, Short max) { + if (min != null) this.min = min; + if (max != null) this.max = max; + } + + @Override + public Short getMin() { + return min; + } + + @Override + public Short getMax() { + return max; + } + + public void setMin(Short min) { + if (min != null) this.min = min; + } + + public void setMax(Short max) { + if (max != null) this.max = max; + } + + @Override + public String toString() { + return "{min:" + min + ", max:" + max + "}"; + } + } + + public static final class IntegerRange implements Range { + + private Integer min = Integer.MIN_VALUE; + + private Integer max = Integer.MAX_VALUE; + + public IntegerRange() { + } + + public IntegerRange(Integer min, Integer max) { + if (min != null) this.min = min; + if (max != null) this.max = max; + } + + @Override + public Integer getMin() { + return min; + } + + @Override + public Integer getMax() { + return max; + } + + public void setMin(Integer min) { + if (min != null) this.min = min; + } + + public void setMax(Integer max) { + if (max != null) this.max = max; + } + + @Override + public String toString() { + return "{min:" + min + ", max:" + max + "}"; + } + } + + public static final class LongRange implements Range { + + private Long min = Long.MIN_VALUE; + + private Long max = Long.MAX_VALUE; + + public LongRange() { + } + + public LongRange(Long min, Long max) { + if (min != null) this.min = min; + if (max != null) this.max = max; + } + + @Override + public Long getMin() { + return min; + } + + @Override + public Long getMax() { + return max; + } + + public void setMin(Long min) { + if (min != null) this.min = min; + } + + public void setMax(Long max) { + if (max != null) this.max = max; + } + + @Override + public String toString() { + return "{min:" + min + ", max:" + max + "}"; + } + } + + public static final class FloatRange implements Range { + + private Float min = Float.MIN_VALUE; + + private Float max = Float.MAX_VALUE; + + public FloatRange() { + } + + public FloatRange(Float min, Float max) { + if (min != null) this.min = min; + if (max != null) this.max = max; + } + + @Override + public Float getMin() { + return min; + } + + @Override + public Float getMax() { + return max; + } + + public void setMin(Float min) { + if (min != null) this.min = min; + } + + public void setMax(Float max) { + if (max != null) this.max = max; + } + + @Override + public String toString() { + return "{min:" + min + ", max:" + max + "}"; + } + } + + public static final class DoubleRange implements Range { + + private Double min = Double.MIN_VALUE; + + private Double max = Double.MAX_VALUE; + + public DoubleRange() { + } + + public DoubleRange(Double min, Double max) { + if (min != null) this.min = min; + if (max != null) this.max = max; + } + + @Override + public Double getMin() { + return min; + } + + @Override + public Double getMax() { + return max; + } + + public void setMin(Double min) { + if (min != null) this.min = min; + } + + public void setMax(Double max) { + if (max != null) this.max = max; + } + + @Override + public String toString() { + return "{min:" + min + ", max:" + max + "}"; + } + } + + public static final class StringRange implements Range { + + private String min = ""; + + private String max = ""; + + public StringRange() { + } + + public StringRange(String min, String max) { + this.min = min; + this.max = max; + } + + @Override + public String getMin() { + return min; + } + + @Override + public String getMax() { + return max; + } + + public void setMin(String min) { + this.min = min; + } + + public void setMax(String max) { + this.max = max; + } + + @Override + public String toString() { + return "{min:'" + min + "', max:'" + max + "'}"; + } + } +} diff --git a/src/org/redkale/source/ReckonType.java b/src/org/redkale/source/ReckonType.java new file mode 100644 index 000000000..eae309a45 --- /dev/null +++ b/src/org/redkale/source/ReckonType.java @@ -0,0 +1,21 @@ +/* + * 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.source; + +/** + * + * @author zhangjx + */ +public enum ReckonType { + + AVG, COUNT, DISTINCTCOUNT, MAX, MIN, SUM; + + public String getReckonColumn(String col) { + if (this == COUNT) return this.name() + "(*)"; + if (this == DISTINCTCOUNT) return "COUNT(DISTINCT " + col + ")"; + return this.name() + "(" + col + ")"; + } +} diff --git a/src/org/redkale/source/VirtualEntity.java b/src/org/redkale/source/VirtualEntity.java new file mode 100644 index 000000000..bb29413fb --- /dev/null +++ b/src/org/redkale/source/VirtualEntity.java @@ -0,0 +1,21 @@ +/* + * 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.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * @author zhangjx + */ +@Documented +@Target(TYPE) +@Retention(RUNTIME) +public @interface VirtualEntity { + +} diff --git a/src/org/redkale/util/AnyValue.java b/src/org/redkale/util/AnyValue.java new file mode 100644 index 000000000..b034a4c9c --- /dev/null +++ b/src/org/redkale/util/AnyValue.java @@ -0,0 +1,434 @@ +/* + * 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.util; + +import java.lang.reflect.Array; +import java.util.*; +import java.util.function.BiPredicate; + +/** + * 该类主要用于读取xml配置文件 + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public interface AnyValue { + + /** + * 可读写的AnyValue默认实现类 + * + * @author zhangjx + */ + @SuppressWarnings("unchecked") + public static final class DefaultAnyValue implements AnyValue { + + public static final BiPredicate EQUALS = (String name1, String name2) -> name1.equals(name2); + + public static final BiPredicate EQUALSIGNORE = (String name1, String name2) -> name1.equalsIgnoreCase(name2); + + private final BiPredicate predicate; + + private Entry[] stringValues = new Entry[0]; + + private Entry[] entityValues = new Entry[0]; + + public static final DefaultAnyValue create() { + return new DefaultAnyValue(); + } + + public static final DefaultAnyValue create(String name, String value) { + DefaultAnyValue conf = new DefaultAnyValue(); + conf.addValue(name, value); + return conf; + } + + public static final DefaultAnyValue create(String name, AnyValue value) { + DefaultAnyValue conf = new DefaultAnyValue(); + conf.addValue(name, value); + return conf; + } + + public DefaultAnyValue() { + this(false); + } + + public DefaultAnyValue(boolean ignoreCase) { + this.predicate = ignoreCase ? EQUALSIGNORE : EQUALS; + } + + public DefaultAnyValue(BiPredicate predicate) { + this.predicate = predicate; + } + + public DefaultAnyValue duplicate() { + DefaultAnyValue rs = new DefaultAnyValue(this.predicate); + rs.stringValues = this.stringValues; + rs.entityValues = this.entityValues; + return rs; + } + + public DefaultAnyValue addAll(final AnyValue av) { + if (av == null) return this; + if (av instanceof DefaultAnyValue) { + final DefaultAnyValue adv = (DefaultAnyValue) av; + if (adv.stringValues != null) { + for (Entry en : adv.stringValues) { + this.addValue(en.name, en.value); + } + } + if (adv.entityValues != null) { + for (Entry en : adv.entityValues) { + this.addValue(en.name, en.value); + } + } + } else { + final Entry[] strings = av.getStringEntrys(); + if (strings != null) { + for (Entry en : strings) { + this.addValue(en.name, en.value); + } + } + final Entry[] anys = av.getAnyEntrys(); + if (anys != null) { + for (Entry en : anys) { + this.addValue(en.name, en.value); + } + } + } + return this; + } + + public DefaultAnyValue setAll(final AnyValue av) { + if (av == null) return this; + if (av instanceof DefaultAnyValue) { + final DefaultAnyValue adv = (DefaultAnyValue) av; + if (adv.stringValues != null) { + for (Entry en : adv.stringValues) { + this.setValue(en.name, en.value); + } + } + if (adv.entityValues != null) { + for (Entry en : adv.entityValues) { + this.setValue(en.name, en.value); + } + } + } else { + final Entry[] strings = av.getStringEntrys(); + if (strings != null) { + for (Entry en : strings) { + this.setValue(en.name, en.value); + } + } + final Entry[] anys = av.getAnyEntrys(); + if (anys != null) { + for (Entry en : anys) { + this.setValue(en.name, en.value); + } + } + } + return this; + } + + @Override + public Entry[] getStringEntrys() { + return stringValues; + } + + @Override + public Entry[] getAnyEntrys() { + return entityValues; + } + + @Override + public String[] getNames() { + Set set = new LinkedHashSet<>(); + for (Entry en : this.stringValues) { + set.add(en.name); + } + for (Entry en : this.entityValues) { + set.add(en.name); + } + return set.toArray(new String[set.size()]); + } + + @Override + public String[] getValues(String... names) { + return Entry.getValues(this.predicate, String.class, this.stringValues, names); + } + + @Override + public AnyValue[] getAnyValues(String... names) { + return Entry.getValues(this.predicate, AnyValue.class, this.entityValues, names); + } + + @Override + public String toString() { + return toString(0); + } + + public DefaultAnyValue clear() { + this.stringValues = new Entry[0]; + this.entityValues = new Entry[0]; + return this; + } + + public DefaultAnyValue setValue(String name, String value) { + if (name == null) return this; + if (getValue(name) == null) { + this.addValue(name, value); + } else { + for (Entry en : this.stringValues) { + if (predicate.test(en.name, name)) { + en.value = value; + return this; + } + } + } + return this; + } + + public DefaultAnyValue setValue(String name, AnyValue value) { + if (name == null) return this; + if (getValue(name) == null) { + this.addValue(name, value); + } else { + for (Entry en : this.entityValues) { + if (predicate.test(en.name, name)) { + en.value = value; + return this; + } + } + } + return this; + } + + public DefaultAnyValue addValue(String name, String value) { + if (name == null) return this; + int len = this.stringValues.length; + Entry[] news = new Entry[len + 1]; + System.arraycopy(this.stringValues, 0, news, 0, len); + news[len] = new Entry(name, value); + this.stringValues = news; + return this; + } + + public DefaultAnyValue addValue(String name, AnyValue value) { + if (name == null || value == null) return this; + int len = this.entityValues.length; + Entry[] news = new Entry[len + 1]; + System.arraycopy(this.entityValues, 0, news, 0, len); + news[len] = new Entry(name, value); + this.entityValues = news; + return this; + } + + @Override + public AnyValue getAnyValue(String name) { + for (Entry en : this.entityValues) { + if (predicate.test(en.name, name)) { + return en.value; + } + } + return null; + } + + @Override + public String getValue(String name) { + for (Entry en : this.stringValues) { + if (predicate.test(en.name, name)) { + return en.value; + } + } + return null; + } + + @Override + public String[] getValues(String name) { + return Entry.getValues(this.predicate, String.class, this.stringValues, name); + } + + @Override + public AnyValue[] getAnyValues(String name) { + return Entry.getValues(this.predicate, AnyValue.class, this.entityValues, name); + } + + } + + public final class Entry { + + public final String name; + + T value; + + public Entry(String name0, T value0) { + this.name = name0; + this.value = value0; + } + + public T getValue() { + return value; + } + + static T[] getValues(BiPredicate comparison, Class clazz, Entry[] entitys, String name) { + int len = 0; + for (Entry en : entitys) { + if (comparison.test(en.name, name)) { + ++len; + } + } + if (len == 0) return (T[]) Array.newInstance(clazz, len); + T[] rs = (T[]) Array.newInstance(clazz, len); + int i = 0; + for (Entry en : entitys) { + if (comparison.test(en.name, name)) { + rs[i++] = en.value; + } + } + return rs; + } + + static T[] getValues(BiPredicate comparison, Class clazz, Entry[] entitys, String... names) { + int len = 0; + for (Entry en : entitys) { + for (String name : names) { + if (comparison.test(en.name, name)) { + ++len; + break; + } + } + } + if (len == 0) return (T[]) Array.newInstance(clazz, len); + T[] rs = (T[]) Array.newInstance(clazz, len); + int i = 0; + for (Entry en : entitys) { + for (String name : names) { + if (comparison.test(en.name, name)) { + rs[i++] = en.value; + break; + } + } + } + return rs; + } + } + + public static AnyValue create() { + return new DefaultAnyValue(); + } + + default String toString(int len) { + if (len < 0) len = 0; + char[] chars = new char[len]; + Arrays.fill(chars, ' '); + final String space = new String(chars); + StringBuilder sb = new StringBuilder(); + sb.append("{\r\n"); + for (Entry en : getStringEntrys()) { + sb.append(space).append(" '").append(en.name).append("': '").append(en.value).append("',\r\n"); + } + for (Entry en : getAnyEntrys()) { + sb.append(space).append(" '").append(en.name).append("': '").append(en.value.toString(len + 4)).append("',\r\n"); + } + sb.append(space).append('}'); + return sb.toString(); + } + + public Entry[] getStringEntrys(); + + public Entry[] getAnyEntrys(); + + public String[] getNames(); + + public String[] getValues(String name); + + public String[] getValues(String... names); + + public AnyValue[] getAnyValues(String name); + + public AnyValue[] getAnyValues(String... names); + + public AnyValue getAnyValue(String name); + + public String getValue(String name); + + default boolean getBoolValue(String name) { + return Boolean.parseBoolean(getValue(name)); + } + + default boolean getBoolValue(String name, boolean defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Boolean.parseBoolean(value); + } + + default byte getByteValue(String name) { + return Byte.parseByte(getValue(name)); + } + + default byte getByteValue(String name, byte defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Byte.decode(value); + } + + default char getCharValue(String name) { + return getValue(name).charAt(0); + } + + default char getCharValue(String name, char defaultValue) { + String value = getValue(name); + return value == null || value.length() == 0 ? defaultValue : value.charAt(0); + } + + default short getShortValue(String name) { + return Short.decode(getValue(name)); + } + + default short getShortValue(String name, short defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Short.decode(value); + } + + default int getIntValue(String name) { + return Integer.decode(getValue(name)); + } + + default int getIntValue(String name, int defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Integer.decode(value); + } + + default long getLongValue(String name) { + return Long.decode(getValue(name)); + } + + default long getLongValue(String name, long defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Long.decode(value); + } + + default float getFloatValue(String name) { + return Float.parseFloat(getValue(name)); + } + + default float getFloatValue(String name, float defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Float.parseFloat(value); + } + + default double getDoubleValue(String name) { + return Double.parseDouble(getValue(name)); + } + + default double getDoubleValue(String name, double defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Double.parseDouble(value); + } + + default String getValue(String name, String defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : value; + } + +} diff --git a/src/org/redkale/util/Attribute.java b/src/org/redkale/util/Attribute.java new file mode 100644 index 000000000..93c348ff9 --- /dev/null +++ b/src/org/redkale/util/Attribute.java @@ -0,0 +1,323 @@ +/* + * 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.util; + +import java.lang.reflect.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import jdk.internal.org.objectweb.asm.Type; + +/** + * 该类功能是动态映射一个Data类中成员对应的getter、setter方法; 代替低效的反射实现方式。 + * 映射Field时,field要么是public非final,要么存在对应的getter、setter方法。 + * + * @author zhangjx + * @param + * @param + */ +public interface Attribute { + + public Class type(); + + public Class declaringClass(); + + public String field(); + + public F get(T obj); + + public void set(T obj, F value); + + /** + * 根据一个Field生成 Attribute 对象。 + * + * @param + * @param + * @param field + * @return + */ + public static Attribute create(final Field field) { + return create((Class) field.getDeclaringClass(), field.getName(), field, null, null); + } + + /** + * 根据一个Field和field的别名生成 Attribute 对象。 + * + * @param + * @param + * @param fieldname 别名 + * @param field + * @return + */ + public static Attribute create(String fieldname, final Field field) { + return create((Class) field.getDeclaringClass(), fieldname, field, null, null); + } + + /** + * 根据一个Class和field名生成 Attribute 对象。 + * + * @param + * @param + * @param clazz + * @param fieldname 字段名, 如果该字段不存在则抛异常 + * @return + */ + public static Attribute create(Class clazz, final String fieldname) { + try { + return create(clazz, fieldname, clazz.getDeclaredField(fieldname), null, null); + } catch (NoSuchFieldException | SecurityException ex) { + throw new RuntimeException(ex); + } + } + + public static Attribute create(Class clazz, final java.lang.reflect.Field field) { + return create(clazz, field.getName(), field); + } + + public static Attribute create(Class clazz, final String fieldname, final java.lang.reflect.Field field) { + return create(clazz, fieldname, field, null, null); + } + + public static Attribute create(final Method getter, final Method setter) { + return create((Class) (getter == null ? setter.getDeclaringClass() : getter.getDeclaringClass()), null, null, getter, setter); + } + + public static Attribute create(Class clazz, final Method getter, final Method setter) { + return create(clazz, null, null, getter, setter); + } + + public static Attribute create(Class clazz, final String fieldalias, final Method getter, final Method setter) { + return create(clazz, fieldalias, null, getter, setter); + } + + @SuppressWarnings("unchecked") + public static Attribute create(final Class clazz, String fieldalias0, final Field field0, Method getter0, Method setter0) { + if (fieldalias0 != null && fieldalias0.isEmpty()) fieldalias0 = null; + int mod = field0 == null ? Modifier.STATIC : field0.getModifiers(); + if (field0 != null && !Modifier.isStatic(mod) && !Modifier.isPublic(mod)) { + Class t = field0.getType(); + char[] fs = field0.getName().toCharArray(); + fs[0] = Character.toUpperCase(fs[0]); + String mn = new String(fs); + if (getter0 == null) { + String prefix = t == boolean.class || t == Boolean.class ? "is" : "get"; + try { + getter0 = clazz.getMethod(prefix + mn); + } catch (Exception ex) { + } + } + if (setter0 == null) { + try { + setter0 = clazz.getMethod("set" + mn, field0.getType()); + } catch (Exception ex) { + } + } + } + final Field field = field0 == null ? null : (!Modifier.isPublic(mod) || Modifier.isStatic(mod) ? null : field0); + final java.lang.reflect.Method getter = getter0; + final java.lang.reflect.Method setter = setter0; + if (fieldalias0 == null) { + if (field0 != null) { + fieldalias0 = field0.getName(); + } else { + String s; + if (getter0 != null) { + s = getter0.getName().substring(getter0.getName().startsWith("is") ? 2 : 3); + } else { + s = setter0.getName().substring(3); + } + char[] d = s.toCharArray(); + if (d.length < 2 || Character.isLowerCase(d[1])) { + d[0] = Character.toLowerCase(d[0]); + } + fieldalias0 = new String(d); + } + } + if (getter == null && setter == null && field == null) { + throw new RuntimeException("[" + clazz + "]have no public field or setter or getter"); + } + final String fieldname = fieldalias0; + Class column; + if (field != null) { // public field + column = field.getType(); + } else if (getter != null) { + column = getter.getReturnType(); + } else { // setter != null + column = setter.getParameterTypes()[0]; + } + final Class pcolumn = column; + if (column.isPrimitive()) column = Array.get(Array.newInstance(column, 1), 0).getClass(); + final String supDynName = Attribute.class.getName().replace('.', '/'); + final String interName = clazz.getName().replace('.', '/'); + final String columnName = column.getName().replace('.', '/'); + final String interDesc = Type.getDescriptor(clazz); + final String columnDesc = Type.getDescriptor(column); + + ClassLoader loader = Attribute.class.getClassLoader(); + String newDynName = supDynName + "_Dyn_" + clazz.getSimpleName() + "_" + + fieldname.substring(fieldname.indexOf('.') + 1) + "_" + pcolumn.getSimpleName().replace("[]", "Array"); + if (String.class.getClassLoader() != clazz.getClassLoader()) { + loader = clazz.getClassLoader(); + newDynName = interName + "_Dyn" + Attribute.class.getSimpleName() + "_" + + fieldname.substring(fieldname.indexOf('.') + 1) + "_" + pcolumn.getSimpleName().replace("[]", "Array"); + } + try { + return (Attribute) Class.forName(newDynName.replace('/', '.')).newInstance(); + } catch (Exception ex) { + } + //--------------------------------------------------- + final jdk.internal.org.objectweb.asm.ClassWriter cw = new jdk.internal.org.objectweb.asm.ClassWriter(0); + jdk.internal.org.objectweb.asm.MethodVisitor mv; + + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, "Ljava/lang/Object;L" + supDynName + "<" + interDesc + columnDesc + ">;", "java/lang/Object", new String[]{supDynName}); + + { //构造方法 + mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + + { //field 方法 + mv = cw.visitMethod(ACC_PUBLIC, "field", "()Ljava/lang/String;", null, null); + mv.visitLdcInsn(fieldname); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { //type 方法 + mv = cw.visitMethod(ACC_PUBLIC, "type", "()Ljava/lang/Class;", null, null); + if (pcolumn == boolean.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"); + } else if (pcolumn == byte.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;"); + } else if (pcolumn == char.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;"); + } else if (pcolumn == short.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;"); + } else if (pcolumn == int.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;"); + } else if (pcolumn == float.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;"); + } else if (pcolumn == long.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;"); + } else if (pcolumn == double.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;"); + } else { + mv.visitLdcInsn(Type.getType(pcolumn)); + } + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { //declaringClass 方法 + mv = cw.visitMethod(ACC_PUBLIC, "declaringClass", "()Ljava/lang/Class;", null, null); + mv.visitLdcInsn(Type.getType(clazz)); + mv.visitInsn(ARETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { //get 方法 + mv = cw.visitMethod(ACC_PUBLIC, "get", "(" + interDesc + ")" + columnDesc, null, null); + int m = 1; + if (getter == null) { + if (field == null) { + mv.visitInsn(ACONST_NULL); + } else { //public field + mv.visitVarInsn(ALOAD, 1); + mv.visitFieldInsn(GETFIELD, interName, field.getName(), Type.getDescriptor(pcolumn)); + if (pcolumn != column) { + mv.visitMethodInsn(INVOKESTATIC, columnName, "valueOf", "(" + Type.getDescriptor(pcolumn) + ")" + columnDesc, false); + m = 2; + } + } + } else { + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, interName, getter.getName(), Type.getMethodDescriptor(getter), false); + if (pcolumn != column) { + mv.visitMethodInsn(INVOKESTATIC, columnName, "valueOf", "(" + Type.getDescriptor(pcolumn) + ")" + columnDesc, false); + m = 2; + } + } + mv.visitInsn(ARETURN); + mv.visitMaxs(m, 2); + mv.visitEnd(); + } + { //set 方法 + mv = cw.visitMethod(ACC_PUBLIC, "set", "(" + interDesc + columnDesc + ")V", null, null); + int m = 2; + if (setter == null) { + if (field == null || Modifier.isFinal(field.getModifiers())) { + m = 0; + } else { //public field + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + if (pcolumn != column) { + try { + java.lang.reflect.Method pm = column.getMethod(pcolumn.getSimpleName() + "Value"); + mv.visitMethodInsn(INVOKEVIRTUAL, columnName, pm.getName(), Type.getMethodDescriptor(pm), false); + m = 3; + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + } + mv.visitFieldInsn(PUTFIELD, interName, field.getName(), Type.getDescriptor(pcolumn)); + } + } else { + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + if (pcolumn != column) { + try { + java.lang.reflect.Method pm = column.getMethod(pcolumn.getSimpleName() + "Value"); + mv.visitMethodInsn(INVOKEVIRTUAL, columnName, pm.getName(), Type.getMethodDescriptor(pm), false); + m = 3; + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + } + mv.visitMethodInsn(INVOKEVIRTUAL, interName, setter.getName(), Type.getMethodDescriptor(setter), false); + } + mv.visitInsn(RETURN); + mv.visitMaxs(m, 3); + mv.visitEnd(); + } + { //虚拟get + mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, interName); + mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "get", "(" + interDesc + ")" + columnDesc, false); + mv.visitInsn(ARETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + {//虚拟set + mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, interName); + mv.visitVarInsn(ALOAD, 2); + mv.visitTypeInsn(CHECKCAST, columnName); + mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "set", "(" + interDesc + columnDesc + ")V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + } + cw.visitEnd(); + + byte[] bytes = cw.toByteArray(); + Class creatorClazz = (Class) new ClassLoader(loader) { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass(newDynName.replace('/', '.'), bytes); + try { + return creatorClazz.newInstance(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/src/org/redkale/util/AutoLoad.java b/src/org/redkale/util/AutoLoad.java new file mode 100644 index 000000000..959955c08 --- /dev/null +++ b/src/org/redkale/util/AutoLoad.java @@ -0,0 +1,24 @@ +/* + * 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.util; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 自动全量缓存 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({TYPE}) +@Retention(RUNTIME) +public @interface AutoLoad { + + boolean value() default true; +} diff --git a/src/org/redkale/util/ByteArray.java b/src/org/redkale/util/ByteArray.java new file mode 100644 index 000000000..f6fddf6ad --- /dev/null +++ b/src/org/redkale/util/ByteArray.java @@ -0,0 +1,161 @@ +/* + * 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.util; + +import java.nio.*; +import java.nio.charset.*; + +/** + * + * @author zhangjx + */ +public final class ByteArray { + + private byte[] content; + + private int count; + + public ByteArray() { + this(1024); + } + + public ByteArray(int size) { + content = new byte[Math.max(128, size)]; + } + + public void clear() { + this.count = 0; + } + + public int find(byte value) { + return find(0, value); + } + + public boolean equal(final byte[] bytes) { + if (bytes == null || count != bytes.length) return false; + for (int i = 0; i < count; i++) { + if (content[i] != bytes[i]) return false; + } + return true; + } + + public boolean isEmpty() { + return count == 0; + } + + public int count() { + return count; + } + + public void write(byte[] buf) { + System.arraycopy(this.content, 0, buf, 0, count); + } + + public byte[] directBytes() { + return content; + } + + public int find(int offset, char value) { + return find(offset, (byte) value); + } + + public int find(int offset, byte value) { + return find(offset, -1, value); + } + + public int find(int offset, int limit, char value) { + return find(offset, limit, (byte) value); + } + + public int find(int offset, int limit, byte value) { + byte[] bytes = this.content; + int end = limit > 0 ? limit : count; + for (int i = offset; i < end; i++) { + if (bytes[i] == value) return i; + } + return -1; + } + + public void removeLastByte() { + if (count > 0) count--; + } + + public void addInt(int value) { + add((byte) (value >> 24 & 0xFF), (byte) (value >> 16 & 0xFF), (byte) (value >> 8 & 0xFF), (byte) (value & 0xFF)); + } + + public void add(byte value) { + if (count >= content.length - 1) { + byte[] ns = new byte[content.length + 8]; + System.arraycopy(content, 0, ns, 0, count); + this.content = ns; + } + content[count++] = value; + } + + public void add(byte... values) { + if (count >= content.length - values.length) { + byte[] ns = new byte[content.length + values.length]; + System.arraycopy(content, 0, ns, 0, count); + this.content = ns; + } + System.arraycopy(content, count, values, 0, values.length); + count += values.length; + } + + public void add(ByteBuffer buffer, int len) { + if (len < 1) return; + if (count >= content.length - len) { + byte[] ns = new byte[content.length + len]; + System.arraycopy(content, 0, ns, 0, count); + this.content = ns; + } + buffer.get(content, count, len); + count += len; + } + + @Override + public String toString() { + return new String(content, 0, count); + } + + public String toString(final Charset charset) { + return toString(0, count, charset); + } + + public String toString(final int offset, int len, final Charset charset) { + if (charset == null) return new String(Utility.decodeUTF8(content, offset, len)); + return new String(content, offset, len, charset); + } + + public String toDecodeString(final int offset, int len, final Charset charset) { + int index = offset; + for (int i = offset; i < (offset + len); i++) { + if (content[i] == '+') { + content[index] = ' '; + } else if (content[i] == '%') { + content[index] = (byte) ((hexBit(content[++i]) * 16 + hexBit(content[++i]))); + } else { + content[index] = content[i]; + } + index++; + } + for (int i = index + 1; i < (offset + len); i++) { + content[i] = ' '; + } + len = index - offset; + if (charset == null) return new String(Utility.decodeUTF8(content, offset, len)); + return new String(content, offset, len, charset); + } + + private static int hexBit(byte b) { + if ('0' <= b && '9' >= b) return b - '0'; + if ('a' <= b && 'z' >= b) return b - 'a' + 10; + if ('A' <= b && 'Z' >= b) return b - 'A' + 10; + return b; + } + +} diff --git a/src/org/redkale/util/Creator.java b/src/org/redkale/util/Creator.java new file mode 100644 index 000000000..66e41f022 --- /dev/null +++ b/src/org/redkale/util/Creator.java @@ -0,0 +1,207 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.util; + +import java.beans.ConstructorProperties; +import java.lang.reflect.*; +import java.util.*; +import jdk.internal.org.objectweb.asm.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import jdk.internal.org.objectweb.asm.Type; + +/** + * 实现一个类的构造方法。 代替低效的反射实现方式。 + * + * @author zhangjx + * @param + */ +public interface Creator { +// +// static class PooledCreator implements Creator { +// +// private final T defValue; +// +// private final Reproduce reproduce; +// +// private final ReferenceQueue refQueue = new ReferenceQueue(); +// +// private final Queue queue; +// +// private final Creator creator; +// +// public PooledCreator(int max, Class clazz, Creator creator) { +// this.creator = creator; +// this.defValue = creator.create(); +// this.reproduce = Reproduce.create(clazz, clazz); +// this.queue = new ArrayBlockingQueue<>(Math.max(Runtime.getRuntime().availableProcessors() * 2, max)); +// new Thread() { +// { +// setDaemon(true); +// setName(PooledCreator.class.getSimpleName() + " " + clazz.getSimpleName() + " Reference Handler"); +// } +// +// @Override +// public void run() { +// try { +// for (;;) { +// T r = refQueue.remove().get(); +// if (r == null) continue; +// reproduce.copy(r, defValue); +// queue.offer(r); +// } +// } catch (Exception e) { +// //do nothind +// } +// } +// }.start(); +// } +// +// @Override +// public T create(Object... params) { +// T rs = queue.poll(); +// if (rs == null) { +// rs = creator.create(params); +// } +// return new WeakReference<>(rs, refQueue).get(); +// } +// +// } +// +// @SuppressWarnings("unchecked") +// public static Creator create(int max, Class clazz) { +// return new PooledCreator<>(max, clazz, create(clazz)); +// } +// +// @SuppressWarnings("unchecked") +// public static Creator create(int max, Class clazz, Creator creator) { +// return new PooledCreator<>(max, clazz, creator); +// } + + public T create(Object... params); + + @SuppressWarnings("unchecked") + public static Creator create(Class clazz) { + if (clazz.isAssignableFrom(ArrayList.class)) { + clazz = (Class) ArrayList.class; + } else if (clazz.isAssignableFrom(HashMap.class)) { + clazz = (Class) HashMap.class; + } else if (clazz.isAssignableFrom(HashSet.class)) { + clazz = (Class) HashSet.class; + } + if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) { + throw new RuntimeException("[" + clazz + "] is a interface or abstract class, cannot create it's Creator."); + } + final String supDynName = Creator.class.getName().replace('.', '/'); + final String interName = clazz.getName().replace('.', '/'); + final String interDesc = Type.getDescriptor(clazz); + ClassLoader loader = Creator.class.getClassLoader(); + String newDynName = supDynName + "_" + clazz.getSimpleName() + "_" + (System.currentTimeMillis() % 10000); + if (String.class.getClassLoader() != clazz.getClassLoader()) { + loader = clazz.getClassLoader(); + newDynName = interName + "_Dyn" + Creator.class.getSimpleName(); + } + try { + return (Creator) Class.forName(newDynName.replace('/', '.')).newInstance(); + } catch (Exception ex) { + } + Constructor constructor = null; + for (Constructor c : clazz.getConstructors()) { + if (c.getParameterCount() == 0) { + constructor = c; + break; + } + } + if (constructor == null) { + for (Constructor c : clazz.getConstructors()) { + if (c.getAnnotation(ConstructorProperties.class) != null) { + constructor = c; + break; + } + } + } + if (constructor == null) throw new RuntimeException("[" + clazz + "] have no public or java.beans.ConstructorProperties-Annotation constructor."); + //------------------------------------------------------------- + ClassWriter cw = new ClassWriter(0); + FieldVisitor fv; + MethodVisitor mv; + AnnotationVisitor av0; + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, "Ljava/lang/Object;L" + supDynName + "<" + interDesc + ">;", "java/lang/Object", new String[]{supDynName}); + + {//构造方法 + mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + ConstructorProperties cps = constructor.getAnnotation(ConstructorProperties.class); + if (cps != null) { + av0 = mv.visitAnnotation(Type.getDescriptor(ConstructorProperties.class), true); + AnnotationVisitor av1 = av0.visitArray("value"); + for (String n : cps.value()) { + av1.visit(null, n); + } + av1.visitEnd(); + av0.visitEnd(); + } + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + {//create 方法 + mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "create", "([Ljava/lang/Object;)L" + interName + ";", null, null); + mv.visitTypeInsn(NEW, interName); + mv.visitInsn(DUP); + //--------------------------------------- + { + Parameter[] params = constructor.getParameters(); + final int[] iconsts = {ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5}; + for (int i = 0; i < params.length; i++) { + mv.visitVarInsn(ALOAD, 1); + if (i < 6) { + mv.visitInsn(iconsts[i]); + } else { + mv.visitIntInsn(BIPUSH, i); + } + mv.visitInsn(AALOAD); + Class ct = params[i].getType(); + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(ct)); + if (ct.isPrimitive()) { + Class fct = Array.get(Array.newInstance(ct, 1), 0).getClass(); + try { + Method pm = ct.getMethod(ct.getSimpleName() + "Value"); + mv.visitMethodInsn(INVOKEVIRTUAL, fct.getName().replace('.', '/'), pm.getName(), Type.getMethodDescriptor(pm), false); + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + } + } + } + //--------------------------------------- + mv.visitMethodInsn(INVOKESPECIAL, interName, "", Type.getConstructorDescriptor(constructor), false); + mv.visitInsn(ARETURN); + mv.visitMaxs((constructor.getParameterCount() > 0 ? (constructor.getParameterCount() + 3) : 2), 2); + mv.visitEnd(); + } + { //虚拟 create 方法 + mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_VARARGS + ACC_SYNTHETIC, "create", "([Ljava/lang/Object;)Ljava/lang/Object;", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "create", "([Ljava/lang/Object;)" + interDesc, false); + mv.visitInsn(ARETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + cw.visitEnd(); + byte[] bytes = cw.toByteArray(); + Class creatorClazz = new ClassLoader(loader) { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass(newDynName.replace('/', '.'), bytes); + try { + return (Creator) creatorClazz.newInstance(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/src/org/redkale/util/DLong.java b/src/org/redkale/util/DLong.java new file mode 100644 index 000000000..6d5351f0e --- /dev/null +++ b/src/org/redkale/util/DLong.java @@ -0,0 +1,81 @@ +/* + * 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.util; + +/** + * + * @author zhangjx + */ +public final class DLong extends Number implements Comparable { + + private final long first; + + private final long second; + + public DLong(long one, long two) { + this.first = one; + this.second = two; + } + + public long getFirst() { + return first; + } + + public long getSecond() { + return second; + } + + public boolean equals(long one, long two) { + return this.first == one && this.second == two; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final DLong other = (DLong) obj; + return (this.first == other.first && this.second == other.second); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 89 * hash + (int) (this.first ^ (this.first >>> 32)); + hash = 89 * hash + (int) (this.second ^ (this.second >>> 32)); + return hash; + } + + @Override + public String toString() { + return this.first + "_" + this.second; + } + + @Override + public int intValue() { + return (int) longValue(); + } + + @Override + public long longValue() { + return first ^ second; + } + + @Override + public float floatValue() { + return (float) longValue(); + } + + @Override + public double doubleValue() { + return (double) longValue(); + } + + @Override + public int compareTo(DLong o) { + return (int) (first == o.first ? (second - o.second) : (first - o.first)); + } + +} diff --git a/src/org/redkale/util/DebugMethodVisitor.java b/src/org/redkale/util/DebugMethodVisitor.java new file mode 100644 index 000000000..3bd42caeb --- /dev/null +++ b/src/org/redkale/util/DebugMethodVisitor.java @@ -0,0 +1,150 @@ +/* + * 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.util; + +import java.util.*; +import jdk.internal.org.objectweb.asm.*; + +/** + * + * @author zhangjx + */ +public class DebugMethodVisitor { + + private final MethodVisitor visitor; + + private boolean debug = false; + + public DebugMethodVisitor setDebug(boolean d) { + debug = d; + return this; + } + + private final Map labels = new LinkedHashMap<>(); + + private static final String[] opcodes = new String[200]; //0 -18 + + static { + try { + for (java.lang.reflect.Field field : Opcodes.class.getFields()) { + String name = field.getName(); + if (name.startsWith("ASM")) continue; + if (name.startsWith("V1_")) continue; + if (name.startsWith("ACC_")) continue; + if (name.startsWith("T_")) continue; + if (name.startsWith("H_")) continue; + if (name.startsWith("F_")) continue; + if (field.getType() != int.class) continue; + opcodes[(int) field.get(null)] = name; + } + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + } + + public DebugMethodVisitor(MethodVisitor visitor) { + //super(Opcodes.ASM5, visitor); + this.visitor = visitor; + } + + public AnnotationVisitor visitParameterAnnotation(int i, String string, boolean bln) { + AnnotationVisitor av = visitor.visitParameterAnnotation(i, string, bln); + if (debug) System.out.println("mv.visitParameterAnnotation(" + i + ", \"" + string + "\", " + bln + ");"); + return av; + } + + public AnnotationVisitor visitAnnotation(String desc, boolean flag) { + AnnotationVisitor av = visitor.visitAnnotation(desc, flag); + if (debug) System.out.println("mv.visitAnnotation(\"" + desc + "\", " + flag + ");"); + return av; + } + + public void visitParameter(String name, int access) { + visitor.visitParameter(name, access); + if (debug) System.out.println("mv.visitParameter(" + name + ", " + access + ");"); + } + + public void visitVarInsn(int opcode, int var) { + visitor.visitVarInsn(opcode, var); + if (debug) System.out.println("mv.visitVarInsn(" + opcodes[opcode] + ", " + var + ");"); + } + + public void visitJumpInsn(int opcode, Label var) { //调用次方法的 ClassWriter 必须由 COMPUTE_FRAMES 构建 + visitor.visitJumpInsn(opcode, var); + if (debug) { + Integer index = labels.get(var); + if (index == null) { + index = labels.size(); + labels.put(var, index); + System.out.println("Label l" + index + " = new Label();"); + } + System.out.println("mv.visitJumpInsn(" + opcodes[opcode] + ", l" + index + ");"); + } + } + + public void visitCode() { + visitor.visitCode(); + if (debug) System.out.println("mv.visitCode();"); + } + + public void visitLabel(Label var) { + visitor.visitLabel(var); + if (debug) { + Integer index = labels.get(var); + if (index == null) { + index = labels.size(); + labels.put(var, index); + System.out.println("Label l" + index + " = new Label();"); + } + System.out.println("mv.visitLabel(l" + index + ");"); + } + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + visitor.visitMethodInsn(opcode, owner, name, desc, itf); + if (debug) System.out.println("mv.visitMethodInsn(" + opcodes[opcode] + ", \"" + owner + "\", \"" + name + "\", \"" + desc + "\", " + itf + ");"); + } + + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + visitor.visitFieldInsn(opcode, owner, name, desc); + if (debug) System.out.println("mv.visitFieldInsn(" + opcodes[opcode] + ", \"" + owner + "\", \"" + name + "\", \"" + desc + "\");"); + } + + public void visitTypeInsn(int opcode, String type) { + visitor.visitTypeInsn(opcode, type); + if (debug) System.out.println("mv.visitTypeInsn(" + opcodes[opcode] + ", \"" + type + "\");"); + } + + public void visitInsn(int opcode) { + visitor.visitInsn(opcode); + if (debug) System.out.println("mv.visitInsn(" + opcodes[opcode] + ");"); + } + + public void visitIntInsn(int opcode, int value) { + visitor.visitIntInsn(opcode, value); + if (debug) System.out.println("mv.visitIntInsn(" + opcodes[opcode] + ", " + value + ");"); + } + + public void visitIincInsn(int opcode, int value) { + visitor.visitIincInsn(opcode, value); + if (debug) System.out.println("mv.visitIincInsn(" + opcode + ", " + value + ");"); + } + + public void visitLdcInsn(Object o) { + visitor.visitLdcInsn(o); + if (debug) System.out.println("mv.visitLdcInsn(" + o + ");"); + } + + public void visitMaxs(int maxStack, int maxLocals) { + visitor.visitMaxs(maxStack, maxLocals); + if (debug) System.out.println("mv.visitMaxs(" + maxStack + ", " + maxLocals + ");"); + } + + public void visitEnd() { + visitor.visitEnd(); + if (debug) System.out.println("mv.visitEnd();\r\n\r\n\r\n"); + } +} diff --git a/src/org/redkale/util/Ignore.java b/src/org/redkale/util/Ignore.java new file mode 100644 index 000000000..077e7ece3 --- /dev/null +++ b/src/org/redkale/util/Ignore.java @@ -0,0 +1,22 @@ +/* + * 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.util; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({TYPE, FIELD, METHOD}) +@Retention(RUNTIME) +public @interface Ignore { + +} diff --git a/src/org/redkale/util/LogLevel.java b/src/org/redkale/util/LogLevel.java new file mode 100644 index 000000000..9636c16a7 --- /dev/null +++ b/src/org/redkale/util/LogLevel.java @@ -0,0 +1,24 @@ +/* + * 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.util; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 被标记的日志级别以上的才会被记录 + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({TYPE}) +@Retention(RUNTIME) +public @interface LogLevel { + + String value(); +} diff --git a/src/org/redkale/util/Nameable.java b/src/org/redkale/util/Nameable.java new file mode 100644 index 000000000..71f7c2a54 --- /dev/null +++ b/src/org/redkale/util/Nameable.java @@ -0,0 +1,15 @@ +/* + * 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.util; + +/** + * + * @author zhangjx + */ +public interface Nameable { + + String name(); +} diff --git a/src/org/redkale/util/ObjectNode.java b/src/org/redkale/util/ObjectNode.java new file mode 100644 index 000000000..79291d845 --- /dev/null +++ b/src/org/redkale/util/ObjectNode.java @@ -0,0 +1,26 @@ +/* + * 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.util; + +import java.util.*; + +/** + * + * @author zhangjx + */ +public class ObjectNode extends HashMap { + + public static ObjectNode create(String key, Object value) { + ObjectNode node = new ObjectNode(); + node.put(key, value); + return node; + } + + public ObjectNode appand(String key, Object value) { + this.put(key, value); + return this; + } +} diff --git a/src/org/redkale/util/ObjectPool.java b/src/org/redkale/util/ObjectPool.java new file mode 100644 index 000000000..fa647c216 --- /dev/null +++ b/src/org/redkale/util/ObjectPool.java @@ -0,0 +1,99 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.util; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.function.*; +import java.util.logging.*; + +/** + * + * @author zhangjx + * @param + */ +public final class ObjectPool implements Supplier { + + private static final Logger logger = Logger.getLogger(ObjectPool.class.getSimpleName()); + + private final boolean debug; + + private final Queue queue; + + private Creator creator; + + private final Consumer prepare; + + private final Predicate recycler; + + private final AtomicLong creatCounter; + + private final AtomicLong cycleCounter; + + public ObjectPool(Class clazz, Consumer prepare, Predicate recycler) { + this(2, clazz, prepare, recycler); + } + + public ObjectPool(int max, Class clazz, Consumer prepare, Predicate recycler) { + this(max, Creator.create(clazz), prepare, recycler); + } + + public ObjectPool(Creator creator, Consumer prepare, Predicate recycler) { + this(2, creator, prepare, recycler); + } + + public ObjectPool(int max, Creator creator, Consumer prepare, Predicate recycler) { + this(null, null, max, creator, prepare, recycler); + } + + public ObjectPool(AtomicLong creatCounter, AtomicLong cycleCounter, int max, Creator creator, Consumer prepare, Predicate recycler) { + this.creatCounter = creatCounter; + this.cycleCounter = cycleCounter; + this.creator = creator; + this.prepare = prepare; + this.recycler = recycler; + this.queue = new LinkedBlockingQueue(Math.max(Runtime.getRuntime().availableProcessors() * 2, max)); + this.debug = logger.isLoggable(Level.FINER); + } + + public void setCreator(Creator creator) { + this.creator = creator; + } + + @Override + public T get() { + T result = queue.poll(); + if (result == null) { + if (creatCounter != null) creatCounter.incrementAndGet(); + result = this.creator.create(); + } + if (prepare != null) prepare.accept(result); + return result; + } + + public void offer(final T e) { + if (e != null && recycler.test(e)) { + if (cycleCounter != null) cycleCounter.incrementAndGet(); + if (debug) { + for (T t : queue) { + if (t == e) { + logger.log(Level.WARNING, "[" + Thread.currentThread().getName() + "] repeat offer the same object(" + e + ")", new Exception()); + return; + } + } + } + queue.offer(e); + } + } + + public long getCreatCount() { + return creatCounter.longValue(); + } + + public long getCycleCount() { + return cycleCounter.longValue(); + } +} diff --git a/src/org/redkale/util/Reproduce.java b/src/org/redkale/util/Reproduce.java new file mode 100644 index 000000000..29ada3ef7 --- /dev/null +++ b/src/org/redkale/util/Reproduce.java @@ -0,0 +1,132 @@ +package org.redkale.util; + +import java.lang.reflect.Modifier; +import java.util.function.Predicate; +import static jdk.internal.org.objectweb.asm.Opcodes.*; +import jdk.internal.org.objectweb.asm.*; + +public interface Reproduce { + + public D copy(D dest, S src); + + public static Reproduce create(final Class destClass, final Class srcClass) { + return create(destClass, srcClass, null); + } + + @SuppressWarnings("unchecked") + public static Reproduce create(final Class destClass, final Class srcClass, final Predicate columnPredicate) { + // ------------------------------------------------------------------------------ + final String supDynName = Reproduce.class.getName().replace('.', '/'); + final String destName = destClass.getName().replace('.', '/'); + final String srcName = srcClass.getName().replace('.', '/'); + final String destDesc = Type.getDescriptor(destClass); + final String srcDesc = Type.getDescriptor(srcClass); + String newDynName = supDynName + "Dyn_" + destClass.getSimpleName() + "_" + srcClass.getSimpleName(); + ClassLoader loader = Reproduce.class.getClassLoader(); + if (String.class.getClassLoader() != destClass.getClassLoader()) { + loader = destClass.getClassLoader(); + newDynName = destName + "_Dyn" + Reproduce.class.getSimpleName() + "_" + srcClass.getSimpleName(); + } + try { + return (Reproduce) Class.forName(newDynName.replace('/', '.')).newInstance(); + } catch (Exception ex) { + } + // ------------------------------------------------------------------------------ + ClassWriter cw = new ClassWriter(0); + FieldVisitor fv; + MethodVisitor mv; + AnnotationVisitor av0; + + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, "Ljava/lang/Object;L" + supDynName + "<" + destDesc + srcDesc + ">;", "java/lang/Object", new String[]{supDynName}); + + { // 构造函数 + mv = (cw.visitMethod(ACC_PUBLIC, "", "()V", null, null)); + //mv.setDebug(true); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { + mv = (cw.visitMethod(ACC_PUBLIC, "copy", "(" + destDesc + srcDesc + ")" + destDesc, null, null)); + //mv.setDebug(true); + + for (java.lang.reflect.Field field : srcClass.getFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + if (Modifier.isFinal(field.getModifiers())) continue; + if (!Modifier.isPublic(field.getModifiers())) continue; + final String fname = field.getName(); + try { + if (!field.getType().equals(destClass.getField(fname).getType())) continue; + if (!columnPredicate.test(fname)) continue; + } catch (Exception e) { + continue; + } + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + String td = Type.getDescriptor(field.getType()); + mv.visitFieldInsn(GETFIELD, srcName, fname, td); + mv.visitFieldInsn(PUTFIELD, destName, fname, td); + } + + for (java.lang.reflect.Method getter : srcClass.getMethods()) { + if (Modifier.isStatic(getter.getModifiers())) continue; + if (getter.getParameterCount() > 0) continue; + if ("getClass".equals(getter.getName())) continue; + if (!getter.getName().startsWith("get") && !getter.getName().startsWith("is")) continue; + java.lang.reflect.Method setter; + boolean is = getter.getName().startsWith("is"); + try { + setter = destClass.getMethod(getter.getName().replaceFirst(is ? "is" : "get", "set"), getter.getReturnType()); + if (columnPredicate != null) { + String col = setter.getName().substring(3); + if (col.length() < 2 || Character.isLowerCase(col.charAt(1))) { + char[] cs = col.toCharArray(); + cs[0] = Character.toLowerCase(cs[0]); + col = new String(cs); + } + if (!columnPredicate.test(col)) continue; + } + } catch (Exception e) { + continue; + } + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, srcName, getter.getName(), Type.getMethodDescriptor(getter), false); + mv.visitMethodInsn(INVOKEVIRTUAL, destName, setter.getName(), Type.getMethodDescriptor(setter), false); + } + mv.visitVarInsn(ALOAD, 1); + mv.visitInsn(ARETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + } + { + mv = (cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "copy", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", null, null)); + //mv.setDebug(true); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(ALOAD, 1); + mv.visitTypeInsn(CHECKCAST, destName); + mv.visitVarInsn(ALOAD, 2); + mv.visitTypeInsn(CHECKCAST, srcName); + mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "copy", "(" + destDesc + srcDesc + ")" + destDesc, false); + mv.visitInsn(ARETURN); + mv.visitMaxs(3, 3); + mv.visitEnd(); + } + cw.visitEnd(); + // ------------------------------------------------------------------------------ + byte[] bytes = cw.toByteArray(); + Class creatorClazz = new ClassLoader(loader) { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass(newDynName.replace('/', '.'), bytes); + try { + return (Reproduce) creatorClazz.newInstance(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + +} diff --git a/src/org/redkale/util/ResourceFactory.java b/src/org/redkale/util/ResourceFactory.java new file mode 100644 index 000000000..aeac6d8ab --- /dev/null +++ b/src/org/redkale/util/ResourceFactory.java @@ -0,0 +1,240 @@ +/* + * 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.util; + +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.logging.*; +import java.util.regex.*; +import javax.annotation.*; + +/** + * 如果Resource(name = "$") 表示资源name采用所属对象的name + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public final class ResourceFactory { + + public static final String RESOURCE_PARENT_NAME = "$"; + + private static final Logger logger = Logger.getLogger(ResourceFactory.class.getSimpleName()); + + private final ResourceFactory parent; + + private static final ResourceFactory instance = new ResourceFactory(null); + + private final ConcurrentHashMap interceptmap = new ConcurrentHashMap<>(); + + private final ConcurrentHashMap, ConcurrentHashMap> store = new ConcurrentHashMap<>(); + + private final ConcurrentHashMap> gencstore = new ConcurrentHashMap<>(); + + private ResourceFactory(ResourceFactory parent) { + this.parent = parent; + } + + public static ResourceFactory root() { + return instance; + } + + public ResourceFactory createChild() { + return new ResourceFactory(this); + } + + public void release() { + this.store.clear(); + } + + public void register(final Class clazz, final Object rs) { + register("", clazz, rs); + } + + public void register(final Object rs) { + if (rs != null) register("", rs.getClass(), rs); + } + + public void add(final Type clazz, final Intercepter rs) { + if (clazz == null || rs == null) return; + interceptmap.put(clazz, rs); + } + + public void register(final String name, final Object rs) { + register(name, rs.getClass(), rs); + } + + public void register(final String name, final Class clazz, final A rs) { + ConcurrentHashMap map = this.store.get(clazz); + if (map == null) { + ConcurrentHashMap sub = new ConcurrentHashMap<>(); + sub.put(name, rs); + store.put(clazz, sub); + } else { + map.put(name, rs); + } + } + + public void register(final String name, final Type clazz, final A rs) { + if (clazz instanceof Class) { + register(name, (Class) clazz, rs); + return; + } + ConcurrentHashMap map = this.gencstore.get(clazz); + if (map == null) { + ConcurrentHashMap sub = new ConcurrentHashMap<>(); + sub.put(name, rs); + gencstore.put(clazz, sub); + } else { + map.put(name, rs); + } + } + + public A find(Class clazz) { + return find("", clazz); + } + + public A find(String name, Type clazz) { + Map map = this.gencstore.get(clazz); + if (map != null) { + A rs = (A) map.get(name); + if (rs != null) return rs; + } + if (parent != null) return parent.find(name, clazz); + return null; + } + + public A find(String name, Class clazz) { + Map map = this.store.get(clazz); + if (map != null) { + A rs = (A) map.get(name); + if (rs != null) return rs; + } + if (parent != null) return parent.find(name, clazz); + return null; + } + + public A findChild(String name, Class clazz) { + A rs = find(name, clazz); + if (rs != null) return rs; + Optional, ConcurrentHashMap>> opt = this.store.entrySet().stream() + .filter(x -> clazz.isAssignableFrom(x.getKey()) && x.getValue().containsKey(name)) + .findFirst(); + return opt.isPresent() ? (A) opt.get().getValue().get(name) : null; + } + + public Map find(final Pattern reg, Class clazz, A exclude) { + Map result = new LinkedHashMap<>(); + load(reg, clazz, exclude, result); + return result; + } + + private void load(final Pattern reg, Class clazz, final A exclude, final Map result) { + ConcurrentHashMap map = this.store.get(clazz); + if (map != null) { + map.forEach((x, y) -> { + if (y != exclude && reg.matcher(x).find() && result.get(x) == null) result.put(x, (A) y); + }); + } + if (parent != null) parent.load(reg, clazz, exclude, result); + } + + public boolean inject(final Object src) { + return inject(src, null); + } + + public boolean inject(final Object src, final T attachment) { + return inject(src, attachment, new ArrayList<>()); + } + + private boolean inject(final Object src, final T attachment, final List list) { + if (src == null) return false; + try { + list.add(src); + Class clazz = src.getClass(); + do { + for (Field field : clazz.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + field.setAccessible(true); + final Class classtype = field.getType(); + final Type genctype = field.getGenericType(); + Resource rc = field.getAnnotation(Resource.class); + if (rc == null) { + boolean flag = true; + Object ns = field.get(src); + for (Object o : list) { + if (o == ns) { + flag = false; + break; + } + } + if (ns == null) continue; + if (ns.getClass().isPrimitive() || ns.getClass().isArray() || ns.getClass().getName().startsWith("java")) continue; + if (flag) this.inject(ns, attachment, list); + continue; + } + if (Modifier.isFinal(field.getModifiers())) continue; + final String rcname = (rc.name().equals(RESOURCE_PARENT_NAME) && src instanceof Nameable) ? ((Nameable) src).name() : rc.name(); + Object rs = genctype == classtype ? null : find(rcname, genctype); + if (rs == null) { + if (Map.class.isAssignableFrom(classtype)) { + rs = find(Pattern.compile(rcname.isEmpty() ? ".*" : rcname), (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[1], src); + } else if (rcname.startsWith("property.")) { + rs = find(rcname, String.class); + } else { + rs = find(rcname, classtype); + } + } + if (rs == null) { + Intercepter it = findIntercepter(field.getGenericType(), field); + if (it != null) it.invoke(this, src, field, attachment); + continue; + } + if (!rs.getClass().isPrimitive() && classtype.isPrimitive()) { + if (classtype == int.class) { + rs = Integer.decode(rs.toString()); + } else if (classtype == long.class) { + rs = Long.decode(rs.toString()); + } else if (classtype == short.class) { + rs = Short.decode(rs.toString()); + } else if (classtype == boolean.class) { + rs = "true".equalsIgnoreCase(rs.toString()); + } else if (classtype == byte.class) { + rs = Byte.decode(rs.toString()); + } else if (classtype == float.class) { + rs = Float.parseFloat(rs.toString()); + } else if (classtype == double.class) { + rs = Double.parseDouble(rs.toString()); + } + } + field.set(src, rs); + } + } while ((clazz = clazz.getSuperclass()) != Object.class); + return true; + } catch (Exception ex) { + logger.log(Level.FINER, "inject " + src + " error", ex); + return false; + } + } + + private Intercepter findIntercepter(Type ft, Field field) { + Intercepter it = this.interceptmap.get(ft); + if (it != null) return it; + Class c = field.getType(); + for (Map.Entry en : this.interceptmap.entrySet()) { + Type t = en.getKey(); + if (t == ft) return en.getValue(); + if (t instanceof Class && (((Class) t)).isAssignableFrom(c)) return en.getValue(); + } + return parent == null ? null : parent.findIntercepter(ft, field); + } + + public static interface Intercepter { + + public void invoke(ResourceFactory factory, Object src, Field field, Object attachment); + } + +} diff --git a/src/org/redkale/util/SelectColumn.java b/src/org/redkale/util/SelectColumn.java new file mode 100644 index 000000000..bd1d3b139 --- /dev/null +++ b/src/org/redkale/util/SelectColumn.java @@ -0,0 +1,136 @@ +/* + * 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.util; + +import java.util.*; +import java.util.function.*; +import java.util.regex.*; + +/** + * + * @author zhangjx + */ +public class SelectColumn implements Predicate { + + private Pattern[] patterns; + + private String[] columns; + + private boolean excludable; //是否排除 + + public SelectColumn() { + } + + protected SelectColumn(final String[] columns0, final boolean excludable) { + this.excludable = excludable; + final int len = columns0.length; + if (len < 1) return; + Pattern[] regs = null; + String[] cols = null; + int regcount = 0; + int colcount = 0; + for (String col : columns0) { + boolean reg = false; + for (int i = 0; i < col.length(); i++) { + char ch = col.charAt(i); + if (ch == '^' || ch == '$' || ch == '*' || ch == '?' || ch == '+' || ch == '[' || ch == '(') { + reg = true; + break; + } + } + if (reg) { + if (regs == null) regs = new Pattern[len]; + regs[regcount++] = Pattern.compile(col); + } else { + if (cols == null) cols = new String[len]; + cols[colcount++] = col; + } + } + if (regs != null) { + if (regcount == len) { + this.patterns = regs; + } else { + this.patterns = Arrays.copyOf(regs, regcount); + } + } + if (cols != null) { + if (colcount == len) { + this.columns = cols; + } else { + this.columns = Arrays.copyOf(cols, colcount); + } + } + } + + /** + * class中的字段名 + * + * @param columns + * @return + */ + public static SelectColumn createIncludes(String... columns) { + return new SelectColumn(columns, false); + } + + /** + * class中的字段名 + * + * @param columns + * @return + */ + public static SelectColumn createExcludes(String... columns) { + return new SelectColumn(columns, true); + } + + @Override + public boolean test(final String column) { + if (this.columns != null) { + for (String col : this.columns) { + if (col.equalsIgnoreCase(column)) return !excludable; + } + } + if (this.patterns != null) { + for (Pattern reg : this.patterns) { + if (reg.matcher(column).find()) return !excludable; + } + } + return excludable; + } + + public String[] getColumns() { + return columns; + } + + public void setColumns(String[] columns) { + this.columns = columns; + } + + public boolean isExcludable() { + return excludable; + } + + public void setExcludable(boolean excludable) { + this.excludable = excludable; + } + + public Pattern[] getPatterns() { + return patterns; + } + + public void setPatterns(Pattern[] patterns) { + this.patterns = patterns; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()).append("{excludable=").append(excludable); + if (columns != null) sb.append(", columns=").append(Arrays.toString(columns)); + if (patterns != null) sb.append(", patterns=").append(Arrays.toString(patterns)); + return sb.append('}').toString(); + } + +} diff --git a/src/org/redkale/util/Sheet.java b/src/org/redkale/util/Sheet.java new file mode 100644 index 000000000..4884d5512 --- /dev/null +++ b/src/org/redkale/util/Sheet.java @@ -0,0 +1,88 @@ +/* + * 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.util; + +import java.util.*; + +/** + * + * @author zhangjx + * @param + */ +@SuppressWarnings("unchecked") +public class Sheet implements java.io.Serializable { + + private long total = -1; + + private Collection rows; + + public Sheet() { + super(); + } + + public Sheet(int total, Collection data) { + this((long) total, data); + } + + public Sheet(long total, Collection data) { + this.total = total; + this.rows = (Collection) data; + } + + public static Sheet asSheet(Collection data) { + return data == null ? new Sheet<>() : new Sheet<>(data.size(), data); + } + + public Sheet copyTo(Sheet copy) { + if (copy == null) return copy; + copy.total = this.total; + if (this.getRows() != null) { + copy.setRows(new ArrayList<>(this.getRows())); + } else { + copy.rows = null; + } + return copy; + } + + /** + * 判断数据列表是否为空 + * + * @return + */ + public boolean isEmpty() { + return this.rows == null || this.rows.isEmpty(); + } + + @Override + public String toString() { + return "Sheet[total=" + this.total + ", rows=" + this.rows + "]"; + } + + public long getTotal() { + return this.total; + } + + public void setTotal(long total) { + this.total = total; + } + + public Collection getRows() { + return this.rows; + } + + public List list() { + return list(false); + } + + public List list(boolean created) { + if (this.rows == null) return created ? new ArrayList<>() : null; + return (this.rows instanceof List) ? (List) this.rows : new ArrayList<>(this.rows); + } + + public void setRows(Collection data) { + this.rows = (Collection) data; + } +} diff --git a/src/org/redkale/util/TypeToken.java b/src/org/redkale/util/TypeToken.java new file mode 100644 index 000000000..38646454e --- /dev/null +++ b/src/org/redkale/util/TypeToken.java @@ -0,0 +1,26 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.util; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +/** + * + * @author zhangjx + * @param + */ +public abstract class TypeToken { + + private final Type type; + + public TypeToken() { + type = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + } + + public final Type getType() { + return type; + } +} diff --git a/src/org/redkale/util/Utility.java b/src/org/redkale/util/Utility.java new file mode 100644 index 000000000..5f5ec4724 --- /dev/null +++ b/src/org/redkale/util/Utility.java @@ -0,0 +1,517 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.util; + +import java.io.*; +import java.lang.reflect.Field; +import java.net.*; +import java.nio.ByteBuffer; +import java.nio.charset.*; +import java.time.*; +import java.util.*; +import javax.net.ssl.*; + +/** + * + * @author zhangjx + */ +public final class Utility { + + private static final String format = "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%tL"; + + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private static final char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + private static final sun.misc.Unsafe UNSAFE; + + private static final long strvaloffset; + + private static final long sbvaloffset; + + private static final javax.net.ssl.SSLContext DEFAULTSSL_CONTEXT; + + static { + sun.misc.Unsafe usafe = null; + long fd1 = 0L; + long fd2 = 0L; + try { + Field safeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + safeField.setAccessible(true); + usafe = (sun.misc.Unsafe) safeField.get(null); + fd1 = usafe.objectFieldOffset(String.class.getDeclaredField("value")); + fd2 = usafe.objectFieldOffset(StringBuilder.class.getSuperclass().getDeclaredField("value")); + } catch (Exception e) { + throw new RuntimeException(e); //不可能会发生 + } + UNSAFE = usafe; + strvaloffset = fd1; + sbvaloffset = fd2; + + try { + DEFAULTSSL_CONTEXT = javax.net.ssl.SSLContext.getInstance("SSL"); + DEFAULTSSL_CONTEXT.init(null, new javax.net.ssl.TrustManager[]{new javax.net.ssl.X509TrustManager() { + @Override + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException { + } + + @Override + public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) throws java.security.cert.CertificateException { + } + }}, null); + } catch (Exception e) { + throw new RuntimeException(e); //不可能会发生 + } + } + + private Utility() { + } + + public static String now() { + return String.format(format, System.currentTimeMillis()); + } + + public static void println(String string, ByteBuffer buffer) { + if (buffer == null || !buffer.hasRemaining()) return; + byte[] bytes = new byte[buffer.remaining()]; + buffer.get(bytes); + buffer.flip(); + println(string, bytes); + } + + public static void println(String string, byte... bytes) { + if (bytes == null) return; + StringBuilder sb = new StringBuilder(); + if (string != null) sb.append(string); + sb.append(bytes.length).append(".["); + boolean last = false; + for (byte b : bytes) { + if (last) sb.append(','); + int v = b & 0xff; + if (v < 16) sb.append('0'); + sb.append(Integer.toHexString(v)); + last = true; + } + sb.append(']'); + (System.out).println(sb); + } + + /** + * 返回本机的第一个内网IPv4地址, 没有则返回null + *

+ * @return + */ + public static InetAddress localInetAddress() { + InetAddress back = null; + try { + Enumeration nifs = NetworkInterface.getNetworkInterfaces(); + while (nifs.hasMoreElements()) { + NetworkInterface nif = nifs.nextElement(); + if (!nif.isUp()) continue; + Enumeration eis = nif.getInetAddresses(); + while (eis.hasMoreElements()) { + InetAddress ia = eis.nextElement(); + if (ia.isLoopbackAddress()) back = ia; + if (ia.isSiteLocalAddress()) return ia; + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return back; + } + + public static int today() { + java.time.LocalDate today = java.time.LocalDate.now(); + return today.getYear() * 10000 + today.getMonthValue() * 100 + today.getDayOfMonth(); + } + + //时间点所在星期的周一 + public static long monday(long time) { + ZoneId zid = ZoneId.systemDefault(); + Instant instant = Instant.ofEpochMilli(time); + LocalDate ld = instant.atZone(zid).toLocalDate(); + ld = ld.minusDays(ld.getDayOfWeek().getValue() - 1); + return ld.atStartOfDay(zid).toInstant().toEpochMilli(); + } + + //时间点所在星期的周日 + public static long sunday(long time) { + ZoneId zid = ZoneId.systemDefault(); + Instant instant = Instant.ofEpochMilli(time); + LocalDate ld = instant.atZone(zid).toLocalDate(); + ld = ld.plusDays(7 - ld.getDayOfWeek().getValue()); + return ld.atStartOfDay(zid).toInstant().toEpochMilli(); + } + + //时间点所在月份的1号 + public static long monthFirstDay(long time) { + ZoneId zid = ZoneId.systemDefault(); + Instant instant = Instant.ofEpochMilli(time); + LocalDate ld = instant.atZone(zid).toLocalDate().withDayOfMonth(1); + return ld.atStartOfDay(zid).toInstant().toEpochMilli(); + } + + public static String binToHexString(byte[] bytes) { + return new String(binToHex(bytes)); + } + + public static char[] binToHex(byte[] bytes) { + return binToHex(bytes, 0, bytes.length); + } + + public static String binToHexString(byte[] bytes, int offset, int len) { + return new String(binToHex(bytes, offset, len)); + } + + public static char[] binToHex(byte[] bytes, int offset, int len) { + final char[] sb = new char[len * 2]; + final int end = offset + len; + int index = 0; + final char[] hexs = hex; + for (int i = offset; i < end; i++) { + byte b = bytes[i]; + sb[index++] = (hexs[((b >> 4) & 0xF)]); + sb[index++] = hexs[((b) & 0xF)]; + } + return sb; + } + + public static byte[] hexToBin(CharSequence src) { + return hexToBin(src, 0, src.length()); + } + + public static byte[] hexToBin(CharSequence src, int offset, int len) { + final int size = (len + 1) / 2; + final byte[] bytes = new byte[size]; + final int end = offset + len; + String digits = "0123456789abcdef"; + for (int i = 0; i < size; i++) { + int ch1 = src.charAt(offset + i * 2); + if ('A' <= ch1 && 'F' >= ch1) ch1 = ch1 - 'A' + 'a'; + int ch2 = src.charAt(offset + i * 2 + 1); + if ('A' <= ch2 && 'F' >= ch2) ch2 = ch2 - 'A' + 'a'; + int pos1 = digits.indexOf(ch1); + if (pos1 < 0) throw new NumberFormatException(); + int pos2 = digits.indexOf(ch2); + if (pos2 < 0) throw new NumberFormatException(); + bytes[i] = (byte) (pos1 * 0x10 + pos2); + } + return bytes; + } + + public static byte[] hexToBin(char[] src) { + return hexToBin(src, 0, src.length); + } + + public static byte[] hexToBin(char[] src, int offset, int len) { + final int size = (len + 1) / 2; + final byte[] bytes = new byte[size]; + final int end = offset + len; + String digits = "0123456789abcdef"; + for (int i = 0; i < size; i++) { + int ch1 = src[offset + i * 2]; + if ('A' <= ch1 && 'F' >= ch1) ch1 = ch1 - 'A' + 'a'; + int ch2 = src[offset + i * 2 + 1]; + if ('A' <= ch2 && 'F' >= ch2) ch2 = ch2 - 'A' + 'a'; + int pos1 = digits.indexOf(ch1); + if (pos1 < 0) throw new NumberFormatException(); + int pos2 = digits.indexOf(ch2); + if (pos2 < 0) throw new NumberFormatException(); + bytes[i] = (byte) (pos1 * 0x10 + pos2); + } + return bytes; + } + + //----------------------------------------------------------------------------- + public static char[] decodeUTF8(final byte[] array) { + return decodeUTF8(array, 0, array.length); + } + + public static char[] decodeUTF8(final byte[] array, final int start, final int len) { + byte b; + int size = len; + final byte[] bytes = array; + final int limit = start + len; + for (int i = start; i < limit; i++) { + b = bytes[i]; + if ((b >> 5) == -2) { + size--; + } else if ((b >> 4) == -2) { + size -= 2; + } + } + final char[] text = new char[size]; + size = 0; + for (int i = start; i < limit;) { + b = bytes[i++]; + if (b >= 0) { + text[size++] = (char) b; + } else if ((b >> 5) == -2) { + text[size++] = (char) (((b << 6) ^ bytes[i++]) ^ (((byte) 0xC0 << 6) ^ ((byte) 0x80))); + } else if ((b >> 4) == -2) { + text[size++] = (char) ((b << 12) ^ (bytes[i++] << 6) ^ (bytes[i++] ^ (((byte) 0xE0 << 12) ^ ((byte) 0x80 << 6) ^ ((byte) 0x80)))); + } + } + return text; + } + + public static byte[] encodeUTF8(final String value) { + if (value == null) return new byte[0]; + return encodeUTF8((char[]) UNSAFE.getObject(value, strvaloffset)); + } + + public static byte[] encodeUTF8(final char[] array) { + return encodeUTF8(array, 0, array.length); + } + + public static byte[] encodeUTF8(final char[] text, final int start, final int len) { + char c; + int size = 0; + final char[] chars = text; + final int limit = start + len; + for (int i = start; i < limit; i++) { + c = chars[i]; + if (c < 0x80) { + size++; + } else if (c < 0x800) { + size += 2; + } else { + size += 3; + } + } + final byte[] bytes = new byte[size]; + size = 0; + for (int i = start; i < limit; i++) { + c = chars[i]; + if (c < 0x80) { + bytes[size++] = (byte) c; + } else if (c < 0x800) { + bytes[size++] = (byte) (0xc0 | (c >> 6)); + bytes[size++] = (byte) (0x80 | (c & 0x3f)); + } else { + bytes[size++] = (byte) (0xe0 | ((c >> 12))); + bytes[size++] = (byte) (0x80 | ((c >> 6) & 0x3f)); + bytes[size++] = (byte) (0x80 | (c & 0x3f)); + } + } + return bytes; + } + + public static char[] charArray(String value) { + return value == null ? null : (char[]) UNSAFE.getObject(value, strvaloffset); + } + + public static char[] charArray(StringBuilder value) { + return value == null ? null : (char[]) UNSAFE.getObject(value, sbvaloffset); + } + + public static ByteBuffer encodeUTF8(final ByteBuffer buffer, final char[] array) { + return encodeUTF8(buffer, array, 0, array.length); + } + + public static ByteBuffer encodeUTF8(final ByteBuffer buffer, int bytesLength, final char[] array) { + return encodeUTF8(buffer, bytesLength, array, 0, array.length); + } + + public static int encodeUTF8Length(String value) { + if (value == null) return -1; + return encodeUTF8Length((char[]) UNSAFE.getObject(value, strvaloffset)); + } + + public static int encodeUTF8Length(final char[] text) { + return encodeUTF8Length(text, 0, text.length); + } + + public static int encodeUTF8Length(final char[] text, final int start, final int len) { + char c; + int size = 0; + final char[] chars = text; + final int limit = start + len; + for (int i = start; i < limit; i++) { + c = chars[i]; + size += (c < 0x80 ? 1 : (c < 0x800 ? 2 : 3)); + } + return size; + } + + /** + * 将两个数字组装成一个long + *

+ * @param high + * @param low + * @return + */ + public static long merge(long high, long low) { + return high << 32 | low; + } + + public static ByteBuffer encodeUTF8(final ByteBuffer buffer, final char[] text, final int start, final int len) { + return encodeUTF8(buffer, encodeUTF8Length(text, start, len), text, start, len); + } + + public static ByteBuffer encodeUTF8(final ByteBuffer buffer, int bytesLength, final char[] text, final int start, final int len) { + char c; + char[] chars = text; + final int limit = start + len; + int remain = buffer.remaining(); + final ByteBuffer buffer2 = remain >= bytesLength ? null : ByteBuffer.allocate(bytesLength - remain + 3); //最差情况buffer最后两byte没有填充 + ByteBuffer buf = buffer; + for (int i = start; i < limit; i++) { + c = chars[i]; + if (c < 0x80) { + if (buf.remaining() < 1) buf = buffer2; + buf.put((byte) c); + } else if (c < 0x800) { + if (buf.remaining() < 2) buf = buffer2; + buf.put((byte) (0xc0 | (c >> 6))); + buf.put((byte) (0x80 | (c & 0x3f))); + } else { + if (buf.remaining() < 3) buf = buffer2; + buf.put((byte) (0xe0 | ((c >> 12)))); + buf.put((byte) (0x80 | ((c >> 6) & 0x3f))); + buf.put((byte) (0x80 | (c & 0x3f))); + } + } + if (buffer2 != null) buffer2.flip(); + return buffer2; + } + + //----------------------------------------------------------------------------- + public static javax.net.ssl.SSLContext getDefaultSSLContext() { + return DEFAULTSSL_CONTEXT; + } + + public static Socket createDefaultSSLSocket(InetSocketAddress address) throws IOException { + return createDefaultSSLSocket(address.getAddress(), address.getPort()); + } + + public static Socket createDefaultSSLSocket(InetAddress host, int port) throws IOException { + Socket socket = DEFAULTSSL_CONTEXT.getSocketFactory().createSocket(host, port); + + return socket; + } + + public static String postHttpContent(String url) throws IOException { + return remoteHttpContent(null, "POST", url, null, null).toString("UTF-8"); + } + + public static String postHttpContent(String url, String body) throws IOException { + return remoteHttpContent(null, "POST", url, null, body).toString("UTF-8"); + } + + public static String postHttpContent(String url, Map headers, String body) throws IOException { + return remoteHttpContent(null, "POST", url, headers, body).toString("UTF-8"); + } + + public static String postHttpContent(SSLContext ctx, String url) throws IOException { + return remoteHttpContent(ctx, "POST", url, null, null).toString("UTF-8"); + } + + public static String postHttpContent(SSLContext ctx, String url, String body) throws IOException { + return remoteHttpContent(ctx, "POST", url, null, body).toString("UTF-8"); + } + + public static String postHttpContent(SSLContext ctx, String url, Map headers, String body) throws IOException { + return remoteHttpContent(ctx, "POST", url, headers, body).toString("UTF-8"); + } + + public static byte[] postHttpBytesContent(String url) throws IOException { + return remoteHttpContent(null, "POST", url, null, null).toByteArray(); + } + + public static byte[] postHttpBytesContent(SSLContext ctx, String url) throws IOException { + return remoteHttpContent(ctx, "POST", url, null, null).toByteArray(); + } + + public static byte[] postHttpBytesContent(String url, Map headers, String body) throws IOException { + return remoteHttpContent(null, "POST", url, headers, body).toByteArray(); + } + + public static byte[] postHttpBytesContent(SSLContext ctx, String url, Map headers, String body) throws IOException { + return remoteHttpContent(ctx, "POST", url, headers, body).toByteArray(); + } + + public static String getHttpContent(String url) throws IOException { + return remoteHttpContent(null, "GET", url, null, null).toString("UTF-8"); + } + + public static String getHttpContent(SSLContext ctx, String url) throws IOException { + return remoteHttpContent(ctx, "GET", url, null, null).toString("UTF-8"); + } + + public static String getHttpContent(SSLContext ctx, String url, Map headers, String body) throws IOException { + return remoteHttpContent(ctx, "GET", url, headers, body).toString("UTF-8"); + } + + public static String getHttpContent(String url, Map headers, String body) throws IOException { + return remoteHttpContent(null, "GET", url, headers, body).toString("UTF-8"); + } + + public static byte[] getHttpBytesContent(String url) throws IOException { + return remoteHttpContent(null, "GET", url, null, null).toByteArray(); + } + + public static byte[] getHttpBytesContent(SSLContext ctx, String url) throws IOException { + return remoteHttpContent(ctx, "GET", url, null, null).toByteArray(); + } + + public static byte[] getHttpBytesContent(String url, Map headers, String body) throws IOException { + return remoteHttpContent(null, "GET", url, headers, body).toByteArray(); + } + + public static byte[] getHttpBytesContent(SSLContext ctx, String url, Map headers, String body) throws IOException { + return remoteHttpContent(ctx, "GET", url, headers, body).toByteArray(); + } + + protected static ByteArrayOutputStream remoteHttpContent(SSLContext ctx, String method, String url, Map headers, String body) throws IOException { + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setConnectTimeout(3000); + conn.setReadTimeout(3000); + if (conn instanceof HttpsURLConnection) ((HttpsURLConnection) conn).setSSLSocketFactory((ctx == null ? DEFAULTSSL_CONTEXT : ctx).getSocketFactory()); + conn.setRequestMethod(method); + if (headers != null) headers.forEach((x, y) -> conn.setRequestProperty(x, y)); + if (body != null) { + conn.setDoInput(true); + conn.setDoOutput(true); + conn.getOutputStream().write(body.getBytes(UTF_8)); + } + conn.connect(); + int rs = conn.getResponseCode(); + if (rs == 301 || rs == 302) { + String newurl = conn.getHeaderField("Location"); + conn.disconnect(); + return remoteHttpContent(ctx, method, newurl, headers, body); + } + InputStream in = conn.getInputStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + byte[] bytes = new byte[1024]; + int pos; + while ((pos = in.read(bytes)) != -1) { + out.write(bytes, 0, pos); + } + conn.disconnect(); + return out; + } + + public static String read(InputStream in) throws IOException { + return read(in, "UTF-8"); + } + + public static String read(InputStream in, String charsetName) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(1024); + byte[] bytes = new byte[1024]; + int pos; + while ((pos = in.read(bytes)) != -1) { + out.write(bytes, 0, pos); + } + return charsetName == null ? out.toString() : out.toString(charsetName); + } +} diff --git a/src/org/redkale/watch/WatchFactory.java b/src/org/redkale/watch/WatchFactory.java new file mode 100644 index 000000000..328b61749 --- /dev/null +++ b/src/org/redkale/watch/WatchFactory.java @@ -0,0 +1,99 @@ +/* + * 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.watch; + +import java.lang.ref.WeakReference; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.LongSupplier; + +/** + * + * @author zhangjx + */ +public final class WatchFactory { + + private static final WatchFactory instance = new WatchFactory(null); + + private final List> beans = new CopyOnWriteArrayList<>(); + + private final WatchFactory parent; + + private WatchFactory(WatchFactory parent) { + this.parent = parent; + } + + public void register(WatchNode bean) { + if (bean != null) beans.add(new WeakReference<>(bean)); + } + + public static WatchFactory root() { + return instance; + } + + public WatchFactory createChild() { + return new WatchFactory(this); + } + + public WatchNumber createWatchNumber(String name) { + return createWatchNumber(name, "", false, 0); + } + + public WatchNumber createWatchNumber(String name, boolean interval) { + return createWatchNumber(name, "", interval, 0); + } + + public WatchNumber createWatchNumber(String name, String description) { + return createWatchNumber(name, description, false, 0); + } + + public WatchNumber createWatchNumber(String name, String description, long v) { + return createWatchNumber(name, description, false, 0); + } + + public WatchNumber createWatchNumber(String name, String description, boolean interval) { + return createWatchNumber(name, description, interval, 0); + } + + public WatchNumber createWatchNumber(String name, String description, boolean interval, long v) { + WatchNumber bean = new WatchNumber(name, description, interval, v); + register(bean); + return bean; + } + + public void register(String name, LongSupplier supplier) { + register(name, "", supplier); + } + + public void register(String name, String description, LongSupplier supplier) { + register(new WatchSupplier(name, description, supplier)); + } + + public boolean inject(final Object src) { + return inject(src, new ArrayList<>()); + } + + private boolean inject(final Object src, final List list) { + if (src == null) return false; + try { + list.add(src); + Class clazz = src.getClass(); + do { + for (Field field : clazz.getDeclaredFields()) { + if (Modifier.isFinal(field.getModifiers())) continue; + field.setAccessible(true); + final Class type = field.getType(); + Watchable wo = field.getAnnotation(Watchable.class); + + } + } while ((clazz = clazz.getSuperclass()) != Object.class); + return true; + } catch (Exception ex) { + return false; + } + } +} diff --git a/src/org/redkale/watch/WatchNode.java b/src/org/redkale/watch/WatchNode.java new file mode 100644 index 000000000..34e7df3dd --- /dev/null +++ b/src/org/redkale/watch/WatchNode.java @@ -0,0 +1,21 @@ +/* + * 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.watch; + +/** + * + * @author zhangjx + */ +public interface WatchNode { + + public String getName(); + + public String getDescription(); + + public long getValue(); + + public boolean isInterval(); +} diff --git a/src/org/redkale/watch/WatchNumber.java b/src/org/redkale/watch/WatchNumber.java new file mode 100644 index 000000000..5fcb1233a --- /dev/null +++ b/src/org/redkale/watch/WatchNumber.java @@ -0,0 +1,49 @@ +/* + * 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.watch; + +import java.util.concurrent.atomic.*; + +/** + * + * @author zhangjx + */ +public final class WatchNumber extends AtomicLong implements WatchNode { + + private final boolean interval; + + private final String name; + + private final String description; + + WatchNumber(String name, String description, boolean interval, long v) { + this.name = name; + this.description = description; + this.interval = interval; + this.set(v); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public long getValue() { + return super.longValue(); + } + + @Override + public boolean isInterval() { + return interval; + } + +} diff --git a/src/org/redkale/watch/WatchSupplier.java b/src/org/redkale/watch/WatchSupplier.java new file mode 100644 index 000000000..d73548eab --- /dev/null +++ b/src/org/redkale/watch/WatchSupplier.java @@ -0,0 +1,47 @@ +/* + * 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.watch; + +import java.util.function.LongSupplier; + +/** + * + * @author zhangjx + */ +public final class WatchSupplier implements WatchNode { + + private final String name; + + private final String description; + + private final LongSupplier supplier; + + WatchSupplier(String name, String description, LongSupplier supplier) { + this.name = name; + this.description = description; + this.supplier = supplier; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public String getDescription() { + return this.description; + } + + @Override + public long getValue() { + return supplier == null ? Long.MIN_VALUE : supplier.getAsLong(); + } + + @Override + public boolean isInterval() { + return false; + } +} diff --git a/src/org/redkale/watch/Watchable.java b/src/org/redkale/watch/Watchable.java new file mode 100644 index 000000000..f5f70bdc7 --- /dev/null +++ b/src/org/redkale/watch/Watchable.java @@ -0,0 +1,34 @@ +/* + * 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.watch; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 该注解只能放在field类型为Collection, Map, 或者java.util.concurrent.atomic.AtomicXXX的Number类); + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface Watchable { + + String name(); + + String description() default ""; + + /** + * 该值指明是不是只收集阶段数据, 而且被注解的字段只能被赋予java.util.concurrent.atomic.AtomicXXX的Number类型字段。 + * 例如收集每分钟的注册用户数, 就需要将interval设置true。 + * + * @return + */ + boolean interval() default false; +}