From e050c7438d30d43087c2569f0aa43d8d5064aca8 Mon Sep 17 00:00:00 2001 From: RedKale <22250530@qq.com> Date: Thu, 31 Mar 2016 18:40:13 +0800 Subject: [PATCH] --- src/org/redkale/boot/Application.java | 656 +++++++ src/org/redkale/boot/ClassFilter.java | 431 ++++ src/org/redkale/boot/LogFileHandler.java | 276 +++ src/org/redkale/boot/NodeHttpServer.java | 145 ++ src/org/redkale/boot/NodeProtocol.java | 24 + src/org/redkale/boot/NodeServer.java | 493 +++++ src/org/redkale/boot/NodeSncpServer.java | 79 + src/org/redkale/boot/package-info.java | 4 + src/org/redkale/convert/AnyEncoder.java | 42 + src/org/redkale/convert/ArrayDecoder.java | 82 + src/org/redkale/convert/ArrayEncoder.java | 78 + .../redkale/convert/CollectionDecoder.java | 72 + .../redkale/convert/CollectionEncoder.java | 66 + src/org/redkale/convert/Convert.java | 29 + src/org/redkale/convert/ConvertColumn.java | 47 + .../redkale/convert/ConvertColumnEntry.java | 68 + src/org/redkale/convert/ConvertColumns.java | 25 + src/org/redkale/convert/ConvertEntity.java | 27 + src/org/redkale/convert/ConvertException.java | 29 + src/org/redkale/convert/ConvertFactory.java | 486 +++++ src/org/redkale/convert/ConvertType.java | 29 + src/org/redkale/convert/DeMember.java | 85 + src/org/redkale/convert/Decodeable.java | 30 + src/org/redkale/convert/EnMember.java | 78 + src/org/redkale/convert/Encodeable.java | 30 + src/org/redkale/convert/MapDecoder.java | 79 + src/org/redkale/convert/MapEncoder.java | 65 + src/org/redkale/convert/ObjectDecoder.java | 234 +++ src/org/redkale/convert/ObjectEncoder.java | 278 +++ src/org/redkale/convert/Reader.java | 170 ++ src/org/redkale/convert/SimpledCoder.java | 45 + src/org/redkale/convert/Writer.java | 205 ++ .../convert/bson/BsonByteBufferReader.java | 172 ++ .../convert/bson/BsonByteBufferWriter.java | 142 ++ src/org/redkale/convert/bson/BsonConvert.java | 214 ++ src/org/redkale/convert/bson/BsonFactory.java | 72 + src/org/redkale/convert/bson/BsonReader.java | 324 +++ .../convert/bson/BsonSimpledCoder.java | 20 + .../convert/bson/BsonStreamReader.java | 60 + .../convert/bson/BsonStreamWriter.java | 48 + src/org/redkale/convert/bson/BsonWriter.java | 301 +++ .../redkale/convert/bson/package-info.java | 4 + .../convert/ext/BigIntegerSimpledCoder.java | 68 + .../convert/ext/BoolArraySimpledCoder.java | 71 + .../redkale/convert/ext/BoolSimpledCoder.java | 36 + .../convert/ext/ByteArraySimpledCoder.java | 71 + .../redkale/convert/ext/ByteSimpledCoder.java | 36 + .../convert/ext/CharArraySimpledCoder.java | 69 + .../convert/ext/CharSequenceSimpledCoder.java | 31 + .../redkale/convert/ext/CharSimpledCoder.java | 35 + .../ext/CompletionHandlerSimpledCoder.java | 36 + .../convert/ext/DLongSimpledCoder.java | 71 + .../redkale/convert/ext/DateSimpledCoder.java | 35 + .../convert/ext/DoubleArraySimpledCoder.java | 69 + .../convert/ext/DoubleSimpledCoder.java | 34 + .../redkale/convert/ext/EnumSimpledCoder.java | 48 + .../convert/ext/FloatArraySimpledCoder.java | 69 + .../convert/ext/FloatSimpledCoder.java | 34 + .../convert/ext/InetAddressSimpledCoder.java | 145 ++ .../convert/ext/IntArraySimpledCoder.java | 69 + .../redkale/convert/ext/IntSimpledCoder.java | 34 + .../convert/ext/LongArraySimpledCoder.java | 71 + .../redkale/convert/ext/LongSimpledCoder.java | 35 + .../convert/ext/NumberSimpledCoder.java | 36 + .../convert/ext/PatternSimpledCoder.java | 40 + .../convert/ext/ShortArraySimpledCoder.java | 69 + .../convert/ext/ShortSimpledCoder.java | 34 + .../convert/ext/StringArraySimpledCoder.java | 69 + .../convert/ext/StringSimpledCoder.java | 34 + .../redkale/convert/ext/TypeSimpledCoder.java | 46 + .../redkale/convert/ext/URISimpledCoder.java | 39 + .../redkale/convert/ext/URLSimpledCoder.java | 39 + src/org/redkale/convert/ext/package-info.java | 4 + .../convert/json/JsonByteBufferReader.java | 336 ++++ .../convert/json/JsonByteBufferWriter.java | 352 ++++ src/org/redkale/convert/json/JsonConvert.java | 207 ++ src/org/redkale/convert/json/JsonFactory.java | 74 + src/org/redkale/convert/json/JsonReader.java | 582 ++++++ .../convert/json/JsonSimpledCoder.java | 20 + .../convert/json/JsonStreamReader.java | 40 + .../convert/json/JsonStreamWriter.java | 151 ++ src/org/redkale/convert/json/JsonWriter.java | 381 ++++ .../redkale/convert/json/package-info.java | 4 + src/org/redkale/convert/package-info.java | 4 + src/org/redkale/net/AsyncConnection.java | 575 ++++++ src/org/redkale/net/Context.java | 133 ++ src/org/redkale/net/PrepareRunner.java | 99 + src/org/redkale/net/PrepareServlet.java | 92 + src/org/redkale/net/ProtocolServer.java | 184 ++ src/org/redkale/net/Request.java | 117 ++ src/org/redkale/net/Response.java | 230 +++ src/org/redkale/net/Server.java | 199 ++ src/org/redkale/net/Servlet.java | 33 + src/org/redkale/net/Transport.java | 253 +++ src/org/redkale/net/WorkThread.java | 32 + .../redkale/net/http/BasedHttpServlet.java | 325 +++ src/org/redkale/net/http/HttpContext.java | 56 + .../redkale/net/http/HttpPrepareServlet.java | 144 ++ src/org/redkale/net/http/HttpRequest.java | 813 ++++++++ .../redkale/net/http/HttpResourceServlet.java | 262 +++ src/org/redkale/net/http/HttpResponse.java | 739 +++++++ src/org/redkale/net/http/HttpServer.java | 131 ++ src/org/redkale/net/http/HttpServlet.java | 31 + src/org/redkale/net/http/MimeType.java | 199 ++ src/org/redkale/net/http/MultiContext.java | 245 +++ src/org/redkale/net/http/MultiPart.java | 116 ++ src/org/redkale/net/http/WebInitParam.java | 26 + src/org/redkale/net/http/WebServlet.java | 31 + src/org/redkale/net/http/WebSocket.java | 453 +++++ src/org/redkale/net/http/WebSocketBinary.java | 24 + src/org/redkale/net/http/WebSocketEngine.java | 101 + src/org/redkale/net/http/WebSocketGroup.java | 139 ++ src/org/redkale/net/http/WebSocketNode.java | 235 +++ src/org/redkale/net/http/WebSocketPacket.java | 123 ++ src/org/redkale/net/http/WebSocketRunner.java | 526 +++++ .../redkale/net/http/WebSocketServlet.java | 148 ++ src/org/redkale/net/http/package-info.java | 4 + src/org/redkale/net/package-info.java | 4 + src/org/redkale/net/sncp/ServiceWrapper.java | 143 ++ src/org/redkale/net/sncp/Sncp.java | 1102 +++++++++++ src/org/redkale/net/sncp/SncpClient.java | 544 ++++++ src/org/redkale/net/sncp/SncpContext.java | 29 + src/org/redkale/net/sncp/SncpDyn.java | 29 + src/org/redkale/net/sncp/SncpDynServlet.java | 428 ++++ .../redkale/net/sncp/SncpPrepareServlet.java | 69 + src/org/redkale/net/sncp/SncpRequest.java | 139 ++ src/org/redkale/net/sncp/SncpResponse.java | 78 + src/org/redkale/net/sncp/SncpServer.java | 73 + src/org/redkale/net/sncp/SncpServlet.java | 36 + src/org/redkale/net/sncp/package-info.java | 4 + .../redkale/service/CacheSourceService.java | 492 +++++ .../service/DataCacheListenerService.java | 45 + .../service/DataSQLListenerService.java | 128 ++ .../redkale/service/DataSourceService.java | 561 ++++++ src/org/redkale/service/DynAttachment.java | 25 + src/org/redkale/service/DynCall.java | 25 + src/org/redkale/service/DynRemote.java | 24 + src/org/redkale/service/DynSourceAddress.java | 25 + src/org/redkale/service/DynTargetAddress.java | 25 + src/org/redkale/service/LocalService.java | 25 + src/org/redkale/service/MultiRun.java | 33 + src/org/redkale/service/RetResult.java | 116 ++ src/org/redkale/service/Service.java | 47 + .../redkale/service/WebSocketNodeService.java | 82 + src/org/redkale/service/package-info.java | 4 + src/org/redkale/source/CacheSource.java | 86 + src/org/redkale/source/DataCacheListener.java | 22 + .../source/DataCallArrayAttribute.java | 60 + src/org/redkale/source/DataCallAttribute.java | 73 + src/org/redkale/source/DataDefaultSource.java | 1738 +++++++++++++++++ src/org/redkale/source/DataSQLListener.java | 21 + src/org/redkale/source/DataSource.java | 325 +++ .../redkale/source/DistributeGenerator.java | 29 + src/org/redkale/source/DistributeTables.java | 31 + src/org/redkale/source/EntityCache.java | 657 +++++++ src/org/redkale/source/EntityInfo.java | 381 ++++ src/org/redkale/source/FilterBean.java | 16 + src/org/redkale/source/FilterColumn.java | 49 + src/org/redkale/source/FilterExpress.java | 63 + src/org/redkale/source/FilterFunc.java | 25 + src/org/redkale/source/FilterGroup.java | 69 + src/org/redkale/source/FilterJoinColumn.java | 41 + src/org/redkale/source/FilterJoinNode.java | 316 +++ src/org/redkale/source/FilterNode.java | 1256 ++++++++++++ src/org/redkale/source/FilterNodeBean.java | 356 ++++ src/org/redkale/source/FilterValue.java | 66 + src/org/redkale/source/Flipper.java | 120 ++ src/org/redkale/source/JDBCPoolSource.java | 260 +++ src/org/redkale/source/Range.java | 325 +++ src/org/redkale/source/VirtualEntity.java | 24 + src/org/redkale/source/package-info.java | 4 + src/org/redkale/util/AnyValue.java | 446 +++++ src/org/redkale/util/AsmMethodVisitor.java | 152 ++ src/org/redkale/util/Attribute.java | 555 ++++++ src/org/redkale/util/AutoLoad.java | 27 + src/org/redkale/util/ByteArray.java | 194 ++ src/org/redkale/util/Creator.java | 358 ++++ src/org/redkale/util/DLong.java | 119 ++ src/org/redkale/util/LogLevel.java | 25 + src/org/redkale/util/ObjectPool.java | 102 + src/org/redkale/util/Reproduce.java | 141 ++ src/org/redkale/util/ResourceFactory.java | 451 +++++ src/org/redkale/util/ResourceType.java | 26 + src/org/redkale/util/SelectColumn.java | 139 ++ src/org/redkale/util/Sheet.java | 92 + src/org/redkale/util/TypeToken.java | 192 ++ src/org/redkale/util/Utility.java | 578 ++++++ src/org/redkale/util/package-info.java | 4 + src/org/redkale/watch/WatchFactory.java | 100 + src/org/redkale/watch/WatchNode.java | 22 + src/org/redkale/watch/WatchNumber.java | 52 + src/org/redkale/watch/WatchSupplier.java | 48 + src/org/redkale/watch/Watchable.java | 37 + src/org/redkale/watch/package-info.java | 4 + 194 files changed, 31002 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/boot/package-info.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/ConvertFactory.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/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/BsonByteBufferReader.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/BsonStreamReader.java create mode 100644 src/org/redkale/convert/bson/BsonStreamWriter.java create mode 100644 src/org/redkale/convert/bson/BsonWriter.java create mode 100644 src/org/redkale/convert/bson/package-info.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/CharSequenceSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/CharSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/CompletionHandlerSimpledCoder.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/ext/URISimpledCoder.java create mode 100644 src/org/redkale/convert/ext/URLSimpledCoder.java create mode 100644 src/org/redkale/convert/ext/package-info.java create mode 100644 src/org/redkale/convert/json/JsonByteBufferReader.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/JsonStreamReader.java create mode 100644 src/org/redkale/convert/json/JsonStreamWriter.java create mode 100644 src/org/redkale/convert/json/JsonWriter.java create mode 100644 src/org/redkale/convert/json/package-info.java create mode 100644 src/org/redkale/convert/package-info.java create mode 100644 src/org/redkale/net/AsyncConnection.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/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/BasedHttpServlet.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/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/http/package-info.java create mode 100644 src/org/redkale/net/package-info.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/SncpClient.java create mode 100644 src/org/redkale/net/sncp/SncpContext.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/SncpPrepareServlet.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/package-info.java create mode 100644 src/org/redkale/service/CacheSourceService.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/DynAttachment.java create mode 100644 src/org/redkale/service/DynCall.java create mode 100644 src/org/redkale/service/DynRemote.java create mode 100644 src/org/redkale/service/DynSourceAddress.java create mode 100644 src/org/redkale/service/DynTargetAddress.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/service/package-info.java create mode 100644 src/org/redkale/source/CacheSource.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/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/DistributeTables.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/FilterFunc.java create mode 100644 src/org/redkale/source/FilterGroup.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/FilterValue.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/VirtualEntity.java create mode 100644 src/org/redkale/source/package-info.java create mode 100644 src/org/redkale/util/AnyValue.java create mode 100644 src/org/redkale/util/AsmMethodVisitor.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/LogLevel.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/ResourceType.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/util/package-info.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 create mode 100644 src/org/redkale/watch/package-info.java diff --git a/src/org/redkale/boot/Application.java b/src/org/redkale/boot/Application.java new file mode 100644 index 000000000..27142ff15 --- /dev/null +++ b/src/org/redkale/boot/Application.java @@ -0,0 +1,656 @@ +/* + * 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.logging.*; +import javax.xml.parsers.*; +import org.redkale.convert.bson.*; +import org.redkale.convert.json.*; +import org.redkale.net.*; +import org.redkale.net.http.*; +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与其他资源之间的依赖注入。 + * + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public final class Application { + + //当前进程启动的时间, 类型: long + public static final String RESNAME_APP_TIME = "APP_TIME"; + + //当前进程的根目录, 类型:String、File、Path + 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"; + + //当前Service的IP地址+端口 类型: SocketAddress、InetSocketAddress、String + public static final String RESNAME_SERVER_ADDR = "SERVER_ADDR"; + + //当前SNCP Server所属的组 类型: String + public static final String RESNAME_SERVER_GROUP = "SERVER_GROUP"; + + //当前Server的ROOT目录 类型:String、File、Path + public static final String RESNAME_SERVER_ROOT = Server.RESNAME_SERVER_ROOT; + + final Map globalNodes = new HashMap<>(); + + final Map> globalGroups = new HashMap<>(); + + final Map globalGroupProtocols = new HashMap<>(); + + final InetAddress localAddress; + + final List cacheSources = new CopyOnWriteArrayList<>(); + + final List dataSources = new CopyOnWriteArrayList<>(); + + final List servers = new CopyOnWriteArrayList<>(); + + CountDownLatch servicecdl; //会出现两次赋值 + + final ObjectPool transportBufferPool; + + final ExecutorService transportExecutor; + + final AsynchronousChannelGroup transportChannelGroup; + + final ResourceFactory resourceFactory = ResourceFactory.root(); + + //-------------------------------------------------------------------------------------------- + private final boolean singletonrun; + + private final WatchFactory watchFactory = WatchFactory.root(); + + private final 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(false, config); + } + + private Application(final boolean singletonrun, final AnyValue config) { + this.singletonrun = singletonrun; + this.config = config; + + final File root = new File(System.getProperty(RESNAME_APP_HOME)); + this.resourceFactory.register(RESNAME_APP_TIME, long.class, this.startTime); + this.resourceFactory.register(RESNAME_APP_HOME, Path.class, root.toPath()); + this.resourceFactory.register(RESNAME_APP_HOME, File.class, root); + try { + this.resourceFactory.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, config.getIntValue("port")).getAddress(); + this.resourceFactory.register(RESNAME_APP_ADDR, this.localAddress.getHostAddress()); + this.resourceFactory.register(RESNAME_APP_ADDR, InetAddress.class, 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(); + } + this.resourceFactory.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); + //------------------配置 节点 ------------------ + ObjectPool transportPool = null; + ExecutorService transportExec = null; + AsynchronousChannelGroup transportGroup = null; + final AnyValue resources = config.getAnyValue("resources"); + if (resources != null) { + AnyValue transportConf = resources.getAnyValue("transport"); + int groupsize = resources.getAnyValues("group").length; + if (groupsize > 0 && transportConf == null) transportConf = new DefaultAnyValue(); + if (transportConf != null) { + //--------------transportBufferPool----------- + AtomicLong createBufferCounter = watchFactory == null ? new AtomicLong() : watchFactory.createWatchNumber(Transport.class.getSimpleName() + ".Buffer.creatCounter"); + AtomicLong cycleBufferCounter = watchFactory == null ? new AtomicLong() : watchFactory.createWatchNumber(Transport.class.getSimpleName() + ".Buffer.cycleCounter"); + final int bufferCapacity = transportConf.getIntValue("bufferCapacity", 8 * 1024); + final int bufferPoolSize = transportConf.getIntValue("bufferPoolSize", groupsize * Runtime.getRuntime().availableProcessors() * 8); + final int threads = transportConf.getIntValue("threads", groupsize * Runtime.getRuntime().availableProcessors() * 8); + transportPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, bufferPoolSize, + (Object... params) -> ByteBuffer.allocateDirect(bufferCapacity), null, (e) -> { + if (e == null || e.isReadOnly() || e.capacity() != bufferCapacity) return false; + e.clear(); + return true; + }); + //-----------transportChannelGroup-------------- + try { + final AtomicInteger counter = new AtomicInteger(); + transportExec = Executors.newFixedThreadPool(threads, (Runnable r) -> { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("Transport-Thread-" + counter.incrementAndGet()); + return t; + }); + transportGroup = AsynchronousChannelGroup.withCachedThreadPool(transportExec, 1); + } catch (Exception e) { + throw new RuntimeException(e); + } + logger.log(Level.INFO, Transport.class.getSimpleName() + " configure bufferCapacity = " + bufferCapacity + "; bufferPoolSize = " + bufferPoolSize + "; threads = " + threads + ";"); + } + } + this.transportBufferPool = transportPool; + this.transportExecutor = transportExec; + this.transportChannelGroup = transportGroup; + } + + public ResourceFactory getResourceFactory() { + return resourceFactory; + } + + public WatchFactory getWatchFactory() { + return watchFactory; + } + + 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.tiny", "true"); + System.setProperty("convert.json.tiny", "true"); + System.setProperty("convert.bson.pool.size", "128"); + System.setProperty("convert.json.pool.size", "128"); + System.setProperty("convert.bson.writer.buffer.defsize", "4096"); + System.setProperty("convert.json.writer.buffer.defsize", "4096"); + + 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(); + //------------------------------------------------------------------------ + final AnyValue resources = config.getAnyValue("resources"); + if (resources != null) { + resourceFactory.register(RESNAME_APP_GRES, AnyValue.class, resources); + final AnyValue properties = resources.getAnyValue("properties"); + if (properties != null) { + String dfloads = properties.getValue("load"); + if (dfloads != null) { + for (String dfload : dfloads.split(";")) { + if (dfload.trim().isEmpty()) continue; + 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) -> resourceFactory.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 if (name.startsWith("mimetype.property.")) { + MimeType.add(name.substring("mimetype.property.".length()), value); + } else { + resourceFactory.register("property." + name, value); + } + } + } + } + if (this.localAddress != null && this.resourceFactory.find("property.datasource.nodeid", String.class) == null) { + byte[] bs = this.localAddress.getAddress(); + int v = (0xff & bs[bs.length - 2]) % 10 * 100 + (0xff & bs[bs.length - 1]); + this.resourceFactory.register("property.datasource.nodeid", "" + v); + } + this.resourceFactory.register(BsonFactory.root()); + this.resourceFactory.register(JsonFactory.root()); + this.resourceFactory.register(BsonFactory.root().getConvert()); + this.resourceFactory.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", ""); + final String protocol = conf.getValue("protocol", Transport.DEFAULT_PROTOCOL).toUpperCase(); + if (!"TCP".equalsIgnoreCase(protocol) && !"UDP".equalsIgnoreCase(protocol)) { + throw new RuntimeException("Not supported Transport Protocol " + conf.getValue("protocol")); + } + Set addrs = globalGroups.get(group); + if (addrs == null) { + addrs = new LinkedHashSet<>(); + globalGroupProtocols.put(group, protocol); + 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); + } + } + //单向SNCP服务不需要对等group + //if (!sncps.isEmpty() && globalNodes.isEmpty()) throw new RuntimeException("found SNCP Server node but not found node info."); + + runServers(timecd, sncps); //必须确保sncp都启动后再启动其他协议 + runServers(timecd, others); + timecd.await(); + logger.info(this.getClass().getSimpleName() + " started in " + (System.currentTimeMillis() - startTime) + " ms"); + if (!singletonrun) 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)) { + 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)) 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); + if (!singletonrun) server.start(); + timecd.countDown(); + sercdl.countDown(); + } catch (Exception ex) { + logger.log(Level.WARNING, serconf + " runServers error", ex); + Application.this.serversLatch.countDown(); + } + } + }; + thread.start(); + } + sercdl.await(); + } + + public static T singleton(Class serviceClass) throws Exception { + return singleton("", serviceClass); + } + + public static T singleton(String name, Class serviceClass) throws Exception { + final Application application = Application.create(true); + application.init(); + application.start(); + for (NodeServer server : application.servers) { + T service = server.resourceFactory.find(name, serviceClass); + if (service != null) return service; + } + return null; + } + + private static Application create(final boolean singleton) 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(singleton, load(new FileInputStream(appfile))); + } + + public static void main(String[] args) throws Exception { + Utility.midnight(); //先初始化一下Utility + //运行主程序 + final Application application = Application.create(false); + 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 findSncpGroups(Transport sameGroupTransport, Collection diffGroupTransports) { + Set gs = new HashSet<>(); + if (sameGroupTransport != null) gs.add(sameGroupTransport.getName()); + if (diffGroupTransports != null) { + for (Transport t : diffGroupTransports) { + gs.add(t.getName()); + } + } + return gs; + } + + NodeSncpServer findNodeSncpServer(final InetSocketAddress sncpAddr) { + for (NodeServer node : servers) { + if (node.isSNCP() && sncpAddr.equals(node.getSncpAddress())) { + return (NodeSncpServer) node; + } + } + return null; + } + + String findGroupProtocol(String group) { + if (group == null) return null; + return globalGroupProtocols.get(group); + } + + 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 : dataSources) { + try { + source.getClass().getMethod("close").invoke(source); + } catch (Exception e) { + logger.log(Level.FINER, "close DataSource erroneous", e); + } + } + for (CacheSource source : cacheSources) { + try { + source.getClass().getMethod("close").invoke(source); + } catch (Exception e) { + logger.log(Level.FINER, "close CacheSource erroneous", e); + } + } + if (this.transportChannelGroup != null) { + try { + this.transportChannelGroup.shutdownNow(); + } catch (Exception e) { + logger.log(Level.FINER, "close transportChannelGroup 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..259daf58b --- /dev/null +++ b/src/org/redkale/boot/ClassFilter.java @@ -0,0 +1,431 @@ +/* + * 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.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.*; +import org.redkale.util.AnyValue; +import org.redkale.util.AnyValue.DefaultAnyValue; +import org.redkale.util.AutoLoad; + +/** + * class过滤器, 符合条件的class会保留下来存入FilterEntry。 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param 泛型 + */ +@SuppressWarnings("unchecked") +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 Set<FilterEntry<T>> + */ + public final Set> getFilterEntrys() { + return entrys; + } + + /** + * 自动扫描地过滤指定的class + * + * @param property AnyValue + * @param clazzname String + */ + @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 AnyValue + * @param classname String + * @return boolean + */ + 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 AnyValue + * @param clazz Class + * @param autoscan boolean + * @return boolean + */ + @SuppressWarnings("unchecked") + public boolean accept(AnyValue property, Class clazz, boolean autoscan) { + if (this.refused || !Modifier.isPublic(clazz.getModifiers())) 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) { + str = str.trim(); + if (str.endsWith(";")) str = str.substring(0, str.length() - 1); + } + 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 boolean isEmptyGroups() { + return groups == null || groups.isEmpty(); + } + + 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) { + classes = new LinkedHashSet<>(); + 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.")) continue; + classes.add(classname); + if (debug) debugstr.append(classname).append("\r\n"); + for (final ClassFilter filter : filters) { + if (filter != null) filter.filter(null, classname); + } + } + } + } + cache.put(url, classes); + } 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) { + classes = new LinkedHashSet<>(); + 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("com.sun.")) continue; + classes.add(classname); + if (debug) debugstr.append(classname).append("\r\n"); + for (final ClassFilter filter : filters) { + if (filter != null) filter.filter(null, classname); + } + } + cache.put(url, classes); + } 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..0039890d0 --- /dev/null +++ b/src/org/redkale/boot/LogFileHandler.java @@ -0,0 +1,276 @@ +/* + * 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; + +/** + * 自定义的日志输出类 + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class LogFileHandler extends Handler { + + /** + * SNCP的日志输出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..f81af74c9 --- /dev/null +++ b/src/org/redkale/boot/NodeHttpServer.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.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 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@NodeProtocol({"HTTP"}) +public final class NodeHttpServer extends NodeServer { + + private final HttpServer httpServer; + + public NodeHttpServer(Application application, AnyValue serconf) { + super(application, 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.serverConf.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(); + resourceFactory.add(WebSocketNode.class, (ResourceFactory rf, final Object src, final String resourceName, Field field, Object attachment) -> { //主要用于单点的服务 + try { + if (field.getAnnotation(Resource.class) == null) return; + if (!(src instanceof WebSocketServlet)) return; + synchronized (regFactory) { + Service nodeService = (Service) rf.find(resourceName, WebSocketNode.class); + if (nodeService == null) { + nodeService = Sncp.createLocalService(resourceName, getExecutor(), application.getResourceFactory(), WebSocketNodeService.class, (InetSocketAddress) null, (Transport) null, (Collection) null); + regFactory.register(resourceName, WebSocketNode.class, nodeService); + resourceFactory.inject(nodeService, self); + logger.fine("[" + Thread.currentThread().getName() + "] Load Service " + nodeService); + } + 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("path", ""); + final String threadName = "[" + Thread.currentThread().getName() + "] "; + List> list = new ArrayList(filter.getFilterEntrys()); + list.sort((FilterEntry o1, FilterEntry o2) -> { //必须保证WebSocketServlet优先加载, 因为要确保其他的HttpServlet可以注入本地模式的WebSocketNode + boolean ws1 = WebSocketServlet.class.isAssignableFrom(o1.getType()); + boolean ws2 = WebSocketServlet.class.isAssignableFrom(o2.getType()); + if (ws1 == ws2) return o1.getType().getName().compareTo(o2.getType().getName()); + return ws1 ? -1 : 1; + }); + final List> ss = sb == null ? null : new ArrayList<>(); + for (FilterEntry en : list) { + 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(); + resourceFactory.inject(servlet, this); + final String[] mappings = ws.value(); + String pref = ws.repair() ? prefix : ""; + 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, pref, servletConf, mappings); + if (ss != null) { + for (int i = 0; i < mappings.length; i++) { + mappings[i] = pref + mappings[i]; + } + ss.add(new AbstractMap.SimpleEntry<>(clazz.getName(), mappings)); + } + } + if (ss != null) { + Collections.sort(ss, (AbstractMap.SimpleEntry o1, AbstractMap.SimpleEntry o2) -> o1.getKey().compareTo(o2.getKey())); + int max = 0; + for (AbstractMap.SimpleEntry as : ss) { + if (as.getKey().length() > max) max = as.getKey().length(); + } + for (AbstractMap.SimpleEntry as : ss) { + sb.append(threadName).append(" Loaded ").append(as.getKey()); + for (int i = 0; i < max - as.getKey().length(); i++) { + sb.append(' '); + } + sb.append(" mapping to ").append(Arrays.toString(as.getValue())).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..76c03dff9 --- /dev/null +++ b/src/org/redkale/boot/NodeProtocol.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.boot; + +import java.lang.annotation.*; + +/** + * 根据application.xml中的server节点中的protocol值来适配Server的加载逻辑 + * + *

+ * 详情见: http://www.redkale.org + * + * @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..600d1f517 --- /dev/null +++ b/src/org/redkale/boot/NodeServer.java @@ -0,0 +1,493 @@ +/* + * 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.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.net.InetSocketAddress; +import java.nio.file.Path; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.logging.*; +import java.util.stream.Collectors; +import javax.annotation.Resource; +import javax.persistence.Transient; +import static org.redkale.boot.Application.*; +import org.redkale.boot.ClassFilter.FilterEntry; +import org.redkale.net.*; +import org.redkale.net.sncp.*; +import org.redkale.service.*; +import org.redkale.source.*; +import org.redkale.util.AnyValue.DefaultAnyValue; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public abstract class NodeServer { + + //INFO日志的换行符 + public static final String LINE_SEPARATOR = "\r\n"; + + //日志输出对象 + protected final Logger logger; + + //日志是否为FINE级别 + protected final boolean fine; + + //日志是否为FINE级别 + protected final boolean finer; + + //进程主类 + protected final Application application; + + //依赖注入工厂类 + protected final ResourceFactory resourceFactory; + + //当前Server对象 + protected final Server server; + + private String sncpGroup = null; //当前Server的SNCP协议的组 + + private InetSocketAddress sncpAddress; //SNCP服务的地址, 非SNCP为null + + protected Consumer consumer; + + protected AnyValue serverConf; + + protected final Set localServiceWrappers = new LinkedHashSet<>(); + + protected final Set remoteServiceWrappers = new LinkedHashSet<>(); + + public NodeServer(Application application, Server server) { + this.application = application; + this.resourceFactory = application.getResourceFactory().createChild(); + this.server = server; + this.logger = Logger.getLogger(this.getClass().getSimpleName()); + this.fine = logger.isLoggable(Level.FINE); + this.finer = logger.isLoggable(Level.FINER); + } + + 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.serverConf = config == null ? AnyValue.create() : config; + if (isSNCP()) { // SNCP协议 + String host = this.serverConf.getValue("host", "0.0.0.0").replace("0.0.0.0", ""); + this.sncpAddress = new InetSocketAddress(host.isEmpty() ? application.localAddress.getHostAddress() : host, this.serverConf.getIntValue("port")); + this.sncpGroup = application.globalNodes.get(this.sncpAddress); + //单向SNCP服务不需要对等group + //if (this.sncpGroup == null) throw new RuntimeException("Server (" + String.valueOf(config).replaceAll("\\s+", " ") + ") not found info"); + } + + if (this.sncpAddress != null) this.resourceFactory.register(RESNAME_SERVER_ADDR, this.sncpAddress); //单点服务不会有 sncpAddress、sncpGroup + if (this.sncpGroup != null) this.resourceFactory.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); + } + + resourceFactory.register(Server.RESNAME_SERVER_ROOT, String.class, myroot.getCanonicalPath()); + resourceFactory.register(Server.RESNAME_SERVER_ROOT, File.class, myroot.getCanonicalFile()); + resourceFactory.register(Server.RESNAME_SERVER_ROOT, Path.class, myroot.toPath()); + + final String homepath = myroot.getCanonicalPath(); + Server.loadLib(logger, config.getValue("lib", "").replace("${APP_HOME}", homepath) + ";" + homepath + "/lib/*;" + homepath + "/classes"); + if (server != null) server.init(config); + } + + initResource(); //给 DataSource、CacheSource 注册依赖注入时的监听回调事件。 + + 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 appResFactory = application.getResourceFactory(); + resourceFactory.add(DataSource.class, (ResourceFactory rf, final Object src, String resourceName, Field field, final Object attachment) -> { + try { + if (field.getAnnotation(Resource.class) == null) return; + if ((src instanceof Service) && Sncp.isRemote((Service) src)) return; //远程模式不得注入 DataSource + DataSource source = new DataDefaultSource(resourceName); + application.dataSources.add(source); + appResFactory.register(resourceName, DataSource.class, source); + + SncpClient client = null; + Transport sameGroupTransport = null; + List diffGroupTransports = null; + try { + Field ts = src.getClass().getDeclaredField("_sameGroupTransport"); + ts.setAccessible(true); + sameGroupTransport = (Transport) ts.get(src); + + ts = src.getClass().getDeclaredField("_diffGroupTransports"); + ts.setAccessible(true); + diffGroupTransports = Arrays.asList((Transport[]) ts.get(src)); + + ts = src.getClass().getDeclaredField("_client"); + ts.setAccessible(true); + client = (SncpClient) ts.get(src); + } catch (Exception e) { + throw new RuntimeException(src.getClass().getName() + " not found _sameGroupTransport or _diffGroupTransports at " + field, e); + } + final InetSocketAddress sncpAddr = client == null ? null : client.getClientAddress(); + if ((src instanceof DataSource) && sncpAddr != null && resourceFactory.find(resourceName, DataCacheListener.class) == null) { //只有DataSourceService 才能赋值 DataCacheListener + Service cacheListenerService = Sncp.createLocalService(resourceName, getExecutor(), appResFactory, DataCacheListenerService.class, sncpAddr, sameGroupTransport, diffGroupTransports); + appResFactory.register(resourceName, DataCacheListener.class, cacheListenerService); + final NodeSncpServer sncpServer = application.findNodeSncpServer(sncpAddr); + Set gs = application.findSncpGroups(sameGroupTransport, diffGroupTransports); + ServiceWrapper wrapper = new ServiceWrapper(DataCacheListenerService.class, cacheListenerService, resourceName, sncpServer.getSncpGroup(), gs, null); + localServiceWrappers.add(wrapper); + sncpServer.consumerAccept(wrapper); + rf.inject(cacheListenerService, self); + if (fine) logger.fine("[" + Thread.currentThread().getName() + "] Load Service " + wrapper.getService()); + } + field.set(src, source); + rf.inject(source, self); // 给 "datasource.nodeid" 赋值; + } catch (Exception e) { + logger.log(Level.SEVERE, "DataSource inject error", e); + } + }); + resourceFactory.add(CacheSource.class, (ResourceFactory rf, final Object src, final String resourceName, Field field, final Object attachment) -> { + try { + if (field.getAnnotation(Resource.class) == null) return; + if ((src instanceof Service) && Sncp.isRemote((Service) src)) return; //远程模式不得注入 CacheSource + + SncpClient client = null; + Transport sameGroupTransport = null; + List diffGroupTransports = null; + try { + Field ts = src.getClass().getDeclaredField("_sameGroupTransport"); + ts.setAccessible(true); + sameGroupTransport = (Transport) ts.get(src); + + ts = src.getClass().getDeclaredField("_diffGroupTransports"); + ts.setAccessible(true); + Transport[] dts = (Transport[]) ts.get(src); + if (dts != null) diffGroupTransports = Arrays.asList(dts); + + ts = src.getClass().getDeclaredField("_client"); + ts.setAccessible(true); + client = (SncpClient) ts.get(src); + } catch (Exception e) { + throw new RuntimeException(src.getClass().getName() + " not found _sameGroupTransport or _diffGroupTransports at " + field, e); + } + final InetSocketAddress sncpAddr = client == null ? null : client.getClientAddress(); + final CacheSourceService source = Sncp.createLocalService(resourceName, getExecutor(), appResFactory, CacheSourceService.class, sncpAddr, sameGroupTransport, diffGroupTransports); + Type genericType = field.getGenericType(); + ParameterizedType pt = (genericType instanceof ParameterizedType) ? (ParameterizedType) genericType : null; + Type valType = pt == null ? null : pt.getActualTypeArguments()[1]; + source.setStoreType(pt == null ? Serializable.class : (Class) pt.getActualTypeArguments()[0], valType instanceof Class ? (Class) valType : Object.class); + if (field.getAnnotation(Transient.class) != null) source.setNeedStore(false); //必须在setStoreType之后 + application.cacheSources.add(source); + appResFactory.register(resourceName, CacheSource.class, source); + field.set(src, source); + rf.inject(source, self); // + ((Service) source).init(null); + + if ((src instanceof WebSocketNodeService) && sncpAddr != null) { //只有WebSocketNodeService的服务才需要给SNCP服务注入CacheSourceService + NodeSncpServer sncpServer = application.findNodeSncpServer(sncpAddr); + Set gs = application.findSncpGroups(sameGroupTransport, diffGroupTransports); + ServiceWrapper wrapper = new ServiceWrapper(CacheSourceService.class, (Service) source, resourceName, sncpServer.getSncpGroup(), gs, null); + sncpServer.getSncpServer().addService(wrapper); + if (finer) logger.finer("[" + Thread.currentThread().getName() + "] Load Service " + wrapper.getService()); + } + logger.finer("[" + Thread.currentThread().getName() + "] Load Source " + source); + } catch (Exception e) { + logger.log(Level.SEVERE, "DataSource inject error", e); + } + }); + } + + @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() : resourceFactory; + + for (FilterEntry entry : entrys) { //service实现类 + final Class type = entry.getType(); + if (Modifier.isFinal(type.getModifiers())) continue; //修饰final的类跳过 + if (!Modifier.isPublic(type.getModifiers())) continue; + if (entry.getName().contains("$")) throw new RuntimeException(" value cannot contains '$' in " + entry.getProperty()); + if (resourceFactory.find(entry.getName(), type) != null) continue; //Server加载Service时需要判断是否已经加载过了。 + final HashSet groups = entry.getGroups(); //groups.isEmpty()表示没有配置groups属性。 + if (groups.isEmpty() && isSNCP() && this.sncpGroup != null) groups.add(this.sncpGroup); + + final boolean localed = (this.sncpAddress == null && entry.isEmptyGroups() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) //非SNCP的Server,通常是单点服务 + || groups.contains(this.sncpGroup) //本地IP含在内的 + || (this.sncpGroup == null && entry.isEmptyGroups()) //空的SNCP配置 + || type.getAnnotation(LocalService.class) != null;//本地模式 + if (localed && (type.isInterface() || Modifier.isAbstract(type.getModifiers()))) continue; //本地模式不能实例化接口和抽象类的Service类 + + Service service; + if (localed) { //本地模式 + service = Sncp.createLocalService(entry.getName(), getExecutor(), application.getResourceFactory(), type, this.sncpAddress, loadTransport(this.sncpGroup), loadTransports(groups)); + } else { + service = Sncp.createRemoteService(entry.getName(), getExecutor(), type, this.sncpAddress, loadTransport(groups)); + } + if(SncpClient.parseMethod(type).isEmpty()) continue; //class没有可用的方法, 通常为BaseService + final ServiceWrapper wrapper = new ServiceWrapper(type, service, entry.getName(), localed ? this.sncpGroup : null, groups, entry.getProperty()); + for (final Class restype : wrapper.getTypes()) { + if (resourceFactory.find(wrapper.getName(), restype) == null) { + regFactory.register(wrapper.getName(), restype, wrapper.getService()); + } else if (isSNCP() && !entry.isAutoload()) { + throw new RuntimeException(ServiceWrapper.class.getSimpleName() + "(class:" + type.getName() + ", name:" + entry.getName() + ", group:" + groups + ") is repeat."); + } + } + if (wrapper.isRemote()) { + remoteServiceWrappers.add(wrapper); + } else { + localServiceWrappers.add(wrapper); + if (consumer != null) consumer.accept(wrapper); + } + } + application.servicecdl.countDown(); + application.servicecdl.await(); + + final StringBuilder sb = logger.isLoggable(Level.INFO) ? new StringBuilder() : null; + //---------------- inject ---------------- + new ArrayList<>(localServiceWrappers).forEach(y -> { + resourceFactory.inject(y.getService(), NodeServer.this); + }); + remoteServiceWrappers.forEach(y -> { + resourceFactory.inject(y.getService(), NodeServer.this); + if (sb != null) { + sb.append(threadName).append(y.toSimpleString()).append(" loaded and injected").append(LINE_SEPARATOR); + } + }); + //----------------- init ----------------- + List swlist = new ArrayList<>(localServiceWrappers); + Collections.sort(swlist); + localServiceWrappers.clear(); + localServiceWrappers.addAll(swlist); + final List slist = sb == null ? null : new CopyOnWriteArrayList<>(); + localServiceWrappers.parallelStream().forEach(y -> { + long s = System.currentTimeMillis(); + y.getService().init(y.getConf()); + long e = System.currentTimeMillis() - s; + if (slist != null) slist.add(new StringBuilder().append(threadName).append(y.toSimpleString()).append(" loaded and init ").append(e).append(" ms").append(LINE_SEPARATOR).toString()); + }); + Collections.sort(slist); + if (slist != null && sb != null) { + for (String s : slist) { + sb.append(s); + } + } + if (sb != null && sb.length() > 0) logger.log(Level.INFO, sb.toString()); + } + + protected List loadTransports(final HashSet groups) { + if (groups == null) return null; + final List transports = new ArrayList<>(); + for (String group : groups) { + if (this.sncpGroup == null || !this.sncpGroup.equals(group)) { + transports.add(loadTransport(group)); + } + } + return transports; + } + + protected Transport loadTransport(final HashSet groups) { + if (groups == null || groups.isEmpty()) return null; + List tmpgroup = new ArrayList<>(groups); + Collections.sort(tmpgroup); //按字母排列顺序 + final String groupid = tmpgroup.stream().collect(Collectors.joining(";")); + Transport transport = application.resourceFactory.find(groupid, Transport.class); + if (transport != null) return transport; + final List transports = new ArrayList<>(); + for (String group : groups) { + transports.add(loadTransport(group)); + } + Set addrs = new HashSet(); + transports.forEach(t -> addrs.addAll(Arrays.asList(t.getRemoteAddresses()))); + Transport first = transports.get(0); + Transport newTransport = new Transport(groupid, application.findGroupProtocol(first.getName()), application.getWatchFactory(), + application.transportBufferPool, application.transportChannelGroup, this.sncpAddress, addrs); + synchronized (application.resourceFactory) { + transport = application.resourceFactory.find(groupid, Transport.class); + if (transport == null) { + transport = newTransport; + application.resourceFactory.register(groupid, transport); + } + } + return transport; + } + + protected Transport loadTransport(final String group) { + if (group == null) return null; + Transport transport; + synchronized (application.resourceFactory) { + transport = application.resourceFactory.find(group, Transport.class); + if (transport != null) { + if (this.sncpAddress != null && !this.sncpAddress.equals(transport.getClientAddress())) { + throw new RuntimeException(transport + "repeat create on newClientAddress = " + this.sncpAddress + ", oldClientAddress = " + transport.getClientAddress()); + } + return transport; + } + Set addrs = application.findGlobalGroup(group); + if (addrs == null) throw new RuntimeException("Not found = " + group + " on "); + transport = new Transport(group, application.findGroupProtocol(group), application.getWatchFactory(), + application.transportBufferPool, application.transportChannelGroup, this.sncpAddress, addrs); + application.resourceFactory.register(group, 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.serverConf == null) return cf; + AnyValue[] proplist = this.serverConf.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 = sc.trim(); + if (sc.endsWith(";")) sc = sc.substring(0, sc.length() - 1); + } + 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)) { + final AnyValue[] items = av.getAnyValues("property"); + if (av instanceof DefaultAnyValue && items.length > 0) { + DefaultAnyValue dav = DefaultAnyValue.create(); + final AnyValue.Entry[] strings = av.getStringEntrys(); + if (strings != null) { + for (AnyValue.Entry en : strings) { + dav.addValue(en.name, en.getValue()); + } + } + final AnyValue.Entry[] anys = av.getAnyEntrys(); + if (anys != null) { + for (AnyValue.Entry en : anys) { + if (!"property".equals(en.name)) dav.addValue(en.name, en.getValue()); + } + } + DefaultAnyValue ps = DefaultAnyValue.create(); + for (AnyValue item : items) { + ps.addValue(item.getValue("name"), item.getValue("value")); + } + dav.addValue("property", ps); + av = dav; + } + 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 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(y.toSimpleString()).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..538f3394e --- /dev/null +++ b/src/org/redkale/boot/NodeSncpServer.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.boot; + +import java.net.*; +import java.util.*; +import java.util.logging.*; +import org.redkale.net.*; +import org.redkale.net.sncp.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@NodeProtocol({"SNCP"}) +public final class NodeSncpServer extends NodeServer { + + private final SncpServer sncpServer; + + public NodeSncpServer(Application application, AnyValue serconf) { + super(application, 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(); + } + + public void consumerAccept(ServiceWrapper wrapper) { + if (this.consumer != null) this.consumer.accept(wrapper); + } + + @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() + "] "; + List servlets = sncpServer.getSncpServlets(); + Collections.sort(servlets); + for (SncpServlet en : servlets) { + 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/boot/package-info.java b/src/org/redkale/boot/package-info.java new file mode 100644 index 000000000..569d4b0ee --- /dev/null +++ b/src/org/redkale/boot/package-info.java @@ -0,0 +1,4 @@ +/** + * 提供RedKale服务器的启动、初始化和加载功能 + */ +package org.redkale.boot; diff --git a/src/org/redkale/convert/AnyEncoder.java b/src/org/redkale/convert/AnyEncoder.java new file mode 100644 index 000000000..9a2ff74fb --- /dev/null +++ b/src/org/redkale/convert/AnyEncoder.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.Type; + +/** + * 对不明类型的对象进行序列化; BSON序列化时将对象的类名写入Writer,JSON则不写入。 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param 序列化的泛型类型 + */ +public final class AnyEncoder implements Encodeable { + + final ConvertFactory factory; + + AnyEncoder(ConvertFactory factory) { + this.factory = factory; + } + + @Override + @SuppressWarnings("unchecked") + public void convertTo(final Writer out, final T value) { + if (value == null) { + out.wirteClassName(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..3aa1c5e31 --- /dev/null +++ b/src/org/redkale/convert/ArrayDecoder.java @@ -0,0 +1,82 @@ +/* + * 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存储。 + * 支持一定程度的泛型。 + * + *

+ * 详情见: http://www.redkale.org + * + * @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 ConvertFactory 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..1cc5c4b18 --- /dev/null +++ b/src/org/redkale/convert/ArrayEncoder.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 java.lang.reflect.*; + +/** + * 对象数组的反序列化,不包含int[]、long[]这样的primitive class数组. + * 数组长度不能超过 32767。 在BSON中数组长度设定的是short,对于大于32767长度的数组传输会影响性能,所以没有必要采用int存储。 + * 支持一定程度的泛型。 + * + *

+ * 详情见: http://www.redkale.org + * + * @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 ConvertFactory 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..8c9ed24b8 --- /dev/null +++ b/src/org/redkale/convert/CollectionDecoder.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.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存储。 + * 支持一定程度的泛型。 + * + *

+ * 详情见: http://www.redkale.org + * + * @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 ConvertFactory 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..ebf3b211b --- /dev/null +++ b/src/org/redkale/convert/CollectionEncoder.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.convert; + +import java.lang.reflect.*; +import java.util.Collection; + +/** + * 对象集合的序列化. + * 集合大小不能超过 32767。 在BSON中集合大小设定的是short,对于大于32767长度的集合传输会影响性能,所以没有采用int存储。 + * 支持一定程度的泛型。 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param 序列化的集合元素类型 + */ +@SuppressWarnings("unchecked") +public final class CollectionEncoder implements Encodeable> { + + private final Type type; + + private final Encodeable encoder; + + public CollectionEncoder(final ConvertFactory 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..19b7ab5bf --- /dev/null +++ b/src/org/redkale/convert/Convert.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.convert; + +/** + * 序列化操作类 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类 + * @param Writer输出的子类 + */ +public abstract class Convert { + + protected final ConvertFactory factory; + + protected Convert(ConvertFactory factory) { + this.factory = factory; + } + + public ConvertFactory 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..53d91f62c --- /dev/null +++ b/src/org/redkale/convert/ConvertColumn.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.convert; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +/** + * 依附在setter、getter方法、字段进行简单的配置 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({METHOD, FIELD}) +@Retention(RUNTIME) +@Repeatable(ConvertColumns.class) +public @interface ConvertColumn { + + /** + * 给字段取个别名 + * + * @return 字段别名 + */ + String name() default ""; + + /** + * 解析/序列化时是否屏蔽该字段 + * + * @return 是否屏蔽该字段 + */ + boolean ignore() default false; + + /** + * 解析/序列化定制化的TYPE + * + * @return JSON or BSON or ALL + */ + 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..a4e341481 --- /dev/null +++ b/src/org/redkale/convert/ConvertColumnEntry.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; + +/** + * ConvertColumn 对应的实体类 + * + *

详情见: http://www.redkale.org + * @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..4173069a4 --- /dev/null +++ b/src/org/redkale/convert/ConvertColumns.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.*; +import static java.lang.annotation.RetentionPolicy.*; + +/** + * ConvertColumn 的多用类 + * + *

详情见: http://www.redkale.org + * @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..9adb71cd2 --- /dev/null +++ b/src/org/redkale/convert/ConvertEntity.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.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 用于类名的别名, 类似javax.persistence.Table + * 该值必须是全局唯一 + * 使用场景: 当BSON序列化为了不指定class可以使用@ConvertEntity来取个别名。关联方法: Reader.readClassName() 和 Writer.wirteClassName(String value) 。 + * + *

详情见: http://www.redkale.org + * @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..7898ad319 --- /dev/null +++ b/src/org/redkale/convert/ConvertException.java @@ -0,0 +1,29 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.convert; + +/** + * + *

详情见: http://www.redkale.org + * @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/ConvertFactory.java b/src/org/redkale/convert/ConvertFactory.java new file mode 100644 index 000000000..a9a568db2 --- /dev/null +++ b/src/org/redkale/convert/ConvertFactory.java @@ -0,0 +1,486 @@ +/* + * 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 java.nio.channels.*; +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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类 + * @param Writer输出的子类 + */ +@SuppressWarnings("unchecked") +public abstract class ConvertFactory { + + private final ConvertFactory parent; + + protected Convert convert; + + protected boolean tiny; + + private final Encodeable anyEncoder = new AnyEncoder(this); + + //----------------------------------------------------------------------------------- + private final ConcurrentHashMap creators = new ConcurrentHashMap(); + + private final ConcurrentHashMap entitys = new ConcurrentHashMap(); + + private final ConcurrentHashMap> decoders = new ConcurrentHashMap(); + + private final ConcurrentHashMap> encoders = new ConcurrentHashMap(); + + private final ConcurrentHashMap columnEntrys = new ConcurrentHashMap(); + + private final Set skipIgnores = new HashSet(); + + private boolean skipAllIgnore = false; + + protected ConvertFactory(ConvertFactory 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(CharSequence.class, CharSequenceSimpledCoder.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(CompletionHandler.class, CompletionHandlerSimpledCoder.instance); + this.register(URL.class, URLSimpledCoder.instance); + this.register(URI.class, URISimpledCoder.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 ConvertFactory parent() { + return this.parent; + } + + public abstract ConvertType getConvertType(); + + public abstract boolean isReversible(); + + public abstract ConvertFactory createChild(); + + public abstract ConvertFactory createChild(boolean tiny); + + public Convert getConvert() { + return convert; + } + + public ConvertFactory tiny(boolean tiny) { + this.tiny = tiny; + return this; + } + + public ConvertColumnEntry findRef(AccessibleObject element) { + if (element == null) return null; + ConvertColumnEntry en = this.columnEntrys.get(element); + if (en != null) return en; + final ConvertType ct = this.getConvertType(); + ConvertColumn[] ccs = element.getAnnotationsByType(ConvertColumn.class); + if (ccs.length == 0 && element instanceof Method) { + final Method method = (Method) element; + String fieldName = readGetSetFieldName(method); + if (fieldName != null) { + try { + ccs = method.getDeclaringClass().getDeclaredField(fieldName).getAnnotationsByType(ConvertColumn.class); + } catch (Exception e) { //说明没有该字段,忽略异常 + } + } + } + for (ConvertColumn ref : ccs) { + 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) element).getDeclaringClass())) entry.setIgnore(false); + return entry; + } + } + return null; + } + + 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; + } + + 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 是否忽略Ignore注解 + */ + 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 reloadCoder(final Type type) { + this.register(type, this.createDecoder(type)); + this.register(type, this.createEncoder(type)); + } + + public final void reloadCoder(final Type type, final Class clazz) { + this.register(type, this.createDecoder(type, clazz)); + this.register(type, this.createEncoder(type, clazz)); + } + + 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); + if (result != null) 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 TypeVariable) { // e.g. + final TypeVariable tv = (TypeVariable) type; + Class cz = tv.getBounds().length == 0 ? Object.class : null; + for (Type f : tv.getBounds()) { + if (f instanceof Class) { + cz = (Class) f; + break; + } + } + clazz = cz; + if (cz == null) throw new ConvertException("not support the type (" + type + ")"); + } else if (type instanceof WildcardType) { // e.g. + final WildcardType wt = (WildcardType) type; + Class cz = null; + for (Type f : wt.getUpperBounds()) { + if (f instanceof Class) { + cz = (Class) f; + break; + } + } + clazz = cz; + if (cz == null) throw new ConvertException("not support the type (" + type + ")"); + } 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.")) { + Decodeable simpleCoder = null; + for (final Method method : clazz.getDeclaredMethods()) { + if (!Modifier.isStatic(method.getModifiers())) continue; + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1) continue; + if (paramTypes[0] != ConvertFactory.class && paramTypes[0] != this.getClass()) continue; + if (!Decodeable.class.isAssignableFrom(method.getReturnType())) continue; + try { + method.setAccessible(true); + simpleCoder = (Decodeable) method.invoke(null, this); + break; + } catch (Exception e) { + } + } + if (simpleCoder == null) { + od = new ObjectDecoder(type); + decoder = od; + } else { + decoder = simpleCoder; + } + } + 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.")) { + Encodeable simpleCoder = null; + for (final Method method : clazz.getDeclaredMethods()) { + if (!Modifier.isStatic(method.getModifiers())) continue; + Class[] paramTypes = method.getParameterTypes(); + if (paramTypes.length != 1) continue; + if (paramTypes[0] != ConvertFactory.class && paramTypes[0] != this.getClass()) continue; + if (!Encodeable.class.isAssignableFrom(method.getReturnType())) continue; + try { + method.setAccessible(true); + simpleCoder = (Encodeable) method.invoke(null, this); + break; + } catch (Exception e) { + } + } + if (simpleCoder == null) { + oe = new ObjectEncoder(type); + encoder = oe; + } else { + encoder = simpleCoder; + } + } + 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/ConvertType.java b/src/org/redkale/convert/ConvertType.java new file mode 100644 index 000000000..070ac5a86 --- /dev/null +++ b/src/org/redkale/convert/ConvertType.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.convert; + +/** + * + *

详情见: http://www.redkale.org + * @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..3278c5618 --- /dev/null +++ b/src/org/redkale/convert/DeMember.java @@ -0,0 +1,85 @@ +/* + * 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 org.redkale.util.Attribute; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类 + * @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 static DeMember create(final ConvertFactory factory, final Class clazz, final String fieldname) { + try { + Field field = clazz.getDeclaredField(fieldname); + return new DeMember<>(Attribute.create(field), factory.loadDecoder(field.getGenericType())); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public final boolean match(String name) { + return attribute.field().equals(name); + } + + public final void read(R in, T obj) { + this.attribute.set(obj, decoder.convertFrom(in)); + } + + public final F read(R in) { + return 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..c4f1a6767 --- /dev/null +++ b/src/org/redkale/convert/Decodeable.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.convert; + +import java.lang.reflect.Type; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类 + * @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..e31dcf2bb --- /dev/null +++ b/src/org/redkale/convert/EnMember.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 java.lang.reflect.*; +import org.redkale.util.Attribute; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Writer输出的子类 + * @param 字段依附的类 + * @param 字段的数据类型 + */ +@SuppressWarnings("unchecked") +public final class EnMember implements Comparable> { + + final Attribute attribute; + + final Encodeable encoder; + + final boolean istring; + + //final boolean isnumber; + 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 static EnMember create(final ConvertFactory factory, final Class clazz, final String fieldname) { + try { + Field field = clazz.getDeclaredField(fieldname); + return new EnMember<>(Attribute.create(field), factory.loadEncoder(field.getGenericType())); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public final boolean match(String name) { + return attribute.field().equals(name); + } + + @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..9a2a96191 --- /dev/null +++ b/src/org/redkale/convert/Encodeable.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.convert; + +import java.lang.reflect.Type; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Writer输出的子类 + * @param 序列化的数据类型 + */ +public interface Encodeable { + + public void convertTo(final W out, T value); + + /** + * 泛型映射接口 + * + * @return 返回序列化对象类的数据类型 + */ + public Type getType(); + +} diff --git a/src/org/redkale/convert/MapDecoder.java b/src/org/redkale/convert/MapDecoder.java new file mode 100644 index 000000000..22fb3a3c8 --- /dev/null +++ b/src/org/redkale/convert/MapDecoder.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 org.redkale.util.Creator; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Map key的数据类型 + * @param Map value的数据类型 + */ +@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 ConvertFactory 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.readBlank(); + V value = valueDecoder.convertFrom(in); + result.put(key, value); + } + } else { + for (int i = 0; i < len; i++) { + K key = keyDecoder.convertFrom(in); + in.readBlank(); + 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..1b1f2a264 --- /dev/null +++ b/src/org/redkale/convert/MapEncoder.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.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Map key的数据类型 + * @param Map value的数据类型 + */ +@SuppressWarnings("unchecked") +public final class MapEncoder implements Encodeable> { + + private final Type type; + + private final Encodeable keyencoder; + + private final Encodeable valencoder; + + public MapEncoder(final ConvertFactory 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..8bd7d5c12 --- /dev/null +++ b/src/org/redkale/convert/ObjectDecoder.java @@ -0,0 +1,234 @@ +/* + * 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.*; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类 + * @param 反解析的数据类型 + */ +@SuppressWarnings("unchecked") +public final class ObjectDecoder implements Decodeable { + + protected final Type type; + + protected final Class typeClass; + + protected Creator creator; + + protected DeMember[] creatorConstructorMembers; + + protected DeMember[] members; + + protected ConvertFactory 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 ConvertFactory 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; + } + this.creator = factory.loadCreator(clazz); + + final Set list = new HashSet(); + final String[] cps = ObjectEncoder.findConstructorProperties(this.creator); + 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.createClassType(field.getGenericType(), this.type); + 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 && (cps == null || !ObjectEncoder.contains(cps, ConvertFactory.readGetSetFieldName(method)))) { + 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.createClassType(method.getGenericParameterTypes()[0], this.type); + list.add(new DeMember(ObjectEncoder.createAttribute(factory, clazz, null, null, method), factory.loadDecoder(t))); + } + if (cps != null) { //可能存在某些构造函数中的字段名不存在setter方法 + for (final String constructorField : cps) { + boolean flag = false; + for (DeMember m : list) { + if (m.attribute.field().equals(constructorField)) { + flag = true; + break; + } + } + if (flag) continue; + //不存在setter方法 + try { + Field f = clazz.getDeclaredField(constructorField); + Type t = ObjectEncoder.createClassType(f.getGenericType(), this.type); + list.add(new DeMember(ObjectEncoder.createAttribute(factory, clazz, f, null, null), factory.loadDecoder(t))); + } catch (NoSuchFieldException nsfe) { //不存在field, 可能存在getter方法 + char[] fs = constructorField.toCharArray(); + fs[0] = Character.toUpperCase(fs[0]); + String mn = new String(fs); + Method getter; + try { + getter = clazz.getMethod("get" + mn); + } catch (NoSuchMethodException ex) { + getter = clazz.getMethod("is" + mn); + } + Type t = ObjectEncoder.createClassType(getter.getGenericParameterTypes()[0], this.type); + list.add(new DeMember(ObjectEncoder.createAttribute(factory, clazz, null, getter, null), factory.loadDecoder(t))); + } + } + } + this.members = list.toArray(new DeMember[list.size()]); + Arrays.sort(this.members); + + if (cps != null) { + final String[] fields = cps; + final DeMember[] ms = new DeMember[fields.length]; + for (int i = 0; i < fields.length; i++) { + for (DeMember m : this.members) { + if (m.attribute.field().equals(fields[i])) { + ms[i] = m; + break; + } + } + } + this.creatorConstructorMembers = ms; + } + } 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.readObjectB(typeClass); + if (clazz == null) return null; + if (!clazz.isEmpty()) return (T) factory.loadDecoder(factory.getEntity(clazz)).convertFrom(in); + if (!this.inited) { + synchronized (lock) { + try { + lock.wait(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + if (this.creatorConstructorMembers == null) { //空构造函数 + final T result = this.creator.create(); + while (in.hasNext()) { + DeMember member = in.readFieldName(members); + in.readBlank(); + if (member == null) { + in.skipValue(); //跳过不存在的属性的值 + } else { + member.read(in, result); + } + } + in.readObjectE(typeClass); + return result; + } else { //带参数的构造函数 + final DeMember[] fields = this.creatorConstructorMembers; + final Object[] constructorParams = new Object[fields.length]; + final Object[][] otherParams = new Object[this.members.length][2]; + int oc = 0; + while (in.hasNext()) { + DeMember member = in.readFieldName(members); + in.readBlank(); + if (member == null) { + in.skipValue(); //跳过不存在的属性的值 + } else { + Object val = member.read(in); + boolean flag = true; + for (int i = 0; i < fields.length; i++) { + if (member == fields[i]) { + constructorParams[i] = val; + flag = false; + break; + } + } + if (flag) otherParams[oc++] = new Object[]{member.attribute, val}; + } + } + in.readObjectE(typeClass); + final T result = this.creator.create(constructorParams); + for (int i = 0; i < oc; i++) { + ((Attribute) otherParams[i][0]).set(result, otherParams[i][1]); + } + 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..0e3a94281 --- /dev/null +++ b/src/org/redkale/convert/ObjectEncoder.java @@ -0,0 +1,278 @@ +/* + * 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.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Writer输出的子类 + * @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 ConvertFactory 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]; + } + + public void init(final ConvertFactory 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 boolean reversible = factory.isReversible(); + Creator creator = null; + try { + creator = factory.loadCreator(this.typeClass); + } catch (RuntimeException e) { + if (reversible) throw e; + } + final String[] cps = creator == null ? null : ObjectEncoder.findConstructorProperties(creator); + 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 = createClassType(field.getGenericType(), this.type); + list.add(new EnMember(createAttribute(factory, clazz, field, null, null), factory.loadEncoder(t))); + } + 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 && (cps == null || !contains(cps, ConvertFactory.readGetSetFieldName(method)))) { + 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 = createClassType(method.getGenericReturnType(), this.type); + 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.writeObjectNull(null); + 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(value); + for (EnMember member : members) { + out.writeObjectField(member, value); + } + out.writeObjectE(value); + } + + @Override + public final Type getType() { + return this.type; + } + + @Override + public String toString() { + return "ObjectEncoder{" + "type=" + type + ", members=" + Arrays.toString(members) + '}'; + } + + static Type createClassType(final Type type, final Type declaringType0) { + if (TypeToken.isClassType(type)) return type; + if (type instanceof ParameterizedType) { // e.g. Map + final ParameterizedType pt = (ParameterizedType) type; + final Type[] paramTypes = pt.getActualTypeArguments(); + for (int i = 0; i < paramTypes.length; i++) { + paramTypes[i] = createClassType(paramTypes[i], declaringType0); + } + return TypeToken.createParameterizedType(pt.getOwnerType(), pt.getRawType(), paramTypes); + } + Type declaringType = declaringType0; + if (declaringType instanceof Class) { + do { + declaringType = ((Class) declaringType).getGenericSuperclass(); + if (declaringType == Object.class) return Object.class; + } while (declaringType instanceof Class); + } + //存在通配符则declaringType 必须是 ParameterizedType + if (!(declaringType instanceof ParameterizedType)) return Object.class; + final ParameterizedType declaringPType = (ParameterizedType) declaringType; + final Type[] virTypes = ((Class) declaringPType.getRawType()).getTypeParameters(); + final Type[] desTypes = declaringPType.getActualTypeArguments(); + if (type instanceof WildcardType) { // e.g. + final WildcardType wt = (WildcardType) type; + for (Type f : wt.getUpperBounds()) { + for (int i = 0; i < virTypes.length; i++) { + if (virTypes[i].equals(f)) return desTypes.length <= i ? Object.class : desTypes[i]; + } + } + } else if (type instanceof TypeVariable) { // e.g. + for (int i = 0; i < virTypes.length; i++) { + if (virTypes[i].equals(type)) return desTypes.length <= i ? Object.class : desTypes[i]; + } + } + return type; + } +// +// static Type makeGenericType(final Type type, final Type[] virGenericTypes, final Type[] realGenericTypes) { +// if (type instanceof Class) { //e.g. String +// return type; +// } else if (type instanceof ParameterizedType) { //e.g. Map +// 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) { // e.g. +// 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) { // e.g. +// for (int i = 0; i < virGenericTypes.length; i++) { +// if (virGenericTypes[i] == type) return i >= realGenericTypes.length ? Object.class : realGenericTypes[i]; +// } +// } +// return type; +// } + + static boolean contains(String[] values, String value) { + for (String str : values) { + if (str.equals(value)) return true; + } + return false; + } + + static String[] findConstructorProperties(Creator creator) { + try { + Creator.ConstructorParameters cps = creator.getClass().getMethod("create", Object[].class).getAnnotation(Creator.ConstructorParameters.class); + return cps == null ? null : cps.value(); + } catch (Exception e) { + return null; + } + } + + static Attribute createAttribute(final ConvertFactory factory, Class clazz, final Field field, final Method getter, final Method setter) { + String fieldalias; + 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 = ConvertFactory.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 = ConvertFactory.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); + } + +} diff --git a/src/org/redkale/convert/Reader.java b/src/org/redkale/convert/Reader.java new file mode 100644 index 000000000..5afc6ebe3 --- /dev/null +++ b/src/org/redkale/convert/Reader.java @@ -0,0 +1,170 @@ +/* + * 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; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public abstract class Reader { + + //当前对象字段名的游标 + protected int fieldIndex; + + public static final short SIGN_NULL = -1; + + public static final short SIGN_NOLENGTH = -2; + + /** + * 是否还存在下个元素或字段 + * + * @return 是否还存在下个元素或字段 + */ + public abstract boolean hasNext(); + + /** + * 跳过值(不包含值前面的字段) + */ + public abstract void skipValue(); + + /** + * /跳过字段与值之间的多余内容, json就是跳过:符, map跳过: + */ + public abstract void readBlank(); + + /** + * 读取对象的类名, 返回 null 表示对象为null, 返回空字符串表示当前class与返回的class一致,返回非空字符串表示class是当前class的子类。 + * + * @param clazz 类名 + * @return 返回字段数 + */ + public String readObjectB(final Class clazz) { + this.fieldIndex = 0; + return null; + } + + /** + * 读取对象的尾端 + * + * @param clazz 类名 + */ + public abstract void readObjectE(final Class clazz); + + /** + * 读取数组的开头并返回数组的长度 + * + * @return 返回数组的长度 + */ + public abstract int readArrayB(); + + /** + * 读取数组的尾端 + * + */ + public abstract void readArrayE(); + + /** + * 读取map的开头并返回map的size + * + * @return 返回map的size + */ + public abstract int readMapB(); + + /** + * 读取数组的尾端 + * + */ + public abstract void readMapE(); + + /** + * 根据字段读取字段对应的DeMember + * + * @param members DeMember的全量集合 + * @return 匹配的DeMember + */ + public abstract DeMember readFieldName(final DeMember[] members); + + /** + * 读取一个boolean值 + * + * @return boolean值 + */ + public abstract boolean readBoolean(); + + /** + * 读取一个byte值 + * + * @return byte值 + */ + public abstract byte readByte(); + + /** + * 读取一个char值 + * + * @return char值 + */ + public abstract char readChar(); + + /** + * 读取一个short值 + * + * @return short值 + */ + public abstract short readShort(); + + /** + * 读取一个int值 + * + * @return int值 + */ + public abstract int readInt(); + + /** + * 读取一个long值 + * + * @return long值 + */ + public abstract long readLong(); + + /** + * 读取一个float值 + * + * @return float值 + */ + public abstract float readFloat(); + + /** + * 读取一个double值 + * + * @return double值 + */ + public abstract double readDouble(); + + /** + * 读取无转义字符长度不超过255的字符串, 例如枚举值、字段名、类名字符串等 + * + * @return String值 + */ + public abstract String readSmallString(); + + /** + * 读取反解析对象的类名 + * + * @return 类名 + */ + public abstract String readClassName(); + + /** + * 读取一个String值 + * + * @return String值 + */ + public abstract String readString(); + +} diff --git a/src/org/redkale/convert/SimpledCoder.java b/src/org/redkale/convert/SimpledCoder.java new file mode 100644 index 000000000..35ae99813 --- /dev/null +++ b/src/org/redkale/convert/SimpledCoder.java @@ -0,0 +1,45 @@ +/* + * 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; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类 + * @param Writer输出的子类 + * @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..190201a8e --- /dev/null +++ b/src/org/redkale/convert/Writer.java @@ -0,0 +1,205 @@ +/* + * 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; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public abstract class Writer { + + //当前对象输出字段名之前是否需要分隔符, JSON字段间的分隔符为,逗号 + protected boolean comma; + + /** + * 当tiny=true时, 字符串为空、boolean为false的字段值都会被跳过, 不会输出。 + * + * @return 是否简化 + */ + public abstract boolean tiny(); + + /** + * 输出null值 + */ + public abstract void writeNull(); + + /** + * 写入类名 + * + * @param clazz 类名 + */ + public abstract void wirteClassName(String clazz); + + /** + * 输出一个对象前的操作 + * 注: 覆盖此方法必须要先调用父方法 super.writeObjectB(obj); + * + * @param obj 写入的对象 + */ + public void writeObjectB(Object obj) { + this.comma = false; + } + + /** + * 输出一个为null的对象 + * + * @param clazz 对象的类名 + */ + public final void writeObjectNull(final Class clazz) { + wirteClassName(null); + writeNull(); + } + + /** + * 输出一个对象的某个字段 + * + * @param member 字段 + * + * @param obj 写入的对象 + */ + @SuppressWarnings("unchecked") + public final void writeObjectField(final EnMember member, Object obj) { + Object value = member.attribute.get(obj); + if (value == null) return; + if (tiny()) { + if (member.istring) { + if (((CharSequence) value).length() == 0) return; + } else if (member.isbool) { + if (!((Boolean) value)) return; + } + } + this.writeFieldName(member.attribute); + member.encoder.convertTo(this, value); + this.comma = true; + } + + /** + * 输出一个对象后的操作 + * + * @param obj 写入的对象 + */ + public abstract void writeObjectE(Object obj); + + /** + * 输出一个数组前的操作 + * + * @param size 数组长度 + */ + public abstract void writeArrayB(int size); + + /** + * 输出数组元素间的间隔符 + * + */ + public abstract void writeArrayMark(); + + /** + * 输出一个数组后的操作 + * + */ + public abstract void writeArrayE(); + + /** + * 输出一个Map前的操作 + * + * @param size map大小 + */ + public abstract void writeMapB(int size); + + /** + * 输出一个Map中key与value间的间隔符 + * + */ + public abstract void writeMapMark(); + + /** + * 输出一个Map后的操作 + * + */ + public abstract void writeMapE(); + + /** + * 输出一个字段名 + * + * @param attribute 字段的Attribute对象 + */ + public abstract void writeFieldName(Attribute attribute); + + /** + * 写入一个boolean值 + * + * @param value boolean值 + */ + public abstract void writeBoolean(boolean value); + + /** + * 写入一个byte值 + * + * @param value byte值 + */ + public abstract void writeByte(byte value); + + /** + * 写入一个char值 + * + * @param value char值 + */ + public abstract void writeChar(char value); + + /** + * 写入一个short值 + * + * @param value short值 + */ + public abstract void writeShort(short value); + + /** + * 写入一个int值 + * + * @param value int值 + */ + public abstract void writeInt(int value); + + /** + * 写入一个long值 + * + * @param value long值 + */ + public abstract void writeLong(long value); + + /** + * 写入一个float值 + * + * @param value float值 + */ + public abstract void writeFloat(float value); + + /** + * 写入一个double值 + * + * @param value double值 + */ + public abstract void writeDouble(double value); + + /** + * 写入无转义字符长度不超过255的字符串, 例如枚举值、字段名、类名字符串等 * + * + * @param value 非空且不含需要转义的字符的String值 + */ + public abstract void writeSmallString(String value); + + /** + * 写入一个String值 + * + * @param value String值 + */ + public abstract void writeString(String value); +} diff --git a/src/org/redkale/convert/bson/BsonByteBufferReader.java b/src/org/redkale/convert/bson/BsonByteBufferReader.java new file mode 100644 index 000000000..f9ecc131a --- /dev/null +++ b/src/org/redkale/convert/bson/BsonByteBufferReader.java @@ -0,0 +1,172 @@ +/* + * 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 org.redkale.convert.*; +import static org.redkale.convert.Reader.SIGN_NULL; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +public class BsonByteBufferReader extends BsonReader { + + private ByteBuffer[] buffers; + + private int currentIndex = 0; + + private ByteBuffer currentBuffer; + + protected BsonByteBufferReader(ByteBuffer... buffers) { + this.buffers = buffers; + if (buffers != null && buffers.length > 0) this.currentBuffer = buffers[currentIndex]; + } + + @Override + protected boolean recycle() { + super.recycle(); // this.position 初始化值为-1 + this.currentIndex = 0; + this.currentBuffer = null; + this.buffers = null; + return false; + } + + @Override + protected byte currentByte() { + return currentBuffer.get(currentBuffer.position()); + } + + /** + * 判断下一个非空白字节是否为[ + * + * @return 数组长度或 SIGN_NULL + */ + @Override + public final int readArrayB() { + short bt = readShort(); + if (bt == Reader.SIGN_NULL) return bt; + short lt = readShort(); + return (bt & 0xffff) << 16 | (lt & 0xffff); + } +//------------------------------------------------------------ + + @Override + public final boolean readBoolean() { + return readByte() == 1; + } + + @Override + public byte readByte() { + if (this.currentBuffer.hasRemaining()) { + this.position++; + return this.currentBuffer.get(); + } + for (;;) { + this.currentBuffer = this.buffers[++this.currentIndex]; + if (this.currentBuffer.hasRemaining()) { + this.position++; + return this.currentBuffer.get(); + } + } + } + + @Override + public final char readChar() { + if (this.currentBuffer != null) { + int remain = this.currentBuffer.remaining(); + if (remain >= 2) { + this.position += 2; + return this.currentBuffer.getChar(); + } + } + return (char) ((0xff00 & (readByte() << 8)) | (0xff & readByte())); + } + + @Override + public final short readShort() { + if (this.currentBuffer != null) { + int remain = this.currentBuffer.remaining(); + if (remain >= 2) { + this.position += 2; + return this.currentBuffer.getShort(); + } + } + return (short) ((0xff00 & (readByte() << 8)) | (0xff & readByte())); + } + + @Override + public final int readInt() { + if (this.currentBuffer != null) { + int remain = this.currentBuffer.remaining(); + if (remain >= 4) { + this.position += 4; + return this.currentBuffer.getInt(); + } + } + return ((readByte() & 0xff) << 24) | ((readByte() & 0xff) << 16) | ((readByte() & 0xff) << 8) | (readByte() & 0xff); + } + + @Override + public final long readLong() { + if (this.currentBuffer != null) { + int remain = this.currentBuffer.remaining(); + if (remain >= 8) { + this.position += 8; + return this.currentBuffer.getLong(); + } + } + return ((((long) readByte() & 0xff) << 56) + | (((long) readByte() & 0xff) << 48) + | (((long) readByte() & 0xff) << 40) + | (((long) readByte() & 0xff) << 32) + | (((long) readByte() & 0xff) << 24) + | (((long) readByte() & 0xff) << 16) + | (((long) readByte() & 0xff) << 8) + | (((long) readByte() & 0xff))); + } + + protected byte[] read(final int len) { + byte[] bs = new byte[len]; + read(bs, 0); + return bs; + } + + private void read(final byte[] bs, final int pos) { + int remain = this.currentBuffer.remaining(); + if (remain < 1) { + this.currentBuffer = this.buffers[++this.currentIndex]; + read(bs, pos); + return; + } + int len = bs.length - pos; + if (remain >= len) { + this.position += len; + this.currentBuffer.get(bs, pos, len); + return; + } + this.currentBuffer.get(bs, pos, remain); + this.position += remain; + this.currentBuffer = this.buffers[++this.currentIndex]; + read(bs, pos + remain); + } + + @Override + public final String readSmallString() { + int len = 0xff & readByte(); + if (len == 0) return ""; + return new String(read(len)); + } + + @Override + public final String readString() { + int len = readInt(); + if (len == SIGN_NULL) return null; + if (len == 0) return ""; + return new String(Utility.decodeUTF8(read(len))); + } +} diff --git a/src/org/redkale/convert/bson/BsonByteBufferWriter.java b/src/org/redkale/convert/bson/BsonByteBufferWriter.java new file mode 100644 index 000000000..5bf18f4d2 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonByteBufferWriter.java @@ -0,0 +1,142 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class BsonByteBufferWriter extends BsonWriter { + + private final Supplier supplier; + + private ByteBuffer[] buffers; + + private int index; + + public BsonByteBufferWriter(Supplier supplier) { + this(false, supplier); + } + + protected BsonByteBufferWriter(boolean tiny, Supplier supplier) { + super((byte[]) null); + this.tiny = tiny; + 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 tiny(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..2d77dc0f5 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonConvert.java @@ -0,0 +1,214 @@ +/* + * 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.*; +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不一致才会有该值, 该值可以使用@ConvertEntity给其取个别名)
+ *      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)
+ *
+ * 
+ *

+ * 详情见: http://www.redkale.org + * + * @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(ConvertFactory factory, boolean tiny) { + super(factory); + this.tiny = tiny; + } + + @Override + public BsonFactory getFactory() { + return (BsonFactory) factory; + } + + public static BsonConvert root() { + return BsonFactory.root().getConvert(); + } + + //------------------------------ reader ----------------------------------------------------------- + public BsonReader pollBsonReader(final ByteBuffer... buffers) { + return new BsonByteBufferReader(buffers); + } + + public BsonReader pollBsonReader(final InputStream in) { + return new BsonStreamReader(in); + } + + public BsonReader pollBsonReader() { + return readerPool.get(); + } + + public void offerBsonReader(final BsonReader in) { + if (in != null) readerPool.offer(in); + } + + //------------------------------ writer ----------------------------------------------------------- + public BsonByteBufferWriter pollBsonWriter(final Supplier supplier) { + return new BsonByteBufferWriter(tiny, supplier); + } + + public BsonWriter pollBsonWriter(final OutputStream out) { + return new BsonStreamWriter(tiny, out); + } + + public BsonWriter pollBsonWriter() { + return writerPool.get().tiny(tiny); + } + + public void offerBsonWriter(final BsonWriter out) { + if (out != null) writerPool.offer(out); + } + + //------------------------------ convertFrom ----------------------------------------------------------- + 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, final int start, final 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 Type type, final InputStream in) { + if (type == null || in == null) return null; + return (T) factory.loadDecoder(type).convertFrom(new BsonStreamReader(in)); + } + + public T convertFrom(final Type type, final ByteBuffer... buffers) { + if (type == null || buffers.length < 1) return null; + return (T) factory.loadDecoder(type).convertFrom(new BsonByteBufferReader(buffers)); + } + + public T convertFrom(final Type type, final BsonReader reader) { + if (type == null) return null; + @SuppressWarnings("unchecked") + T rs = (T) factory.loadDecoder(type).convertFrom(reader); + return rs; + } + + //------------------------------ convertTo ----------------------------------------------------------- + public byte[] convertTo(final Object value) { + if (value == null) { + final BsonWriter out = writerPool.get().tiny(tiny); + out.writeNull(); + byte[] result = out.toArray(); + writerPool.offer(out); + return result; + } + return convertTo(value.getClass(), value); + } + + public byte[] convertTo(final Type type, final Object value) { + if (type == null) return null; + final BsonWriter out = writerPool.get().tiny(tiny); + factory.loadEncoder(type).convertTo(out, value); + byte[] result = out.toArray(); + writerPool.offer(out); + return result; + } + + public void convertTo(final OutputStream out, final Object value) { + if (value == null) { + new BsonStreamWriter(tiny, out).writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(new BsonStreamWriter(tiny, out), value); + } + } + + public void convertTo(final OutputStream out, final Type type, final Object value) { + if (type == null) return; + if (value == null) { + new BsonStreamWriter(tiny, out).writeNull(); + } else { + factory.loadEncoder(type).convertTo(new BsonStreamWriter(tiny, out), value); + } + } + + public ByteBuffer[] convertTo(final Supplier supplier, final Type type, final Object value) { + if (supplier == null || type == null) return null; + BsonByteBufferWriter out = new BsonByteBufferWriter(tiny, supplier); + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(type).convertTo(out, value); + } + return out.toBuffers(); + } + + public ByteBuffer[] convertTo(final Supplier supplier, final Object value) { + if (supplier == null) return null; + BsonByteBufferWriter out = new BsonByteBufferWriter(tiny, supplier); + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(out, value); + } + return out.toBuffers(); + } + + public void convertTo(final BsonWriter writer, final Object value) { + if (value == null) { + writer.writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(writer, value); + } + } + + public void convertTo(final BsonWriter writer, final Type type, final Object value) { + if (type == null) return; + factory.loadEncoder(type).convertTo(writer, value); + } + + public BsonWriter convertToWriter(final Object value) { + if (value == null) return null; + return convertToWriter(value.getClass(), value); + } + + public BsonWriter convertToWriter(final Type type, final Object value) { + if (type == null) return null; + final BsonWriter out = writerPool.get().tiny(tiny); + factory.loadEncoder(type).convertTo(out, value); + return out; + } + +} diff --git a/src/org/redkale/convert/bson/BsonFactory.java b/src/org/redkale/convert/bson/BsonFactory.java new file mode 100644 index 000000000..67c6a2af7 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonFactory.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.convert.bson; + +import java.io.Serializable; +import org.redkale.convert.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public final class BsonFactory extends ConvertFactory { + + 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); + } + + @Override + public BsonFactory tiny(boolean tiny) { + this.tiny = tiny; + return this; + } + + 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..9759032ff --- /dev/null +++ b/src/org/redkale/convert/bson/BsonReader.java @@ -0,0 +1,324 @@ +/* + * 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.function.*; +import org.redkale.convert.*; +import static org.redkale.convert.Reader.SIGN_NULL; +import org.redkale.convert.ext.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class BsonReader extends 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; + + protected byte typeval; //字段的类型值 对应 BsonWriter.writeField + + protected int position = -1; + + 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 final String readObjectB(final Class clazz) { + this.fieldIndex = 0; //必须要重置为0 + final String newcls = readClassName(); + if (newcls != null && !newcls.isEmpty()) return newcls; + short bt = readShort(); + if (bt == Reader.SIGN_NULL) return null; + if (bt != SIGN_OBJECTB) { + throw new ConvertException("a bson object must begin with " + (SIGN_OBJECTB) + + " (position = " + position + ") but '" + currentByte() + "'"); + } + return ""; + } + + @Override + public final void readObjectE(final Class clazz) { + if (readShort() != SIGN_OBJECTE) { + throw new ConvertException("a bson object must end with " + (SIGN_OBJECTE) + + " (position = " + position + ") but '" + currentByte() + "'"); + } + } + + protected byte currentByte() { + return this.content[this.position]; + } + + @Override + public final int readMapB() { + return readArrayB(); + } + + @Override + public final void readMapE() { + } + + /** + * 判断下一个非空白字节是否为[ + * + * @return 数组长度或SIGN_NULL + */ + @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 final void readArrayE() { + } + + /** + * 判断下一个非空白字节是否: + */ + @Override + public final void readBlank() { + } + + /** + * 判断对象是否存在下一个属性或者数组是否存在下一个元素 + * + * @return 是否存在 + */ + @Override + public final 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 final DeMember readFieldName(final DeMember[] members) { + final String exceptedfield = readSmallString(); + this.typeval = readByte(); + final int len = members.length; + if (this.fieldIndex >= len) this.fieldIndex = 0; + for (int k = this.fieldIndex; k < len; k++) { + if (exceptedfield.equals(members[k].getAttribute().field())) { + this.fieldIndex = k; + return members[k]; + } + } + for (int k = 0; k < this.fieldIndex; k++) { + if (exceptedfield.equals(members[k].getAttribute().field())) { + this.fieldIndex = 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 final float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + @Override + public final double readDouble() { + return Double.longBitsToDouble(readLong()); + } + + @Override + public final 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; //上一行已经++this.position,所以此处要-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;//上一行已经++this.position,所以此处要-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..d5b497d51 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonSimpledCoder.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.convert.bson; + +import org.redkale.convert.SimpledCoder; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param 序列化/反解析的数据类型 + */ +public abstract class BsonSimpledCoder extends SimpledCoder { + +} diff --git a/src/org/redkale/convert/bson/BsonStreamReader.java b/src/org/redkale/convert/bson/BsonStreamReader.java new file mode 100644 index 000000000..cd314d3dc --- /dev/null +++ b/src/org/redkale/convert/bson/BsonStreamReader.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.bson; + +import java.io.*; +import org.redkale.convert.*; + +/** + * + * @author zhangjx + */ +class BsonStreamReader extends BsonByteBufferReader { + + private InputStream in; + + private byte currByte; + + protected BsonStreamReader(InputStream in) { + this.in = in; + } + + @Override + protected boolean recycle() { + super.recycle(); // this.position 初始化值为-1 + this.in = null; + this.currByte = 0; + return false; + } + + @Override + public byte readByte() { + try { + byte b = (currByte = (byte) in.read()); + this.position++; + return b; + } catch (IOException e) { + throw new ConvertException(e); + } + } + + @Override + protected byte currentByte() { + return currByte; + } + + @Override + protected byte[] read(final int len) { + byte[] bs = new byte[len]; + try { + in.read(bs); + this.position += len; + } catch (IOException e) { + throw new ConvertException(e); + } + return bs; + } +} diff --git a/src/org/redkale/convert/bson/BsonStreamWriter.java b/src/org/redkale/convert/bson/BsonStreamWriter.java new file mode 100644 index 000000000..c0c22fd10 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonStreamWriter.java @@ -0,0 +1,48 @@ +/* + * 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.*; +import org.redkale.convert.*; + +/** + * + * @author zhangjx + */ +class BsonStreamWriter extends BsonByteBufferWriter { + + private OutputStream out; + + protected BsonStreamWriter(boolean tiny, OutputStream out) { + super(tiny, null); + this.out = out; + } + + @Override + protected boolean recycle() { + super.recycle(); + this.out = null; + return false; + } + + @Override + public void writeTo(final byte[] chs, final int start, final int len) { + try { + out.write(chs, start, len); + } catch (IOException e) { + throw new ConvertException(e); + } + } + + @Override + public void writeTo(final byte ch) { + try { + out.write((byte) ch); + } catch (IOException e) { + throw new ConvertException(e); + } + } +} diff --git a/src/org/redkale/convert/bson/BsonWriter.java b/src/org/redkale/convert/bson/BsonWriter.java new file mode 100644 index 000000000..40c1925d3 --- /dev/null +++ b/src/org/redkale/convert/bson/BsonWriter.java @@ -0,0 +1,301 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class BsonWriter extends 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 tiny() { + return tiny; + } + + public BsonWriter tiny(boolean tiny) { + this.tiny = tiny; + return this; + } + + //----------------------------------------------------------------------- + //----------------------------------------------------------------------- + /** + * 扩充指定长度的缓冲区 + * + * @param len 扩容长度 + * @return 固定0 + */ + 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(Object obj) { + super.writeObjectB(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 writeFieldName( 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 String值 + */ + @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/bson/package-info.java b/src/org/redkale/convert/bson/package-info.java new file mode 100644 index 000000000..6518406c1 --- /dev/null +++ b/src/org/redkale/convert/bson/package-info.java @@ -0,0 +1,4 @@ +/** + * 提供BSON的序列化和反解析功能 + */ +package org.redkale.convert.bson; diff --git a/src/org/redkale/convert/ext/BigIntegerSimpledCoder.java b/src/org/redkale/convert/ext/BigIntegerSimpledCoder.java new file mode 100644 index 000000000..4723235e8 --- /dev/null +++ b/src/org/redkale/convert/ext/BigIntegerSimpledCoder.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.SimpledCoder; +import org.redkale.convert.Writer; +import org.redkale.convert.Reader; +import java.math.BigInteger; + +/** + * BigInteger 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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); + } + + /** + * BigInteger 的JsonSimpledCoder实现 + * + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ + public static class BigIntegerJsonSimpledCoder extends SimpledCoder { + + public static final BigIntegerJsonSimpledCoder instance = new BigIntegerJsonSimpledCoder(); + + @Override + public void convertTo(final Writer out, final BigInteger value) { + if (value == null) { + out.writeNull(); + } else { + out.writeString(value.toString()); + } + } + + @Override + public BigInteger convertFrom(Reader in) { + final String str = in.readString(); + if (str == null) return null; + return new BigInteger(str); + } + } +} diff --git a/src/org/redkale/convert/ext/BoolArraySimpledCoder.java b/src/org/redkale/convert/ext/BoolArraySimpledCoder.java new file mode 100644 index 000000000..2af5397bf --- /dev/null +++ b/src/org/redkale/convert/ext/BoolArraySimpledCoder.java @@ -0,0 +1,71 @@ +/* + * 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; + +/** + * boolean[] 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + 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..5f7905bec --- /dev/null +++ b/src/org/redkale/convert/ext/BoolSimpledCoder.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * boolean 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..6c6b21b06 --- /dev/null +++ b/src/org/redkale/convert/ext/ByteArraySimpledCoder.java @@ -0,0 +1,71 @@ +/* + * 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; + +/** + * byte[] 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + 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..041b7c804 --- /dev/null +++ b/src/org/redkale/convert/ext/ByteSimpledCoder.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * byte 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..4a8589dd9 --- /dev/null +++ b/src/org/redkale/convert/ext/CharArraySimpledCoder.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.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * char[] 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + 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/CharSequenceSimpledCoder.java b/src/org/redkale/convert/ext/CharSequenceSimpledCoder.java new file mode 100644 index 000000000..0fcf32e98 --- /dev/null +++ b/src/org/redkale/convert/ext/CharSequenceSimpledCoder.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.convert.ext; + +import org.redkale.convert.*; + +/** + * CharSequence 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +public class CharSequenceSimpledCoder extends SimpledCoder { + + public static final CharSequenceSimpledCoder instance = new CharSequenceSimpledCoder(); + + @Override + public void convertTo(W out, CharSequence value) { + out.writeString(value == null ? null : value.toString()); + } + + @Override + public CharSequence convertFrom(R in) { + return in.readString(); + } +} diff --git a/src/org/redkale/convert/ext/CharSimpledCoder.java b/src/org/redkale/convert/ext/CharSimpledCoder.java new file mode 100644 index 000000000..edea0bfd1 --- /dev/null +++ b/src/org/redkale/convert/ext/CharSimpledCoder.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * char 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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/CompletionHandlerSimpledCoder.java b/src/org/redkale/convert/ext/CompletionHandlerSimpledCoder.java new file mode 100644 index 000000000..b4882a8a5 --- /dev/null +++ b/src/org/redkale/convert/ext/CompletionHandlerSimpledCoder.java @@ -0,0 +1,36 @@ +/* + * 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.nio.channels.*; +import org.redkale.convert.*; + +/** + * java.nio.channels.CompletionHandler 的SimpledCoder实现, 只输出null + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +public final class CompletionHandlerSimpledCoder extends SimpledCoder { + + public static final CompletionHandlerSimpledCoder instance = new CompletionHandlerSimpledCoder(); + + @Override + public void convertTo(W out, CompletionHandler value) { + out.writeObjectNull(CompletionHandler.class); + } + + @Override + public CompletionHandler convertFrom(R in) { + in.readObjectB(CompletionHandler.class); + return null; + } + +} diff --git a/src/org/redkale/convert/ext/DLongSimpledCoder.java b/src/org/redkale/convert/ext/DLongSimpledCoder.java new file mode 100644 index 000000000..9dbd7eb22 --- /dev/null +++ b/src/org/redkale/convert/ext/DLongSimpledCoder.java @@ -0,0 +1,71 @@ +/* + * 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 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +public final class DLongSimpledCoder extends SimpledCoder { + + private static final ByteArraySimpledCoder bsSimpledCoder = ByteArraySimpledCoder.instance; + + public static final DLongSimpledCoder instance = new DLongSimpledCoder(); + + @Override + public void convertTo(final W out, final DLong value) { + if (value == null) { + out.writeNull(); + } else { + bsSimpledCoder.convertTo(out, value.directBytes()); + } + } + + @Override + public DLong convertFrom(R in) { + byte[] bs = bsSimpledCoder.convertFrom(in); + if (bs == null) return null; + return DLong.create(bs); + } + + /** + * DLong 的JsonSimpledCoder实现 + * + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ + public static class DLongJsonSimpledCoder extends SimpledCoder { + + public static final DLongJsonSimpledCoder instance = new DLongJsonSimpledCoder(); + + @Override + public void convertTo(final Writer out, final DLong value) { + if (value == null) { + out.writeNull(); + } else { + out.writeSmallString(value.toString()); + } + } + + @Override + public DLong convertFrom(Reader in) { + final String str = in.readSmallString(); + if (str == null) return null; + return DLong.create(Utility.hexToBin(str)); + } + } +} diff --git a/src/org/redkale/convert/ext/DateSimpledCoder.java b/src/org/redkale/convert/ext/DateSimpledCoder.java new file mode 100644 index 000000000..79aa269ff --- /dev/null +++ b/src/org/redkale/convert/ext/DateSimpledCoder.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * Date 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..ddc5cf259 --- /dev/null +++ b/src/org/redkale/convert/ext/DoubleArraySimpledCoder.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.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * double[] 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + 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..346864533 --- /dev/null +++ b/src/org/redkale/convert/ext/DoubleSimpledCoder.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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * double 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..53ea7739a --- /dev/null +++ b/src/org/redkale/convert/ext/EnumSimpledCoder.java @@ -0,0 +1,48 @@ +/* + * 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; + +/** + * 枚举 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + * @param Enum的子类 + */ +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..b3d85dbdc --- /dev/null +++ b/src/org/redkale/convert/ext/FloatArraySimpledCoder.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.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * float[] 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + 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..3c81e1d23 --- /dev/null +++ b/src/org/redkale/convert/ext/FloatSimpledCoder.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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * float 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..4a31c9f37 --- /dev/null +++ b/src/org/redkale/convert/ext/InetAddressSimpledCoder.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.convert.ext; + +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; +import org.redkale.convert.Reader; +import java.net.*; + +/** + * InetAddress 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + } + } + + /** + * InetSocketAddress 的SimpledCoder实现 + * + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ + 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; + } + } + + } + + /** + * InetAddress 的JsonSimpledCoder实现 + * + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ + public final static class InetAddressJsonSimpledCoder extends SimpledCoder { + + public static final InetAddressJsonSimpledCoder instance = new InetAddressJsonSimpledCoder(); + + @Override + public void convertTo(W out, InetAddress value) { + if (value == null) { + out.writeNull(); + return; + } + StringSimpledCoder.instance.convertTo(out, value.getHostAddress()); + } + + @Override + public InetAddress convertFrom(R in) { + String str = StringSimpledCoder.instance.convertFrom(in); + if (str == null) return null; + try { + return InetAddress.getByName(str); + } catch (Exception ex) { + return null; + } + } + + } + + /** + * InetSocketAddress 的JsonSimpledCoder实现 + * + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ + public final static class InetSocketAddressJsonSimpledCoder extends SimpledCoder { + + public static final InetSocketAddressJsonSimpledCoder instance = new InetSocketAddressJsonSimpledCoder(); + + @Override + public void convertTo(W out, InetSocketAddress value) { + if (value == null) { + out.writeNull(); + return; + } + StringSimpledCoder.instance.convertTo(out, value.getHostString() + ":" + value.getPort()); + } + + @Override + public InetSocketAddress convertFrom(R 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/ext/IntArraySimpledCoder.java b/src/org/redkale/convert/ext/IntArraySimpledCoder.java new file mode 100644 index 000000000..9b60bbc2d --- /dev/null +++ b/src/org/redkale/convert/ext/IntArraySimpledCoder.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.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * int[] 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + 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..2028a409a --- /dev/null +++ b/src/org/redkale/convert/ext/IntSimpledCoder.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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * int 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..c9d906106 --- /dev/null +++ b/src/org/redkale/convert/ext/LongArraySimpledCoder.java @@ -0,0 +1,71 @@ +/* + * 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; + +/** + * long[] 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + 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..09a635881 --- /dev/null +++ b/src/org/redkale/convert/ext/LongSimpledCoder.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * long 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..9569dd2ba --- /dev/null +++ b/src/org/redkale/convert/ext/NumberSimpledCoder.java @@ -0,0 +1,36 @@ +/* + * 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; + +/** + * Number 的SimpledCoder实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..a0787c077 --- /dev/null +++ b/src/org/redkale/convert/ext/PatternSimpledCoder.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 java.util.regex.*; +import org.redkale.convert.*; + +/** + * Pattern 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..a86817bee --- /dev/null +++ b/src/org/redkale/convert/ext/ShortArraySimpledCoder.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.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * short[] 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + 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..d761540fd --- /dev/null +++ b/src/org/redkale/convert/ext/ShortSimpledCoder.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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * short 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..76e39156a --- /dev/null +++ b/src/org/redkale/convert/ext/StringArraySimpledCoder.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.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * String[] 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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; + 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..e5e61bbf7 --- /dev/null +++ b/src/org/redkale/convert/ext/StringSimpledCoder.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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.SimpledCoder; +import org.redkale.convert.Writer; + +/** + * String 的SimpledCoder实现 + * + *

详情见: http://www.redkale.org + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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..2bbe852c4 --- /dev/null +++ b/src/org/redkale/convert/ext/TypeSimpledCoder.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.convert.ext; + +import org.redkale.convert.Reader; +import org.redkale.convert.Writer; +import org.redkale.convert.SimpledCoder; + +/** + * Type 的SimpledCoder实现 只支持Type的子类Class + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +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/ext/URISimpledCoder.java b/src/org/redkale/convert/ext/URISimpledCoder.java new file mode 100644 index 000000000..05daf0f74 --- /dev/null +++ b/src/org/redkale/convert/ext/URISimpledCoder.java @@ -0,0 +1,39 @@ +/* + * 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.net.*; +import org.redkale.convert.*; + +/** + * + * @author zhangjx + */ +public class URISimpledCoder extends SimpledCoder { + + public static final URLSimpledCoder instance = new URLSimpledCoder(); + + @Override + public void convertTo(final Writer out, final URI value) { + if (value == null) { + out.writeNull(); + } else { + out.writeString(value.toString()); + } + } + + @Override + public URI convertFrom(Reader in) { + final String str = in.readString(); + if (str == null) return null; + try { + return new URI(str); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/org/redkale/convert/ext/URLSimpledCoder.java b/src/org/redkale/convert/ext/URLSimpledCoder.java new file mode 100644 index 000000000..431424efe --- /dev/null +++ b/src/org/redkale/convert/ext/URLSimpledCoder.java @@ -0,0 +1,39 @@ +/* + * 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.net.*; +import org.redkale.convert.*; + +/** + * + * @author zhangjx + */ +public class URLSimpledCoder extends SimpledCoder { + + public static final URLSimpledCoder instance = new URLSimpledCoder(); + + @Override + public void convertTo(final Writer out, final URL value) { + if (value == null) { + out.writeNull(); + } else { + out.writeString(value.toString()); + } + } + + @Override + public URL convertFrom(Reader in) { + final String str = in.readString(); + if (str == null) return null; + try { + return new URL(str); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/org/redkale/convert/ext/package-info.java b/src/org/redkale/convert/ext/package-info.java new file mode 100644 index 000000000..3d10de53b --- /dev/null +++ b/src/org/redkale/convert/ext/package-info.java @@ -0,0 +1,4 @@ +/** + * Convert的基本数据的Coder实现 + */ +package org.redkale.convert.ext; diff --git a/src/org/redkale/convert/json/JsonByteBufferReader.java b/src/org/redkale/convert/json/JsonByteBufferReader.java new file mode 100644 index 000000000..3e04a27d8 --- /dev/null +++ b/src/org/redkale/convert/json/JsonByteBufferReader.java @@ -0,0 +1,336 @@ +/* + * 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 org.redkale.convert.*; +import static org.redkale.convert.Reader.*; + +/** + * 只支持UTF-8格式 + * + * @author zhangjx + */ +public class JsonByteBufferReader extends JsonReader { + + private char currentChar; + + private ByteBuffer[] buffers; + + private int currentIndex = 0; + + private ByteBuffer currentBuffer; + + protected JsonByteBufferReader(ByteBuffer... buffers) { + this.buffers = buffers; + if (buffers != null && buffers.length > 0) this.currentBuffer = buffers[currentIndex]; + } + + @Override + protected boolean recycle() { + super.recycle(); // this.position 初始化值为-1 + this.currentIndex = 0; + this.currentChar = 0; + this.currentBuffer = null; + this.buffers = null; + return false; + } + + protected byte nextByte() { + if (this.currentBuffer.hasRemaining()) { + this.position++; + return this.currentBuffer.get(); + } + for (;;) { + this.currentBuffer = this.buffers[++this.currentIndex]; + if (this.currentBuffer.hasRemaining()) { + this.position++; + return this.currentBuffer.get(); + } + } + } + + /** + * 读取下一个字符, 不跳过空白字符 + * + * @return 有效字符或空白字符 + */ + @Override + protected final char nextChar() { + if (currentChar != 0) { + char ch = currentChar; + this.currentChar = 0; + return ch; + } + if (this.currentBuffer != null) { + int remain = this.currentBuffer.remaining(); + if (remain == 0 && this.currentIndex + 1 >= this.buffers.length) return 0; + } + byte b1 = nextByte(); + if (b1 >= 0) {// 1 byte, 7 bits: 0xxxxxxx + return (char) b1; + } else if ((b1 >> 5) == -2 && (b1 & 0x1e) != 0) { // 2 bytes, 11 bits: 110xxxxx 10xxxxxx + return (char) (((b1 << 6) ^ nextByte()) ^ (((byte) 0xC0 << 6) ^ ((byte) 0x80))); + } else if ((b1 >> 4) == -2) { // 3 bytes, 16 bits: 1110xxxx 10xxxxxx 10xxxxxx + return (char) ((b1 << 12) ^ (nextByte() << 6) ^ (nextByte() ^ (((byte) 0xE0 << 12) ^ ((byte) 0x80 << 6) ^ ((byte) 0x80)))); + } else { // 4 bytes, 21 bits: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + throw new RuntimeException(new UnmappableCharacterException(4)); + } + } + + /** + * 读取下一个有效字符 + * + * @return 有效字符 + */ + @Override + protected final char nextGoodChar() { + char c = nextChar(); + if (c > ' ' || c == 0) return c; // 0 表示buffer结尾了 + for (;;) { + c = nextChar(); + if (c > ' ' || c == 0) return c; + } + } + + /** + * 回退最后读取的字符 + * + * @param ch 回退的字符 + */ + @Override + protected final void backChar(char ch) { + this.currentChar = ch; + } + + /** + * 判断下一个非空白字符是否为{ + * + * @return SIGN_NOLENGTH 或 SIGN_NULL + */ + @Override + public final String readObjectB(final Class clazz) { + char ch = nextGoodChar(); + if (ch == '{') return ""; + if (ch == 'n' && nextChar() == 'u' && nextChar() == 'l' && nextChar() == 'l') return null; + if (ch == 'N' && nextChar() == 'U' && nextChar() == 'L' && nextChar() == 'L') return null; + throw new ConvertException("a json object text must begin with '{' (position = " + position + ") but '" + ch + "'"); + } + + /** + * 判断下一个非空白字符是否为[ + * + * @return SIGN_NOLENGTH 或 SIGN_NULL + */ + @Override + public final int readArrayB() { + char ch = nextGoodChar(); + if (ch == '[' || ch == '{') return SIGN_NOLENGTH; + if (ch == 'n' && nextChar() == 'u' && nextChar() == 'l' && nextChar() == 'l') return SIGN_NULL; + if (ch == 'N' && nextChar() == 'U' && nextChar() == 'L' && nextChar() == 'L') return SIGN_NULL; + throw new ConvertException("a json array text must begin with '[' (position = " + position + ") but '" + ch + "'"); + } + + /** + * 判断下一个非空白字符是否: + */ + @Override + public final void readBlank() { + char ch = nextGoodChar(); + if (ch == ':') return; + throw new ConvertException("expected a ':' but '" + ch + "'(position = " + position + ")"); + } + + /** + * 判断对象是否存在下一个属性或者数组是否存在下一个元素 + * + * @return 是否存在 + */ + @Override + public final boolean hasNext() { + char ch = nextGoodChar(); + if (ch == ',') return true; + if (ch == '}' || ch == ']') return false; + backChar(ch); // { [ 交由 readObjectB 或 readMapB 或 readArrayB 读取 + return true; + } + + /** + * 读取小字符串 + * + * @return String值 + */ + @Override + public final String readSmallString() { + char ch = nextGoodChar(); + if (ch == 0) return null; + final StringBuilder sb = new StringBuilder(); + if (ch == '"' || ch == '\'') { + final char quote = ch; + for (;;) { + ch = nextChar(); + if (ch == '\\') { + char c = nextChar(); + switch (c) { + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + case 'n': + sb.append('\n'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char) Integer.parseInt(new String(new char[]{nextChar(), nextChar(), nextChar(), nextChar()}), 16)); + break; + case 't': + sb.append('\t'); + break; + case 'b': + sb.append('\b'); + break; + case 'f': + sb.append('\f'); + break; + default: + throw new ConvertException("illegal escape(" + c + ") (position = " + this.position + ")"); + } + } else if (ch == quote || ch == 0) { + break; + } else { + sb.append(ch); + } + } + return sb.toString(); + } else { + sb.append(ch); + for (;;) { + ch = nextChar(); + if (ch == '\\') { + char c = nextChar(); + switch (c) { + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + case 'n': + sb.append('\n'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char) Integer.parseInt(new String(new char[]{nextChar(), nextChar(), nextChar(), nextChar()}), 16)); + break; + case 't': + sb.append('\t'); + break; + case 'b': + sb.append('\b'); + break; + case 'f': + sb.append('\f'); + break; + default: + throw new ConvertException("illegal escape(" + c + ") (position = " + this.position + ")"); + } + } else if (ch == ',' || ch == ']' || ch == '}' || ch <= ' ' || ch == ':') { // ch <= ' ' 包含 0 + break; + } else { + sb.append(ch); + } + } + String rs = sb.toString(); + return "null".equalsIgnoreCase(rs) ? null : rs; + } + } + + /** + * 读取一个int值 + * + * @return int值 + */ + @Override + public final int readInt() { + char firstchar = nextGoodChar(); + if (firstchar == '"' || firstchar == '\'') { + firstchar = nextChar(); + if (firstchar == '"' || firstchar == '\'') return 0; + } + int value = 0; + final boolean negative = firstchar == '-'; + if (!negative) { + if (firstchar < '0' || firstchar > '9') throw new ConvertException("illegal escape(" + firstchar + ") (position = " + position + ")"); + value = firstchar - '0'; + } + for (;;) { + char ch = nextChar(); + if (ch == 0) break; + if (ch >= '0' && ch <= '9') { + value = (value << 3) + (value << 1) + (ch - '0'); + } else if (ch == '"' || ch == '\'') { + } else if (ch == ',' || ch == '}' || ch == ']' || ch <= ' ' || ch == ':') { + backChar(ch); + break; + } else { + throw new ConvertException("illegal escape(" + ch + ") (position = " + position + ")"); + } + } + return negative ? -value : value; + } + + /** + * 读取一个long值 + * + * @return long值 + */ + @Override + public final long readLong() { + char firstchar = nextGoodChar(); + if (firstchar == '"' || firstchar == '\'') { + firstchar = nextChar(); + if (firstchar == '"' || firstchar == '\'') return 0L; + } + long value = 0; + final boolean negative = firstchar == '-'; + if (!negative) { + if (firstchar < '0' || firstchar > '9') throw new ConvertException("illegal escape(" + firstchar + ") (position = " + position + ")"); + value = firstchar - '0'; + } + for (;;) { + char ch = nextChar(); + if (ch == 0) break; + if (ch >= '0' && ch <= '9') { + value = (value << 3) + (value << 1) + (ch - '0'); + } else if (ch == '"' || ch == '\'') { + } else if (ch == ',' || ch == '}' || ch == ']' || ch <= ' ' || ch == ':') { + backChar(ch); + break; + } else { + throw new ConvertException("illegal escape(" + ch + ") (position = " + position + ")"); + } + } + return negative ? -value : value; + } + + /** + * 读取字符串, 必须是"或者'包围的字符串值 + * + * @return String值 + */ + @Override + public final String readString() { + return readSmallString(); + } + +} diff --git a/src/org/redkale/convert/json/JsonByteBufferWriter.java b/src/org/redkale/convert/json/JsonByteBufferWriter.java new file mode 100644 index 000000000..68214fe0b --- /dev/null +++ b/src/org/redkale/convert/json/JsonByteBufferWriter.java @@ -0,0 +1,352 @@ +/* + * 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.convert.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class JsonByteBufferWriter extends JsonWriter { + + protected static final Charset UTF8 = Charset.forName("UTF-8"); + + protected Charset charset; + + private final Supplier supplier; + + private ByteBuffer[] buffers; + + private int index; + + protected JsonByteBufferWriter(boolean tiny, Supplier supplier) { + this(tiny, null, supplier); + } + + protected JsonByteBufferWriter(boolean tiny, Charset charset, Supplier supplier) { + this.tiny = tiny; + this.charset = UTF8.equals(charset) ? null : charset; + this.supplier = supplier; + } + + @Override + public JsonByteBufferWriter tiny(boolean tiny) { + this.tiny = tiny; + return this; + } + + @Override + protected boolean recycle() { + this.index = 0; + this.charset = null; + 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 ConvertException("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]; + } + + protected 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; + } + + protected 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 String值 + */ + @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 writeInt(int value) { + writeTo(false, String.valueOf(value)); + } + + @Override + public void writeLong(long value) { + writeTo(false, String.valueOf(value)); + } + + @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 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..22d691775 --- /dev/null +++ b/src/org/redkale/convert/json/JsonConvert.java @@ -0,0 +1,207 @@ +/* + * 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.io.*; +import java.lang.reflect.*; +import java.nio.*; +import java.nio.charset.*; +import java.util.function.*; +import org.redkale.convert.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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; + } + + @Override + public JsonFactory getFactory() { + return (JsonFactory) factory; + } + + public static JsonConvert root() { + return JsonFactory.root().getConvert(); + } + + //------------------------------ reader ----------------------------------------------------------- + public JsonReader pollJsonReader(final ByteBuffer... buffers) { + return new JsonByteBufferReader(buffers); + } + + public JsonReader pollJsonReader(final InputStream in) { + return new JsonStreamReader(in); + } + + public JsonReader pollJsonReader() { + return readerPool.get(); + } + + public void offerJsonReader(final JsonReader in) { + if (in != null) readerPool.offer(in); + } + + //------------------------------ writer ----------------------------------------------------------- + public JsonByteBufferWriter pollJsonWriter(final Supplier supplier) { + return new JsonByteBufferWriter(tiny, supplier); + } + + public JsonWriter pollJsonWriter(final OutputStream out) { + return new JsonStreamWriter(tiny, out); + } + + public JsonWriter pollJsonWriter(final Charset charset, final OutputStream out) { + return new JsonStreamWriter(tiny, charset, out); + } + + public JsonWriter pollJsonWriter() { + return writerPool.get().tiny(tiny); + } + + public void offerJsonWriter(final JsonWriter out) { + if (out != null) writerPool.offer(out); + } + + //------------------------------ convertFrom ----------------------------------------------------------- + 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, final int start, final 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 T convertFrom(final Type type, final InputStream in) { + if (type == null || in == null) return null; + return (T) factory.loadDecoder(type).convertFrom(new JsonStreamReader(in)); + } + + public T convertFrom(final Type type, final ByteBuffer... buffers) { + if (type == null || buffers == null || buffers.length == 0) return null; + return (T) factory.loadDecoder(type).convertFrom(new JsonByteBufferReader(buffers)); + } + + public T convertFrom(final Type type, final JsonReader reader) { + if (type == null) return null; + @SuppressWarnings("unchecked") + T rs = (T) factory.loadDecoder(type).convertFrom(reader); + return rs; + } + + //------------------------------ convertTo ----------------------------------------------------------- + public String convertTo(final Object value) { + if (value == null) return "null"; + return convertTo(value.getClass(), value); + } + + public String convertTo(final Type type, final Object value) { + if (type == null) return null; + if (value == null) return "null"; + final JsonWriter out = writerPool.get().tiny(tiny); + factory.loadEncoder(type).convertTo(out, value); + String result = out.toString(); + writerPool.offer(out); + return result; + } + + public void convertTo(final OutputStream out, final Object value) { + if (value == null) { + new JsonStreamWriter(tiny, out).writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(new JsonStreamWriter(tiny, out), value); + } + } + + public void convertTo(final OutputStream out, final Type type, final Object value) { + if (type == null) return; + if (value == null) { + new JsonStreamWriter(tiny, out).writeNull(); + } else { + factory.loadEncoder(type).convertTo(new JsonStreamWriter(tiny, out), value); + } + } + + public ByteBuffer[] convertTo(final Supplier supplier, final Object value) { + if (supplier == null) return null; + JsonByteBufferWriter out = new JsonByteBufferWriter(tiny, null, supplier); + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(out, value); + } + return out.toBuffers(); + } + + public ByteBuffer[] convertTo(final Supplier supplier, final Type type, final Object value) { + if (supplier == null || type == null) return null; + JsonByteBufferWriter out = new JsonByteBufferWriter(tiny, null, supplier); + if (value == null) { + out.writeNull(); + } else { + factory.loadEncoder(type).convertTo(out, value); + } + return out.toBuffers(); + } + + public void convertTo(final JsonWriter writer, final Object value) { + if (value == null) { + writer.writeNull(); + } else { + factory.loadEncoder(value.getClass()).convertTo(writer, value); + } + } + + public void convertTo(final JsonWriter writer, final Type type, final Object value) { + if (type == null) return; + if (value == null) { + writer.writeNull(); + } else { + factory.loadEncoder(type).convertTo(writer, value); + } + } + + public JsonWriter convertToWriter(final Object value) { + if (value == null) return null; + return convertToWriter(value.getClass(), value); + } + + public JsonWriter convertToWriter(final Type type, final Object value) { + if (type == null) return null; + final JsonWriter out = writerPool.get().tiny(tiny); + factory.loadEncoder(type).convertTo(out, value); + return out; + } +} diff --git a/src/org/redkale/convert/json/JsonFactory.java b/src/org/redkale/convert/json/JsonFactory.java new file mode 100644 index 000000000..c7d24d520 --- /dev/null +++ b/src/org/redkale/convert/json/JsonFactory.java @@ -0,0 +1,74 @@ +/* + * 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.ConvertFactory; +import java.io.Serializable; +import java.math.*; +import java.net.*; +import org.redkale.convert.ext.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public final class JsonFactory extends ConvertFactory { + + private static final JsonFactory instance = new JsonFactory(null, Boolean.getBoolean("convert.json.tiny")); + + static { + instance.register(InetAddress.class, InetAddressSimpledCoder.InetAddressJsonSimpledCoder.instance); + instance.register(InetSocketAddress.class, InetAddressSimpledCoder.InetSocketAddressJsonSimpledCoder.instance); + instance.register(DLong.class, DLongSimpledCoder.DLongJsonSimpledCoder.instance); + instance.register(BigInteger.class, BigIntegerSimpledCoder.BigIntegerJsonSimpledCoder.instance); + instance.register(Serializable.class, instance.loadEncoder(Object.class)); + } + + private JsonFactory(JsonFactory parent, boolean tiny) { + super(parent, tiny); + } + + @Override + public JsonFactory tiny(boolean tiny) { + this.tiny = tiny; + return this; + } + + 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..b40f71b11 --- /dev/null +++ b/src/org/redkale/convert/json/JsonReader.java @@ -0,0 +1,582 @@ +/* + * 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.*; +import static org.redkale.convert.Reader.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class JsonReader extends Reader { + + protected int position = -1; + + private char[] text; + + private int limit; + + public static ObjectPool createPool(int max) { + return new ObjectPool<>(max, (Object... params) -> new JsonReader(), null, JsonReader::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) { + 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(); + readBlank(); + if (key1.equals(field)) break; + skipValue(); + } + } + + } + + /** + * 跳过属性的值 + */ + @Override + public final void skipValue() { + final char ch = nextGoodChar(); + switch (ch) { + case '"': + case '\'': + backChar(ch); + readString(); + break; + case '{': + while (hasNext()) { + this.readSmallString(); //读掉field + this.readBlank(); + this.skipValue(); + } + break; + case '[': + while (hasNext()) { + this.skipValue(); + } + break; + default: + 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--; + } + + /** + * 判断下一个非空白字符是否为{ + * + * @param clazz 类名 + * @return 返回 null 表示对象为null, 返回空字符串表示当前class与返回的class一致,返回非空字符串表示class是当前class的子类。 + */ + @Override + public String readObjectB(final Class clazz) { + this.fieldIndex = 0; //必须要重置为0 + char ch = this.text[++this.position]; + if (ch == '{') return ""; + if (ch <= ' ') { + for (;;) { + ch = this.text[++this.position]; + if (ch > ' ') break; + } + if (ch == '{') return ""; + } + if (ch == 'n' && text[++position] == 'u' && text[++position] == 'l' && text[++position] == 'l') return null; + if (ch == 'N' && text[++position] == 'U' && text[++position] == 'L' && text[++position] == 'L') return null; + throw new ConvertException("a json object text must begin with '{' (position = " + position + ") but '" + ch + "' in (" + new String(this.text) + ")"); + } + + @Override + public final void readObjectE(final Class clazz) { + } + + /** + * 判断下一个非空白字符是否为{ + * + * @return SIGN_NOLENGTH 或 SIGN_NULL + */ + @Override + public final int readMapB() { + return readArrayB(); + } + + @Override + public final void readMapE() { + } + + /** + * 判断下一个非空白字符是否为[ + * + * @return SIGN_NOLENGTH 或 SIGN_NULL + */ + @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 final void readArrayE() { + } + + /** + * 判断下一个非空白字符是否: + */ + @Override + public void readBlank() { + 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--; // { [ 交由 readObjectB 或 readMapB 或 readArrayB 读取 + return true; + } + + @Override + public final 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 int值 + */ + @Override + public 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 ConvertException("illegal escape(" + firstchar + ") (position = " + currpos + ") in (" + new String(this.text) + ")"); + value = firstchar - '0'; + } + for (;;) { + if (currpos == eof) break; + char ch = text0[++currpos]; + int val = digits[ch]; + if (val == -3) break; + if (val == -1) throw new ConvertException("illegal escape(" + ch + ") (position = " + currpos + ") but '" + ch + "' in (" + new String(this.text) + ")"); + if (val != -2) value = value * 10 + val; + } + this.position = currpos - 1; + return negative ? -value : value; + } + + /** + * 读取一个long值 + * + * @return long值 + */ + @Override + public 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 ConvertException("illegal escape(" + firstchar + ") (position = " + currpos + ") in (" + new String(this.text) + ")"); + value = firstchar - '0'; + } + for (;;) { + if (currpos == eof) break; + char ch = text0[++currpos]; + int val = digits[ch]; + if (val == -3) break; + if (val == -1) throw new ConvertException("illegal escape(" + ch + ") (position = " + currpos + ") but '" + ch + "' in (" + new String(this.text) + ")"); + if (val != -2) value = value * 10 + val; + } + this.position = currpos - 1; + return negative ? -value : value; + } + + @Override + public final DeMember readFieldName(final DeMember[] members) { + final String exceptedfield = this.readSmallString(); + final int len = members.length; + if (this.fieldIndex >= len) this.fieldIndex = 0; + for (int k = this.fieldIndex; k < len; k++) { + if (exceptedfield.equals(members[k].getAttribute().field())) { + this.fieldIndex = k; + return members[k]; + } + } + for (int k = 0; k < this.fieldIndex; k++) { + if (exceptedfield.equals(members[k].getAttribute().field())) { + this.fieldIndex = k; + return members[k]; + } + } + return null; + //if (result == null && len == 1 && text0[start] == '@') return REFER; + } +//------------------------------------------------------------ + + @Override + public final boolean readBoolean() { + return "true".equalsIgnoreCase(this.readSmallString()); + } + + @Override + public final byte readByte() { + return (byte) readInt(); + } + + @Override + public final char readChar() { + return (char) readInt(); + } + + @Override + public final short readShort() { + return (short) readInt(); + } + + @Override + public final float readFloat() { + String chars = readSmallString(); + if (chars == null || chars.isEmpty()) return 0.f; + return Float.parseFloat(chars); + } + + @Override + public final double readDouble() { + String chars = readSmallString(); + if (chars == null || chars.isEmpty()) return 0.0; + return Double.parseDouble(chars); + } + + /** + * 读取字符串, 必须是"或者'包围的字符串值 + * + * @return String值 + */ + @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); + } + } + } + + final static int[] digits = new int[255]; + + static { + for (int i = 0; i < digits.length; i++) { + digits[i] = -1; //-1 错误 + } + for (int i = '0'; i <= '9'; i++) { + digits[i] = i - '0'; + } + for (int i = 'a'; i <= 'f'; i++) { + digits[i] = i - 'a' + 10; + } + for (int i = 'A'; i <= 'F'; i++) { + digits[i] = i - 'A' + 10; + } + digits['"'] = digits['\''] = -2; //-2 跳过 + digits[','] = digits['}'] = digits[']'] = digits[' '] = digits['\t'] = digits['\r'] = digits['\n'] = digits[':'] = -3; //-3退出 + + } +} diff --git a/src/org/redkale/convert/json/JsonSimpledCoder.java b/src/org/redkale/convert/json/JsonSimpledCoder.java new file mode 100644 index 000000000..d8d4f6006 --- /dev/null +++ b/src/org/redkale/convert/json/JsonSimpledCoder.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.convert.json; + +import org.redkale.convert.SimpledCoder; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param 序列化/反解析的数据类型 + */ +public abstract class JsonSimpledCoder extends SimpledCoder { + +} diff --git a/src/org/redkale/convert/json/JsonStreamReader.java b/src/org/redkale/convert/json/JsonStreamReader.java new file mode 100644 index 000000000..836f554f8 --- /dev/null +++ b/src/org/redkale/convert/json/JsonStreamReader.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.json; + +import java.io.*; +import org.redkale.convert.*; + +/** + * + * @author zhangjx + */ +class JsonStreamReader extends JsonByteBufferReader { + + private InputStream in; + + protected JsonStreamReader(InputStream in) { + this.in = in; + } + + @Override + protected boolean recycle() { + super.recycle(); // this.position 初始化值为-1 + this.in = null; + return false; + } + + @Override + protected byte nextByte() { + try { + byte b = (byte) in.read(); + this.position++; + return b; + } catch (IOException e) { + throw new ConvertException(e); + } + } +} diff --git a/src/org/redkale/convert/json/JsonStreamWriter.java b/src/org/redkale/convert/json/JsonStreamWriter.java new file mode 100644 index 000000000..3c34e0160 --- /dev/null +++ b/src/org/redkale/convert/json/JsonStreamWriter.java @@ -0,0 +1,151 @@ +/* + * 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.io.*; +import java.nio.*; +import java.nio.charset.*; +import org.redkale.convert.*; +import org.redkale.util.*; + +/** + * + * @author zhangjx + */ +class JsonStreamWriter extends JsonByteBufferWriter { + + private OutputStream out; + + protected JsonStreamWriter(boolean tiny, OutputStream out) { + this(tiny, null, out); + } + + protected JsonStreamWriter(boolean tiny, Charset charset, OutputStream out) { + super(tiny, charset, null); + this.out = out; + } + + @Override + protected boolean recycle() { + super.recycle(); + this.out = null; + return false; + } + + @Override + public void writeTo(final char ch) { + if (ch > Byte.MAX_VALUE) throw new ConvertException("writeTo char(int.value = " + (int) ch + ") must be less 127"); + try { + out.write((byte) ch); + } catch (IOException e) { + throw new ConvertException(e); + } + } + + @Override + public void writeTo(final char[] chs, final int start, final int len) { + writeTo(false, chs, start, len); + } + + private void writeTo(final boolean quote, final char[] chs, final int start, final int len) { + try { + if (quote) out.write('"'); + if (charset == null) { //UTF-8 + final int limit = start + len; + for (int i = start; i < limit; i++) { + char c = chs[i]; + if (c < 0x80) { + out.write((byte) c); + } else if (c < 0x800) { + out.write((byte) (0xc0 | (c >> 6))); + out.write((byte) (0x80 | (c & 0x3f))); + } else { + out.write((byte) (0xe0 | ((c >> 12)))); + out.write((byte) (0x80 | ((c >> 6) & 0x3f))); + out.write((byte) (0x80 | (c & 0x3f))); + } + } + } else { + ByteBuffer bb = charset.encode(CharBuffer.wrap(chs, start, len)); + out.write(bb.array()); + } + if (quote) out.write('"'); + } catch (IOException e) { + throw new ConvertException(e); + } + } + + /** + * 注意: 该String值不能为null且不会进行转义, 只用于不含需要转义字符的字符串,例如enum、double、BigInteger转换的String + * + * @param quote 是否写入双引号 + * @param value String值 + */ + @Override + public void writeTo(final boolean quote, final String value) { + char[] chs = Utility.charArray(value); + writeTo(quote, chs, 0, chs.length); + } + + @Override + public void writeInt(int value) { + writeTo(false, String.valueOf(value)); + } + + @Override + public void writeLong(long value) { + writeTo(false, String.valueOf(value)); + } + + @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(true, chs, 0, len); + 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(true, cs, 0, sb.length()); + } +} diff --git a/src/org/redkale/convert/json/JsonWriter.java b/src/org/redkale/convert/json/JsonWriter.java new file mode 100644 index 000000000..988b47044 --- /dev/null +++ b/src/org/redkale/convert/json/JsonWriter.java @@ -0,0 +1,381 @@ +/* + * 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 org.redkale.convert.*; +import org.redkale.util.*; + +/** + * + * writeTo系列的方法输出的字符不能含特殊字符 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class JsonWriter extends 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, (Object... params) -> new JsonWriter(), null, (JsonWriter t) -> t.recycle()); + } + + public JsonWriter() { + this(defaultSize); + } + + public JsonWriter(int size) { + this.content = new char[size > 128 ? size : 128]; + } + + @Override + public boolean tiny() { + return tiny; + } + + public JsonWriter tiny(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 非null且不含需要转义的字符的String值 + */ + 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 final void writeFieldName(Attribute attribute) { + if (this.comma) writeTo(','); + writeTo(true, attribute.field()); + writeTo(':'); + } + + @Override + public final void writeSmallString(String value) { + writeTo(true, 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 void writeInt(int value) { + final char sign = value >= 0 ? 0 : '-'; + if (value < 0) value = -value; + int size; + for (int i = 0;; i++) { + if (value <= sizeTable[i]) { + size = i + 1; + break; + } + } + if (sign != 0) size++; //负数 + expand(size); + + int q, r; + int charPos = count + size; + + // Generate two digits per iteration + while (value >= 65536) { + q = value / 100; + // really: r = i - (q * 100); + r = value - ((q << 6) + (q << 5) + (q << 2)); + value = q; + content[--charPos] = DigitOnes[r]; + content[--charPos] = DigitTens[r]; + } + + // Fall thru to fast mode for smaller numbers + // assert(i <= 65536, i); + for (;;) { + q = (value * 52429) >>> (16 + 3); + r = value - ((q << 3) + (q << 1)); // r = i-(q*10) ... + content[--charPos] = digits[r]; + value = q; + if (value == 0) break; + } + if (sign != 0) content[--charPos] = sign; + count += size; + } + + @Override + public void writeLong(long value) { + final char sign = value >= 0 ? 0 : '-'; + if (value < 0) value = -value; + int size = 19; + long p = 10; + for (int i = 1; i < 19; i++) { + if (value < p) { + size = i; + break; + } + p = 10 * p; + } + if (sign != 0) size++; //负数 + expand(size); + + long q; + int r; + int charPos = count + size; + + // Get 2 digits/iteration using longs until quotient fits into an int + while (value > Integer.MAX_VALUE) { + q = value / 100; + // really: r = i - (q * 100); + r = (int) (value - ((q << 6) + (q << 5) + (q << 2))); + value = q; + content[--charPos] = DigitOnes[r]; + content[--charPos] = DigitTens[r]; + } + + // Get 2 digits/iteration using ints + int q2; + int i2 = (int) value; + while (i2 >= 65536) { + q2 = i2 / 100; + // really: r = i2 - (q * 100); + r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2)); + i2 = q2; + content[--charPos] = DigitOnes[r]; + content[--charPos] = DigitTens[r]; + } + + // Fall thru to fast mode for smaller numbers + // assert(i2 <= 65536, i2); + for (;;) { + q2 = (i2 * 52429) >>> (16 + 3); + r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ... + content[--charPos] = digits[r]; + i2 = q2; + if (i2 == 0) break; + } + if (sign != 0) content[--charPos] = sign; + count += size; + } + + @Override + public final void writeFloat(float value) { + writeTo(false, String.valueOf(value)); + } + + @Override + public final void writeDouble(double value) { + writeTo(false, String.valueOf(value)); + } + + @Override + public final void wirteClassName(String clazz) { + } + + @Override + public final void writeObjectB(Object obj) { + super.writeObjectB(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('}'); + } + + final static char[] DigitTens = { + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', + '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', + '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', + '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', + '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', + '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', + '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', + '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', + '9', '9', '9', '9', '9', '9', '9', '9', '9', '9' + }; + + final static char[] DigitOnes = { + '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', '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', '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', '2', '3', '4', '5', '6', '7', '8', '9' + }; + + final static char[] digits = { + '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' + }; + + final static int[] sizeTable = {9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE}; +} diff --git a/src/org/redkale/convert/json/package-info.java b/src/org/redkale/convert/json/package-info.java new file mode 100644 index 000000000..7e181f13b --- /dev/null +++ b/src/org/redkale/convert/json/package-info.java @@ -0,0 +1,4 @@ +/** + * 提供JSON的序列化和反解析功能 + */ +package org.redkale.convert.json; diff --git a/src/org/redkale/convert/package-info.java b/src/org/redkale/convert/package-info.java new file mode 100644 index 000000000..72764d0e0 --- /dev/null +++ b/src/org/redkale/convert/package-info.java @@ -0,0 +1,4 @@ +/** + * 提供数据的序列化和反解析功能 + */ +package org.redkale.convert; diff --git a/src/org/redkale/net/AsyncConnection.java b/src/org/redkale/net/AsyncConnection.java new file mode 100644 index 000000000..4c3bc1d16 --- /dev/null +++ b/src/org/redkale/net/AsyncConnection.java @@ -0,0 +1,575 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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 AsynchronousChannelGroup group, final SocketAddress address) throws IOException { + return create(protocol, group, address, 0, 0); + } + + /** + * 创建客户端连接 + * + * @param protocol 连接类型 只能是TCP或UDP + * @param address 连接点子 + * @param group 连接AsynchronousChannelGroup + * @param readTimeoutSecond0 读取超时秒数 + * @param writeTimeoutSecond0 写入超时秒数 + * @return 连接 + * @throws java.io.IOException 异常 + */ + public static AsyncConnection create(final String protocol, final AsynchronousChannelGroup group, final SocketAddress address, + final int readTimeoutSecond0, final int writeTimeoutSecond0) throws IOException { + if ("TCP".equalsIgnoreCase(protocol)) { + AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(group); + 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 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, final 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/Context.java b/src/org/redkale/net/Context.java new file mode 100644 index 000000000..4977d5e81 --- /dev/null +++ b/src/org/redkale/net/Context.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.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.*; + +/** + * + *

详情见: http://www.redkale.org + * @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..5678e7727 --- /dev/null +++ b/src/org/redkale/net/PrepareRunner.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.net; + +import java.nio.*; +import java.nio.channels.*; +import java.util.logging.*; +import org.redkale.util.*; + +/** + * + *

详情见: http://www.redkale.org + * @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..b0ee5c272 --- /dev/null +++ b/src/org/redkale/net/PrepareServlet.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; + +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.atomic.*; +import java.util.logging.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Context的子类型 + * @param Request的子类型 + * @param

Response的子类型 + */ +public abstract class PrepareServlet, P extends Response, S extends Servlet> extends Servlet { + + protected final AtomicLong executeCounter = new AtomicLong(); //执行请求次数 + + protected final AtomicLong illRequestCounter = new AtomicLong(); //错误请求次数 + + protected final Set servlets = new HashSet<>(); + + protected final Map mappings = new HashMap<>(); + + public abstract void addServlet(S servlet, Object attachment, AnyValue conf, K... mappings); + + 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); + } + }); + } + } + + protected AnyValue getServletConf(Servlet servlet) { + return servlet._conf; + } + + protected void setServletConf(Servlet servlet, AnyValue conf) { + servlet._conf = conf; + } +} diff --git a/src/org/redkale/net/ProtocolServer.java b/src/org/redkale/net/ProtocolServer.java new file mode 100644 index 000000000..64917c812 --- /dev/null +++ b/src/org/redkale/net/ProtocolServer.java @@ -0,0 +1,184 @@ +/* + * 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.*; + +/** + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +public abstract class ProtocolServer { + + 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..8cf0cd61f --- /dev/null +++ b/src/org/redkale/net/Request.java @@ -0,0 +1,117 @@ +/* + * 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.ByteBuffer; +import java.util.*; +import org.redkale.convert.bson.BsonConvert; +import org.redkale.convert.json.JsonConvert; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public abstract class Request { + + protected final C 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(C context) { + this.context = context; + this.bsonConvert = context.getBsonConvert(); + this.jsonConvert = context.getJsonConvert(); + } + + /** + * 返回值:Integer.MIN_VALUE: 帧数据; -1:数据不合法; 0:解析完毕; >0: 需再读取的字节数。 + * + * @param buffer ByteBuffer对象 + * @return 缺少的字节数 + */ + protected abstract int readHeader(ByteBuffer buffer); + + /** + * 读取buffer,并返回读取的有效数据长度 + * + * @param buffer ByteBuffer对象 + * @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 T setProperty(String name, T value) { + properties.put(name, value); + return value; + } + + @SuppressWarnings("unchecked") + protected T getProperty(String name) { + return (T) properties.get(name); + } + + protected T removeProperty(String name) { + return (T)properties.remove(name); + } + + protected Map getProperties() { + return properties; + } + + public T setAttribute(String name, T value) { + attributes.put(name, value); + return value; + } + + @SuppressWarnings("unchecked") + public T getAttribute(String name) { + return (T) attributes.get(name); + } + + public T removeAttribute(String name) { + return (T)attributes.remove(name); + } + + public Map getAttributes() { + return attributes; + } + + public C 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..38cfd694a --- /dev/null +++ b/src/org/redkale/net/Response.java @@ -0,0 +1,230 @@ +/* + * 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.function.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Request的子类型 + */ +@SuppressWarnings("unchecked") +public abstract class Response> { + + protected final C context; + + protected final R request; + + protected AsyncConnection channel; + + private boolean inited = true; + + protected BiConsumer> 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(C 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.accept(request, this); + } 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(BiConsumer> 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, final 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 C getContext() { + return context; + } +} diff --git a/src/org/redkale/net/Server.java b/src/org/redkale/net/Server.java new file mode 100644 index 000000000..8fc99cc38 --- /dev/null +++ b/src/org/redkale/net/Server.java @@ -0,0 +1,199 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public abstract class Server, P extends Response, S extends Servlet> { + + public static final String RESNAME_SERVER_ROOT = "SERVER_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 C context; + + protected AnyValue config; + + protected Charset charset; + + protected InetSocketAddress address; + + protected int backlog; + + protected ProtocolServer serverChannel; + + protected int bufferCapacity; + + 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.maxbody = config.getIntValue("maxbody", 64 * 1024); + this.bufferCapacity = config.getIntValue("bufferCapacity", 8 * 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; + } + + @SuppressWarnings("unchecked") + public void addServlet(S servlet, final Object attachment, AnyValue conf, K... mappings) { + this.prepare.addServlet(servlet, attachment, conf, mappings); + } + + 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() + ("TCP".equalsIgnoreCase(protocol) ? "" : ("." + protocol)) + " listen: " + address + + ", threads: " + threads + ", bufferCapacity: " + bufferCapacity + ", bufferPoolSize: " + bufferPoolSize + ", responsePoolSize: " + responsePoolSize + + ", started in " + (System.currentTimeMillis() - context.getServerStartTime()) + " ms"); + } + + protected abstract C 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 URL[] loadLib(final Logger logger, final String lib) throws Exception { + if (lib == null || lib.isEmpty()) return new URL[0]; + 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 new URL[0]; + 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)); + } + List list = new ArrayList<>(set); + Collections.sort(list, (URL o1, URL o2) -> o1.getFile().compareTo(o2.getFile())); + return list.toArray(new URL[list.size()]); + } + +} diff --git a/src/org/redkale/net/Servlet.java b/src/org/redkale/net/Servlet.java new file mode 100644 index 000000000..5324152f3 --- /dev/null +++ b/src/org/redkale/net/Servlet.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.net; + +import org.redkale.util.AnyValue; +import java.io.IOException; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Context的子类型 + * @param Request的子类型 + * @param

Response的子类型 + */ +public abstract class Servlet, P extends Response> { + + AnyValue _conf; //当前Servlet的配置 + + public void init(C context, AnyValue config) { + } + + public abstract void execute(R request, P response) throws IOException; + + public void destroy(C 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..da3b2adb7 --- /dev/null +++ b/src/org/redkale/net/Transport.java @@ -0,0 +1,253 @@ +/* + * 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.function.*; +import java.util.stream.*; +import org.redkale.util.*; +import org.redkale.watch.*; + +/** + * 传输客户端 + * + *

+ * 详情见: http://www.redkale.org + * + * @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; //即的name属性 + + protected final boolean tcp; + + protected final String protocol; + + protected final WatchFactory watch; + + protected final AsynchronousChannelGroup group; + + protected final InetSocketAddress clientAddress; + + protected InetSocketAddress[] remoteAddres = new InetSocketAddress[0]; + + protected final ObjectPool bufferPool; + + protected final ConcurrentHashMap> connPool = new ConcurrentHashMap<>(); + + public Transport(String name, WatchFactory watch, final ObjectPool transportBufferPool, + final AsynchronousChannelGroup transportChannelGroup, final InetSocketAddress clientAddress, final Collection addresses) { + this(name, DEFAULT_PROTOCOL, watch, transportBufferPool, transportChannelGroup, clientAddress, addresses); + } + + public Transport(String name, String protocol, WatchFactory watch, final ObjectPool transportBufferPool, + final AsynchronousChannelGroup transportChannelGroup, final InetSocketAddress clientAddress, final Collection addresses) { + this.name = name; + this.watch = watch; + this.protocol = protocol; + this.tcp = "TCP".equalsIgnoreCase(protocol); + this.group = transportChannelGroup; + this.bufferPool = transportBufferPool; + this.clientAddress = clientAddress; + updateRemoteAddresses(addresses); + } + + public Transport(final Collection transports) { + Transport first = null; + List tmpgroup = new ArrayList<>(); + for (Transport t : transports) { + if (first == null) first = t; + tmpgroup.add(t.name); + } + Collections.sort(tmpgroup); //必须按字母排列顺序确保,相同内容的transport列表组合的name相同,而不会因为list的顺序不同产生不同的name + this.name = tmpgroup.stream().collect(Collectors.joining(";")); + this.watch = first.watch; + this.protocol = first.protocol; + this.tcp = "TCP".equalsIgnoreCase(first.protocol); + this.group = first.group; + this.bufferPool = first.bufferPool; + this.clientAddress = first.clientAddress; + Set addrs = new HashSet<>(); + transports.forEach(t -> addrs.addAll(Arrays.asList(t.getRemoteAddresses()))); + updateRemoteAddresses(addrs); + } + + public final InetSocketAddress[] updateRemoteAddresses(final Collection addresses) { + InetSocketAddress[] oldAddresses = this.remoteAddres; + List list = new ArrayList<>(); + if (addresses != null) { + for (InetSocketAddress addr : addresses) { + if (clientAddress != null && clientAddress.equals(addr)) continue; + list.add(addr); + } + } + this.remoteAddres = list.toArray(new InetSocketAddress[list.size()]); + return oldAddresses; + } + + public String getName() { + return name; + } + + public void close() { + connPool.forEach((k, v) -> v.forEach(c -> c.dispose())); + } + + public InetSocketAddress getClientAddress() { + return clientAddress; + } + + public InetSocketAddress[] getRemoteAddresses() { + return remoteAddres; + } + + @Override + public String toString() { + return Transport.class.getSimpleName() + "{name = " + name + ", protocol = " + protocol + ", clientAddress = " + clientAddress + ", remoteAddres = " + Arrays.toString(remoteAddres) + "}"; + } + + 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..5b17c7c40 --- /dev/null +++ b/src/org/redkale/net/WorkThread.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; + +import java.util.concurrent.*; + +/** + * + *

详情见: http://www.redkale.org + * @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/BasedHttpServlet.java b/src/org/redkale/net/http/BasedHttpServlet.java new file mode 100644 index 000000000..3e64deffb --- /dev/null +++ b/src/org/redkale/net/http/BasedHttpServlet.java @@ -0,0 +1,325 @@ +/* + * 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.util.AnyValue; +import java.io.IOException; +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public abstract class BasedHttpServlet extends HttpServlet { + + /** + * 配合 BasedHttpServlet 使用。 + * 当标记为 @AuthIgnore 的方法不会再调用之前调用authenticate 方法。 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ + @Inherited + @Documented + @Target({METHOD, TYPE}) + @Retention(RUNTIME) + protected @interface AuthIgnore { + + } + + /** + * 配合 BasedHttpServlet 使用。 + * 用于对@WebServlet对应的url进行细分。 其 url + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ + @Target({ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + protected @interface WebAction { + + int actionid() default 0; + + String url(); + } + + /** + * 配合 BasedHttpServlet 使用。 + * 当标记为 @HttpCacheable 的方法使用response.finish的参数将被缓存一定时间(默认值timeout=15秒)。 + * 通常情况下 @HttpCacheable 需要与 @AuthIgnore 一起使用,因为没有标记@AuthIgnore的方法一般输出的结果与当前用户信息有关。 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ + @Target({ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + protected @interface HttpCacheable { + + /** + * 超时的秒数 + * + * @return 超时秒数 + */ + int timeout() default 15; + } + + 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.setBufferHandler(entry.cacheHandler); + } + entry.servlet.execute(request, response); + } + return; + } + } + throw new IOException(this.getClass().getName() + " not found method for URI(" + request.getRequestURI() + ")"); + } + + public final void preInit(HttpContext context, AnyValue config) { + String path = _prefix == null ? "" : _prefix; + WebServlet ws = this.getClass().getAnnotation(WebServlet.class); + if (ws != null && !ws.repair()) 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 final void postDestroy(HttpContext context, AnyValue config) { + } + + 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; + //----------------------------------------------- + + final WebAction action = method.getAnnotation(WebAction.class); + if (action == null) continue; + final int actionid = action.actionid(); + final String 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 sub-contains " + 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.cacheHandler = 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.BufferHandler cacheHandler; + + 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/HttpContext.java b/src/org/redkale/net/http/HttpContext.java new file mode 100644 index 000000000..ce0f3dffb --- /dev/null +++ b/src/org/redkale/net/http/HttpContext.java @@ -0,0 +1,56 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class HttpContext extends Context { + + 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) { + super(serverStartTime, logger, executor, bufferCapacity, bufferPool, responsePool, maxbody, charset, + address, prepare, watch, readTimeoutSecond, writeTimeoutSecond); + + random.setSeed(Math.abs(System.nanoTime())); + } + + 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..c9507b859 --- /dev/null +++ b/src/org/redkale/net/http/HttpPrepareServlet.java @@ -0,0 +1,144 @@ +/* + * 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.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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public final class HttpPrepareServlet extends PrepareServlet { + + private SimpleEntry, HttpServlet>[] regArray = new SimpleEntry[0]; + + private HttpServlet resourceHttpServlet = new HttpResourceServlet(); + + @Override + public void init(HttpContext context, AnyValue config) { + this.servlets.forEach(s -> { + if (s instanceof WebSocketServlet) { + ((WebSocketServlet) s).preInit(context, getServletConf(s)); + } else if (s instanceof BasedHttpServlet) { + ((BasedHttpServlet) s).preInit(context, getServletConf(s)); + } + s.init(context, getServletConf(s)); + }); + final WatchFactory watch = context.getWatchFactory(); + if (watch != null) { + this.servlets.forEach(s -> { + watch.inject(s); + }); + } + if (config != null) { + AnyValue ssConfig = config.getAnyValue("servlets"); + AnyValue resConfig = null; + if (ssConfig != null) { + resConfig = ssConfig.getAnyValue("resource-servlet"); + if ((resConfig instanceof DefaultAnyValue) && 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); + } + } + + @Override + public void execute(HttpRequest request, HttpResponse response) throws IOException { + try { + final String uri = request.getRequestURI(); + Servlet servlet = this.mappings.isEmpty() ? null : this.mappings.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); + } + } + + @Override + public void addServlet(HttpServlet servlet, Object prefix, AnyValue conf, String... mappings) { + if (prefix == null) prefix = ""; + for (String mapping : mappings) { + if (!prefix.toString().isEmpty()) mapping = prefix + mapping; + if (contains(mapping, '.', '*', '{', '[', '(', '|', '^', '$', '+', '?', '\\')) { //是否是正则表达式)) + if (mapping.charAt(0) != '^') mapping = '^' + mapping; + if (mapping.endsWith("/*")) { + mapping = mapping.substring(0, mapping.length() - 1) + ".*"; + } else { + mapping = 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()) { + this.mappings.put(mapping, servlet); + } + } + setServletConf(servlet, conf); + servlet._prefix = prefix == null ? "" : prefix.toString(); + 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(HttpContext context, AnyValue config) { + this.resourceHttpServlet.destroy(context, config); + this.servlets.forEach(s -> { + s.destroy(context, getServletConf(s)); + if (s instanceof WebSocketServlet) { + ((WebSocketServlet) s).postDestroy(context, getServletConf(s)); + } else if (s instanceof BasedHttpServlet) { + ((BasedHttpServlet) s).postDestroy(context, getServletConf(s)); + } + }); + } + +} diff --git a/src/org/redkale/net/http/HttpRequest.java b/src/org/redkale/net/http/HttpRequest.java new file mode 100644 index 000000000..a8bb9c5f7 --- /dev/null +++ b/src/org/redkale/net/http/HttpRequest.java @@ -0,0 +1,813 @@ +/* + * 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.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.charset.Charset; +import org.redkale.convert.json.JsonConvert; +import org.redkale.net.*; +import org.redkale.util.AnyValue.DefaultAnyValue; +import org.redkale.util.ByteArray; + +/** + * Http请求包 与javax.servlet.http.HttpServletRequest 基本类似。 + * 同时提供json的解析接口: public Object getJsonParameter(Class clazz, String name) + * RedKale提倡带简单的参数的GET请求采用类似REST风格, 因此提供了 getRequstURIPath 系列接口。 + * 例如简单的翻页查询 /pipes/record/query/page:2/size:20 + * 获取页号: int page = request.getRequstURIPath("page:", 1); + * 获取行数: int size = request.getRequstURIPath("size:", 10); + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class HttpRequest extends Request { + + protected static final Charset UTF8 = Charset.forName("UTF-8"); + + protected static final String SESSIONID_NAME = "JSESSIONID"; + + 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 boundary = false; + + private final String remoteAddrHeader; + + public HttpRequest(HttpContext 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)) 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.size() - index, charset).trim(); + while (readLine(buffer, array)) { + if (array.size() < 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.size() - 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.write(buffer, buffer.remaining()); + if (this.contentType != null && this.contentType.contains("boundary=")) { + this.boundary = true; + } + if (this.boundary) this.keepAlive = false; //文件上传必须设置keepAlive为false,因为文件过大时用户不一定会skip掉多余的数据 + if (this.contentLength > 0 && (this.contentType == null || !this.boundary)) { + if (this.contentLength > context.getMaxbody()) return -1; + int lr = (int) this.contentLength - array.size(); + return lr > 0 ? lr : 0; + } + return 0; + } + + @Override + protected int readBody(ByteBuffer buffer) { + int len = buffer.remaining(); + array.write(buffer, len); + return len; + } + + @Override + protected void prepare() { + } + + private void parseBody() { + if (this.boundary || bodyparsed) return; + addParameter(array, 0, array.size()); + 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.write(lasted); + return false; + } + byte b = buffer.get(); + if (b == -1 || (lasted == '\r' && b == '\n')) break; + if (lasted != '\r') bytes.write(lasted); + lasted = b; + } + return true; + } + + @Override + protected T setProperty(String name, T value) { + return super.setProperty(name, value); + } + + @Override + @SuppressWarnings("unchecked") + protected T getProperty(String name) { + return super.getProperty(name); + } + + @Override + protected T removeProperty(String name) { + return super.removeProperty(name); + } + + /** + * 获取客户端地址IP + * + * @return 地址 + */ + public SocketAddress getRemoteAddress() { + return this.channel.getRemoteAddress(); + } + + /** + * 获取客户端地址IP, 与getRemoteAddres() 的区别在于:本方法优先取header中指定为RemoteAddress名的值,没有则返回getRemoteAddres()的getHostAddress()。 + * 本方法适用于服务前端有如nginx的代理服务器进行中转,通过getRemoteAddres()是获取不到客户端的真实IP。 + * + * @return 地址 + */ + 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); + } + + /** + * 获取请求内容指定的编码字符串 + * + * @param charset 编码 + * @return 内容 + */ + public String getBody(final Charset charset) { + return charset == null ? array.toString() : array.toString(charset); + } + + /** + * 获取请求内容的UTF-8编码字符串 + * + * @return 内容 + */ + public String getBodyUTF8() { + return array.toString(UTF8); + } + + @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 + "}"; + } + + /** + * 获取文件上传对象 + * + * @return 文件上传对象 + */ + public final MultiContext getMultiContext() { + return new MultiContext(context.getCharset(), this.getContentType(), this.params, + new BufferedInputStream(Channels.newInputStream(this.channel), Math.max(array.size(), 8192)) { + { + array.copyTo(this.buf); + this.count = array.size(); + } + }, null); + } + + /** + * 获取文件上传信息列表 + * + * @return 文件上传对象集合 + * @throws IOException IO异常 + */ + public final Iterable multiParts() throws IOException { + return getMultiContext().parts(); + } + + @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.header.clear(); + this.params.clear(); + this.array.clear(); + super.recycle(); + } + + /** + * 获取sessionid + * + * @param create 无sessionid是否自动创建 + * @return sessionid + */ + 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; + } + + /** + * 更新sessionid + * + * @return 新的sessionid值 + */ + public String changeSessionid() { + this.newsessionid = context.createSessionid(); + return newsessionid; + } + + /** + * 使sessionid失效 + */ + public void invalidateSession() { + this.newsessionid = ""; //为空表示删除sessionid + } + + /** + * 获取所有Cookie对象 + * + * @return cookie对象数组 + */ + public HttpCookie[] getCookies() { + if (this.cookies == null) this.cookies = parseCookies(this.cookiestr); + return this.cookies; + } + + /** + * 获取Cookie值 + * + * @param name cookie名 + * @return cookie值 + */ + public String getCookie(String name) { + return getCookie(name, null); + } + + /** + * 获取Cookie值, 没有返回默认值 + * + * @param name cookie名 + * @param dfvalue 默认cookie值 + * @return cookie值 + */ + 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; + } + + /** + * 获取协议名 http、https、ws、wss等 + * + * @return protocol + */ + public String getProtocol() { + return protocol; + } + + /** + * 获取请求方法 GET、POST等 + * + * @return method + */ + public String getMethod() { + return method; + } + + /** + * 获取Content-Type的header值 + * + * @return contentType + */ + public String getContentType() { + return contentType; + } + + /** + * 获取请求内容的长度, 为-1表示内容长度不确定 + * + * @return 内容长度 + */ + public long getContentLength() { + return contentLength; + } + + /** + * 获取Connection的Header值 + * + * @return Connection + */ + public String getConnection() { + return connection; + } + + /** + * 获取Host的Header值 + * + * @return Host + */ + public String getHost() { + return host; + } + + /** + * 获取请求的URL + * + * @return 请求的URL + */ + public String getRequestURI() { + return requestURI; + } + + /** + * 截取getRequestURI最后的一个/后面的部分 + * + * @return String + */ + public String getRequstURILastPath() { + if (requestURI == null) return ""; + return requestURI.substring(requestURI.lastIndexOf('/') + 1); + } + + /** + * + * 从prefix之后截取getRequestURI再对"/"进行分隔 + *

+ * @param prefix 前缀 + * @return String[] + */ + 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("/"); + } + + /** + * 获取请求URL分段中含prefix段的值 + * 例如请求URL /pipes/record/query/name:hello + * 获取name参数: String name = request.getRequstURIPath("name:", "none"); + * + * @param prefix prefix段前缀 + * @param defvalue 默认值 + * @return prefix截断后的值 + */ + 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); + } + + /** + * 获取请求URL分段中含prefix段的short值 + * 例如请求URL /pipes/record/query/type:10 + * 获取type参数: short type = request.getRequstURIPath("type:", (short)0); + * + * @param prefix prefix段前缀 + * @param defvalue 默认short值 + * @return short值 + */ + public short getRequstURIPath(String prefix, short defvalue) { + String val = getRequstURIPath(prefix, null); + return val == null ? defvalue : Short.parseShort(val); + } + + /** + * 获取请求URL分段中含prefix段的int值 + * 例如请求URL /pipes/record/query/page:2/size:50 + * 获取page参数: int page = request.getRequstURIPath("page:", 1); + * 获取size参数: int size = request.getRequstURIPath("size:", 20); + * + * @param prefix prefix段前缀 + * @param defvalue 默认int值 + * @return int值 + */ + public int getRequstURIPath(String prefix, int defvalue) { + String val = getRequstURIPath(prefix, null); + return val == null ? defvalue : Integer.parseInt(val); + } + + /** + * 获取请求URL分段中含prefix段的long值 + * 例如请求URL /pipes/record/query/time:1453104341363/id:40 + * 获取time参数: long time = request.getRequstURIPath("time:", 0L); + * + * @param prefix prefix段前缀 + * @param defvalue 默认long值 + * @return long值 + */ + public long getRequstURIPath(String prefix, long defvalue) { + String val = getRequstURIPath(prefix, null); + return val == null ? defvalue : Long.parseLong(val); + } + + //------------------------------------------------------------------------------ + /** + * 获取所有的header名 + * + * @return header名数组 + */ + public String[] getHeaderNames() { + return header.getNames(); + } + + /** + * 获取指定的header值 + * + * @param name header名 + * @return header值 + */ + public String getHeader(String name) { + return header.getValue(name); + } + + /** + * 获取指定的header值, 没有返回默认值 + * + * @param name header名 + * @param defaultValue 默认值 + * @return header值 + */ + public String getHeader(String name, String defaultValue) { + return header.getValue(name, defaultValue); + } + + /** + * 获取指定的header的json值 + * + * @param 泛型 + * @param clazz 反序列化的类名 + * @param name header名 + * @return header值 + */ + public T getJsonHeader(Class clazz, String name) { + String v = getHeader(name); + return v == null || v.isEmpty() ? null : jsonConvert.convertFrom(clazz, v); + } + + /** + * 获取指定的header的json值 + * + * @param 泛型 + * @param convert JsonConvert对象 + * @param clazz 反序列化的类名 + * @param name header名 + * @return header值 + */ + public T getJsonHeader(JsonConvert convert, Class clazz, String name) { + String v = getHeader(name); + return v == null || v.isEmpty() ? null : convert.convertFrom(clazz, v); + } + + /** + * 获取指定的header的boolean值, 没有返回默认boolean值 + * + * @param name header名 + * @param defaultValue 默认boolean值 + * @return header值 + */ + public boolean getBooleanHeader(String name, boolean defaultValue) { + return header.getBoolValue(name, defaultValue); + } + + /** + * 获取指定的header的short值, 没有返回默认short值 + * + * @param name header名 + * @param defaultValue 默认short值 + * @return header值 + */ + public short getShortHeader(String name, short defaultValue) { + return header.getShortValue(name, defaultValue); + } + + /** + * 获取指定的header的int值, 没有返回默认int值 + * + * @param name header名 + * @param defaultValue 默认int值 + * @return header值 + */ + public int getIntHeader(String name, int defaultValue) { + return header.getIntValue(name, defaultValue); + } + + /** + * 获取指定的header的long值, 没有返回默认long值 + * + * @param name header名 + * @param defaultValue 默认long值 + * @return header值 + */ + public long getLongHeader(String name, long defaultValue) { + return header.getLongValue(name, defaultValue); + } + + /** + * 获取指定的header的float值, 没有返回默认float值 + * + * @param name header名 + * @param defaultValue 默认float值 + * @return header值 + */ + public float getFloatHeader(String name, float defaultValue) { + return header.getFloatValue(name, defaultValue); + } + + /** + * 获取指定的header的double值, 没有返回默认double值 + * + * @param name header名 + * @param defaultValue 默认double值 + * @return header值 + */ + public double getDoubleHeader(String name, double defaultValue) { + return header.getDoubleValue(name, defaultValue); + } + + //------------------------------------------------------------------------------ + /** + * 获取所有参数名 + * + * @return 参数名数组 + */ + public String[] getParameterNames() { + parseBody(); + return params.getNames(); + } + + /** + * 获取指定的参数值 + * + * @param name 参数名 + * @return 参数值 + */ + public String getParameter(String name) { + parseBody(); + return params.getValue(name); + } + + /** + * 获取指定的参数值, 没有返回默认值 + * + * @param name 参数名 + * @param defaultValue 默认值 + * @return 参数值 + */ + public String getParameter(String name, String defaultValue) { + parseBody(); + return params.getValue(name, defaultValue); + } + + /** + * 获取指定的参数json值 + * + * @param 泛型 + * @param clazz 反序列化的类名 + * @param name 参数名 + * @return 参数值 + */ + public T getJsonParameter(Class clazz, String name) { + String v = getParameter(name); + return v == null || v.isEmpty() ? null : jsonConvert.convertFrom(clazz, v); + } + + /** + * 获取指定的参数json值 + * + * @param 泛型 + * @param convert JsonConvert对象 + * @param clazz 反序列化的类名 + * @param name 参数名 + * @return 参数值 + */ + public T getJsonParameter(JsonConvert convert, Class clazz, String name) { + String v = getParameter(name); + return v == null || v.isEmpty() ? null : convert.convertFrom(clazz, v); + } + + /** + * 获取指定的参数boolean值, 没有返回默认boolean值 + * + * @param name 参数名 + * @param defaultValue 默认boolean值 + * @return 参数值 + */ + public boolean getBooleanParameter(String name, boolean defaultValue) { + parseBody(); + return params.getBoolValue(name, defaultValue); + } + + /** + * 获取指定的参数short值, 没有返回默认short值 + * + * @param name 参数名 + * @param defaultValue 默认short值 + * @return 参数值 + */ + public short getShortParameter(String name, short defaultValue) { + parseBody(); + return params.getShortValue(name, defaultValue); + } + + /** + * 获取指定的参数int值, 没有返回默认int值 + * + * @param name 参数名 + * @param defaultValue 默认int值 + * @return 参数值 + */ + public int getIntParameter(String name, int defaultValue) { + parseBody(); + return params.getIntValue(name, defaultValue); + } + + /** + * 获取指定的参数long值, 没有返回默认long值 + * + * @param name 参数名 + * @param defaultValue 默认long值 + * @return 参数值 + */ + public long getLongParameter(String name, long defaultValue) { + parseBody(); + return params.getLongValue(name, defaultValue); + } + + /** + * 获取指定的参数float值, 没有返回默认float值 + * + * @param name 参数名 + * @param defaultValue 默认float值 + * @return 参数值 + */ + public float getFloatParameter(String name, float defaultValue) { + parseBody(); + return params.getFloatValue(name, defaultValue); + } + + /** + * 获取指定的参数double值, 没有返回默认double值 + * + * @param name 参数名 + * @param defaultValue 默认double值 + * @return 参数值 + */ + public double getDoubleParameter(String name, double defaultValue) { + parseBody(); + return params.getDoubleValue(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..61ca75178 --- /dev/null +++ b/src/org/redkale/net/http/HttpResourceServlet.java @@ -0,0 +1,262 @@ +/* + * 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.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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(HttpContext 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"), 0 * 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(HttpContext 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..8d255897f --- /dev/null +++ b/src/org/redkale/net/http/HttpResponse.java @@ -0,0 +1,739 @@ +/* + * 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.lang.reflect.Type; +import java.net.HttpCookie; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.nio.file.*; +import java.text.*; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import org.redkale.convert.json.JsonConvert; +import org.redkale.net.*; +import org.redkale.util.AnyValue.DefaultAnyValue; +import org.redkale.util.AnyValue.Entry; +import org.redkale.util.*; + +/** + * Http响应包 与javax.servlet.http.HttpServletResponse 基本类似。 + * 同时提供发送json的系列接口: public void finishJson(Type type, Object obj) + * RedKale提倡http+json的接口风格, 所以主要输出的数据格式为json, 同时提供异步接口。 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class HttpResponse extends Response { + + /** + * HttpResponse.finish 方法内调用 + * 主要给@HttpCacheable使用 + * + */ + protected static interface BufferHandler { + + public ByteBuffer[] execute(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 BufferHandler bufferHandler; + //------------------------------------------------ + + 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(HttpContext context, HttpRequest 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.bufferHandler = null; + return super.recycle(); + } + + @Override + protected void init(AsyncConnection channel) { + super.init(channel); + } + + /** + * 获取状态码对应的状态描述 + * + * @param status 状态码 + * @return 状态描述 + */ + 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; + } + + /** + * 增加Cookie值 + * + * @param cookies cookie + */ + 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; + } + } + + /** + * 将对象以JSON格式输出 + * + * @param obj 输出对象 + */ + public void finishJson(final Object obj) { + this.contentType = "text/plain; charset=utf-8"; + finish(request.getJsonConvert().convertTo(context.getBufferSupplier(), obj)); + } + + /** + * 将对象以JSON格式输出 + * + * @param convert 指定的JsonConvert + * @param obj 输出对象 + */ + public void finishJson(final JsonConvert convert, final Object obj) { + this.contentType = "text/plain; charset=utf-8"; + finish(convert.convertTo(context.getBufferSupplier(), obj)); + } + + /** + * 将对象以JSON格式输出 + * + * @param type 指定的类型 + * @param obj 输出对象 + */ + public void finishJson(final Type type, final Object obj) { + this.contentType = "text/plain; charset=utf-8"; + finish(request.getJsonConvert().convertTo(context.getBufferSupplier(), type, obj)); + } + + /** + * 将对象以JSON格式输出 + * + * @param convert 指定的JsonConvert + * @param type 指定的类型 + * @param obj 输出对象 + */ + public void finishJson(final JsonConvert convert, final Type type, final Object obj) { + this.contentType = "text/plain; charset=utf-8"; + finish(convert.convertTo(context.getBufferSupplier(), type, obj)); + } + + /** + * 将对象以JSON格式输出 + * + * @param objs 输出对象 + */ + public void finishJson(final Object... objs) { + this.contentType = "text/plain; charset=utf-8"; + finish(request.getJsonConvert().convertTo(context.getBufferSupplier(), objs)); + } + + /** + * 将指定字符串以响应结果输出 + * + * @param obj 输出内容 + */ + 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 (bufferHandler != null) { + bufferHandler.execute(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 (bufferHandler != null) { + ByteBuffer[] bufs = bufferHandler.execute(this, new ByteBuffer[]{buffer}); + if (bufs != null) buffer = bufs[0]; + } + this.contentLength = buffer.remaining(); + final ByteBuffer headbuf = createHeader(); + headbuf.flip(); + super.finish(headbuf, buffer); + } + } + + /** + * 以指定响应码附带内容输出 + * + * @param status 响应码 + * @param message 输出内容 + */ + public void finish(int status, String message) { + this.status = status; + if (status != 200) super.refuseAlive(); + finish(message); + } + + /** + * 以304状态码输出 + * + */ + public void finish304() { + super.finish(buffer304.duplicate()); + } + + /** + * 以404状态码输出 + * + */ + public void finish404() { + super.finish(buffer404.duplicate()); + } + + /** + * 将指定ByteBuffer按响应结果输出 + * + * @param buffer 输出内容 + */ + @Override + public void finish(ByteBuffer buffer) { + finish(false, buffer); + } + + /** + * 将指定ByteBuffer按响应结果输出 + * + * @param kill 输出后是否强制关闭连接 + * @param 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); + } + } + + /** + * 将指定ByteBuffer数组按响应结果输出 + * + * @param buffers 输出内容 + */ + @Override + public void finish(ByteBuffer... buffers) { + finish(false, buffers); + } + + /** + * 将指定ByteBuffer数组按响应结果输出 + * + * @param kill 输出后是否强制关闭连接 + * @param buffers 输出内容 + */ + @Override + public void finish(boolean kill, ByteBuffer... buffers) { + if (bufferHandler != null) { + ByteBuffer[] bufs = bufferHandler.execute(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); + } + } + + /** + * 异步输出指定内容 + * + * @param 泛型 + * @param buffer 输出内容 + * @param attachment 异步回调参数 + * @param handler 异步回调函数 + */ + 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); + } + } + + /** + * 将指定文件按响应结果输出 + * + * @param file 输出文件 + * @throws IOException IO异常 + */ + public void finish(File file) throws IOException { + finishFile(file, null); + } + + /** + * 将指定文件句柄或文件内容按响应结果输出,若fileBody不为null则只输出fileBody内容 + * + * @param file 输出文件 + * @param fileBody 文件内容, 没有则输出file + * @throws IOException IO异常 + */ + 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 > 3) { + String v = request.getParameter(headers[2]); + if (v != null) this.header.addValue(headers[0], v); + } else 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 > 3) { + String v = request.getParameter(headers[2]); + if (v != null) this.header.setValue(headers[0], v); + } else if (headers.length > 2) { + String v = request.getHeader(headers[2]); + if (v != null) this.header.setValue(headers[0], v); + } 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.isEmpty()) 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; + } + + /** + * 跳过header的输出 + * 通常应用场景是,调用者的输出内容里已经包含了HTTP的响应头信息,因此需要调用此方法避免重复输出HTTP响应头信息。 + * + */ + public void skipHeader() { + this.headsended = true; + } + + protected DefaultAnyValue duplicateHeader() { + return this.header.duplicate(); + } + + /** + * 设置Header值 + * + * @param name header名 + * @param value header值 + */ + public void setHeader(String name, Object value) { + this.header.setValue(name, String.valueOf(value)); + } + + /** + * 添加Header值 + * + * @param name header名 + * @param value header值 + */ + public void addHeader(String name, Object value) { + this.header.addValue(name, String.valueOf(value)); + } + + /** + * 设置状态码 + * + * @param status 状态码 + */ + public void setStatus(int status) { + this.status = status; + } + + /** + * 获取状态码 + * + * @return 状态码 + */ + public int getStatus() { + return this.status; + } + + /** + * 获取 ContentType + * + * @return ContentType + */ + public String getContentType() { + return contentType; + } + + /** + * 设置 ContentType + * + * @param contentType ContentType + */ + public void setContentType(String contentType) { + this.contentType = contentType; + } + + /** + * 获取内容长度 + * + * @return 内容长度 + */ + public long getContentLength() { + return contentLength; + } + + /** + * 设置内容长度 + * + * @param contentLength 内容长度 + */ + public void setContentLength(long contentLength) { + this.contentLength = contentLength; + } + + /** + * 获取输出时的拦截器 + * + * @return 拦截器 + */ + protected BufferHandler getBufferHandler() { + return bufferHandler; + } + + /** + * 设置输出时的拦截器 + * + * @param bufferHandler 拦截器 + */ + protected void setBufferHandler(BufferHandler bufferHandler) { + this.bufferHandler = bufferHandler; + } + + 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..f1d817d66 --- /dev/null +++ b/src/org/redkale/net/http/HttpServer.java @@ -0,0 +1,131 @@ +/* + * 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.HttpCookie; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import org.redkale.net.*; +import org.redkale.util.*; +import org.redkale.watch.WatchFactory; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public final class HttpServer extends Server { + + 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"); + } + + public void addHttpServlet(HttpServlet servlet, final String prefix, AnyValue conf, String... mappings) { + this.prepare.addServlet(servlet, prefix, conf, mappings); + } + + @Override + @SuppressWarnings("unchecked") + protected HttpContext 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.bufferCapacity, 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.parameters.")) { + defaultAddHeaders.add(new String[]{addHeaders[i].getValue("name"), val, val.substring("request.parameters.".length()), null}); + } else 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.parameters.")) { + defaultSetHeaders.add(new String[]{setHeaders[i].getValue("name"), val, val.substring("request.parameters.".length()), null}); + } else 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); + 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..e8fe6bf94 --- /dev/null +++ b/src/org/redkale/net/http/HttpServlet.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.http; + +import org.redkale.net.Servlet; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public abstract class HttpServlet extends Servlet { + + String _prefix = ""; //当前HttpServlet的path前缀 + + @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..13d1fbe0a --- /dev/null +++ b/src/org/redkale/net/http/MimeType.java @@ -0,0 +1,199 @@ +/* + * 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.*; + +/** + * + *

详情见: http://www.redkale.org + * @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"); + } + + public static String get(String extension) { + return contentTypes.getOrDefault(extension, "text/plain"); + } + + public static String get(String extension, String defaultCt) { + return contentTypes.getOrDefault(extension, defaultCt); + } + + public static boolean contains(String extension) { + return contentTypes.containsKey(extension); + } + + public static void add(String extension, String contentType) { + if (extension != null && extension.length() != 0 && contentType != null && contentType.length() != 0) { + contentTypes.put(extension, contentType); + } + } + + public static String getByFilename(String fileName) { + int length = fileName.length(); + int newEnd = fileName.lastIndexOf('#'); + if (newEnd == -1) newEnd = length; + int i = fileName.lastIndexOf('.', newEnd); + return (i < 0) ? null : get(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..b3fcccb44 --- /dev/null +++ b/src/org/redkale/net/http/MultiContext.java @@ -0,0 +1,245 @@ +/* + * 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.*; +import org.redkale.util.AnyValue.DefaultAnyValue; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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 DefaultAnyValue parameters; + + 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 Charset charsetName, final String contentType, final DefaultAnyValue params, final InputStream in, String fielnameRegex) { + this.charset = charsetName == null ? UTF8 : charsetName; + this.contentType = contentType.trim(); + this.parameters = params; + 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); + } + + 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 parts() throws IOException { + if (!isMultipart()) return emptyIterable; + 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 DefaultAnyValue 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.addValue(parseValue(disposition, "name"), readLine()); + this.boundaryline = null; + this.lastentry = null; + return this.hasNext(); + } + } catch (IOException ex) { + logger.log(Level.FINER, "list multiparts 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.write(lasted); + lasted = (byte) b; + if (bd && bc == c) { + buf.write(lasted); + if (buf.equal(this.endboundarray)) break; + buf.removeLastByte(); + } + } + if (buf.size() == 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..8d7ff886e --- /dev/null +++ b/src/org/redkale/net/http/MultiPart.java @@ -0,0 +1,116 @@ +/* + * 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; + +/** + * + *

详情见: http://www.redkale.org + * @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/WebInitParam.java b/src/org/redkale/net/http/WebInitParam.java new file mode 100644 index 000000000..fdda0b5c2 --- /dev/null +++ b/src/org/redkale/net/http/WebInitParam.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.net.http; + +import java.lang.annotation.*; + +/** + * 功能同JSR 315 (java-servlet 3.0) 规范中的 @WebInitParam + * + *

详情见: http://www.redkale.org + * @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..c0cb0acc2 --- /dev/null +++ b/src/org/redkale/net/http/WebServlet.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.http; + +import java.lang.annotation.*; + +/** + * 功能同JSR 315 (java-servlet 3.0) 规范中的 @WebServlet + * + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface WebServlet { + + String name() default ""; + + boolean repair() 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..c984f9abb --- /dev/null +++ b/src/org/redkale/net/http/WebSocket.java @@ -0,0 +1,453 @@ +/* + * 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.net.*; +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的关闭。
+ *
+ *  此模式下 以上方法都应该被重载。
+ * 
+ *

+ * 详情见: http://www.redkale.org + * + * @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; //不可能为空 + + Serializable _sessionid; //不可能为空 + + Serializable _groupid; //不可能为空 + + SocketAddress _remoteAddress;//不可能为空 + + String _remoteAddr;//不可能为空 + + private final long createtime = System.currentTimeMillis(); + + private final Map attributes = new ConcurrentHashMap<>(); + + protected WebSocket() { + } + + //---------------------------------------------------------------- + /** + * 发送消息体, 包含二进制/文本 + * + * @param packet WebSocketPacket + * @return 0表示成功, 非0表示错误码 + */ + public final int send(WebSocketPacket packet) { + int rs = RETCODE_WSOCKET_CLOSED; + if (this._runner != null) rs = this._runner.sendMessage(packet); + if (_engine.finest) _engine.logger.finest("wsgroupid:" + getGroupid() + " send websocket result is " + rs + " on " + this + " by message(" + packet + ")"); + return rs; + } + + /** + * 发送单一的文本消息 + * + * @param text 不可为空 + * @return 0表示成功, 非0表示错误码 + */ + public final int send(String text) { + return send(text, true); + } + + /** + * 发送文本消息 + * + * @param text 不可为空 + * @param last 是否最后一条 + * @return 0表示成功, 非0表示错误码 + */ + public final int send(String text, boolean last) { + return send(new WebSocketPacket(text, last)); + } + + public final int sendPing() { + //if (_engine.finest) _engine.logger.finest(this + " on "+_engine.getEngineid()+" ping..."); + return send(WebSocketPacket.DEFAULT_PING_PACKET); + } + + 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 byte[] + * @return 0表示成功, 非0表示错误码 + */ + public final int send(byte[] data) { + return send(data, true); + } + + /** + * 发送二进制消息 + * + * @param data 不可为空 + * @param last 是否最后一条 + * @return 0表示成功, 非0表示错误码 + */ + public final int send(byte[] data, boolean last) { + return send(new WebSocketPacket(data, last)); + } + + /** + * 发送消息, 消息类型是String或byte[] + * + * @param message 不可为空, 只能是String或者byte[] + * @param last 是否最后一条 + * @return 0表示成功, 非0表示错误码 + */ + public final int send(Serializable message, boolean last) { + return send(new WebSocketPacket(message, last)); + } + + //---------------------------------------------------------------- + /** + * 给指定groupid的WebSocketGroup下所有WebSocket节点发送文本消息 + * + * @param groupid groupid + * @param text 不可为空 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendEachMessage(Serializable groupid, String text) { + return sendEachMessage(groupid, text, true); + } + + /** + * 给指定groupid的WebSocketGroup下所有WebSocket节点发送二进制消息 + * + * @param groupid 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 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 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 groupid + * @param text 不可为空 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendRecentMessage(Serializable groupid, String text) { + return sendRecentMessage(groupid, text, true); + } + + /** + * 给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送二进制消息 + * + * @param groupid groupid + * @param data 不可为空 + * @return 为0表示成功, 其他值表示异常 + */ + public final int sendRecentMessage(Serializable groupid, byte[] data) { + return sendRecentMessage(groupid, data, true); + } + + /** + * 给指定groupid的WebSocketGroup下最近接入的WebSocket节点发送文本消息 + * + * @param groupid 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 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 (_engine.node == null) return RETCODE_NODESERVICE_NULL; + int rs = _engine.node.sendMessage(groupid, recent, text, last); + if (_engine.finest) _engine.logger.finest("wsgroupid:" + groupid + " " + (recent ? "recent " : "") + "send websocket result is " + rs + " on " + this + " by message(" + text + ")"); + return rs; + } + + private int sendMessage(Serializable groupid, boolean recent, byte[] data, boolean last) { + if (_engine.node == null) return RETCODE_NODESERVICE_NULL; + int rs = _engine.node.sendMessage(groupid, recent, data, last); + if (_engine.finest) _engine.logger.finest("wsgroupid:" + groupid + " " + (recent ? "recent " : "") + "send websocket result is " + rs + " on " + this + " by message(byte[" + data.length + "])"); + return rs; + } + + /** + * 获取在线用户的节点地址列表 + * + * @param groupid groupid + * @return 地址列表 + */ + protected final Collection getOnlineNodes(Serializable groupid) { + return _engine.node.getOnlineNodes(groupid); + } + + /** + * 获取在线用户的详细连接信息 + * + * @param groupid groupid + * @return 地址集合 + */ + protected final Map> getOnlineRemoteAddress(Serializable groupid) { + return _engine.node.getOnlineRemoteAddress(groupid); + } + + /** + * 获取当前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 groupid + */ + public final Serializable getGroupid() { + return _groupid; + } + + /** + * 获取当前WebSocket的会话ID, 不会为null + * + * @return sessionid + */ + public final Serializable getSessionid() { + return _sessionid; + } + + /** + * 获取客户端直接地址, 当WebSocket连接是由代理服务器转发的,则该值固定为代理服务器的IP地址 + * + * @return SocketAddress + */ + public final SocketAddress getRemoteAddress() { + return _remoteAddress; + } + + /** + * 获取客户端真实地址 同 HttpRequest.getRemoteAddr() + * + * @return String + */ + public final String getRemoteAddr() { + return _remoteAddr; + } + + //------------------------------------------------------------------- + /** + * 获取当前WebSocket所属的WebSocketGroup, 不会为null + * + * @return WebSocketGroup + */ + protected final WebSocketGroup getWebSocketGroup() { + return _group; + } + + /** + * 获取指定groupid的WebSocketGroup, 没有返回null + * + * @param groupid groupid + * @return WebSocketGroup + */ + protected final WebSocketGroup getWebSocketGroup(Serializable groupid) { + return _engine.getWebSocketGroup(groupid); + } + + /** + * 获取当前进程节点所有在线的WebSocketGroup + * + * @return WebSocketGroup列表 + */ + protected final Collection getWebSocketGroups() { + return _engine.getWebSocketGroups(); + } + + //------------------------------------------------------------------- + /** + * 返回sessionid, null表示连接不合法或异常,默认实现是request.getSessionid(false),通常需要重写该方法 + * + * @param request HttpRequest + * @return sessionid + */ + public Serializable onOpen(final HttpRequest request) { + return request.getSessionid(false); + } + + /** + * 创建groupid, null表示异常, 必须实现该方法, 通常为用户ID为groupid + * + * @return groupid + */ + protected abstract Serializable createGroupid(); + + /** + * 标记为WebSocketBinary才需要重写此方法 + * + * @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) { + } + + /** + * 显式地关闭WebSocket + */ + public final void close() { + if (this._runner != null) this._runner.closeRunner(); + } + + @Override + public String toString() { + return "ws" + Objects.hashCode(this) + "@" + _remoteAddr; + } +} diff --git a/src/org/redkale/net/http/WebSocketBinary.java b/src/org/redkale/net/http/WebSocketBinary.java new file mode 100644 index 000000000..30590f2b4 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketBinary.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.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 被标记为 @WebSocketBinary 的WebSocketServlet 将使用原始的TCP传输, 通常用于类似音频/视频传输场景 + * + *

详情见: http://www.redkale.org + * @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..0ea5b28e7 --- /dev/null +++ b/src/org/redkale/net/http/WebSocketEngine.java @@ -0,0 +1,101 @@ +/* + * 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.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.*; + +/** + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +public final class WebSocketEngine { + + private static final AtomicInteger sequence = new AtomicInteger(); + + private final int index; + + private final String engineid; + + protected final WebSocketNode node; + + private final Map containers = new ConcurrentHashMap<>(); + + private ScheduledThreadPoolExecutor scheduler; + + protected final Logger logger; + + protected final boolean finest; + + protected WebSocketEngine(String engineid, WebSocketNode node, Logger logger) { + this.engineid = engineid; + this.node = node; + 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.sendEachPing()); + }, 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.putIfAbsent(socket._groupid, group); + } + group.add(socket); + if (node != null) node.connect(socket._groupid, engineid, socket.toString()); + } + + void remove(WebSocket socket) { + final WebSocketGroup group = containers.get(socket._groupid); + if (group == null) { + if (node != null) node.disconnect(socket._groupid, engineid); + return; + } + group.remove(socket); + if (group.isEmpty()) { + containers.remove(socket._groupid); + if (node != null) node.disconnect(socket._groupid, engineid); + } + } + + 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..74cafc85a --- /dev/null +++ b/src/org/redkale/net/http/WebSocketGroup.java @@ -0,0 +1,139 @@ +/* + * 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; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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 WebSocket + */ + 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 sendEachPing() { + int rs = 0; + for (WebSocket s : list) { + rs |= s.sendPing(); + } + 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..5c614151f --- /dev/null +++ b/src/org/redkale/net/http/WebSocketNode.java @@ -0,0 +1,235 @@ +/* + * 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.boot.*; +import org.redkale.service.*; +import org.redkale.source.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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 = Application.RESNAME_SERVER_ADDR) + protected InetSocketAddress localSncpAddress; //为SncpServer的服务address + + @DynRemote + protected WebSocketNode remoteNode; + + //存放所有用户分布在节点上的队列信息,Set 为 sncpnode 的集合 + @Resource(name = "$") + protected CacheSource source; + + //存放本地节点上所有在线用户的队列信息,Set 为 engineid 的集合 + protected final ConcurrentHashMap> localNodes = new ConcurrentHashMap(); + + protected final ConcurrentHashMap engines = new ConcurrentHashMap(); + + public void init(AnyValue conf) { + + } + + 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); + }); + }); + } + + protected abstract List getOnlineRemoteAddresses(@DynTargetAddress InetSocketAddress targetAddress, Serializable groupid); + + protected abstract int sendMessage(@DynTargetAddress InetSocketAddress targetAddress, Serializable groupid, boolean recent, Serializable message, boolean last); + + protected abstract void connect(Serializable groupid, InetSocketAddress addr); + + protected abstract void disconnect(Serializable groupid, InetSocketAddress addr); + + //-------------------------------------------------------------------------------- + protected List remoteOnlineRemoteAddresses(@DynTargetAddress InetSocketAddress targetAddress, Serializable groupid) { + if (remoteNode == null) return null; + try { + return remoteNode.getOnlineRemoteAddresses(targetAddress, groupid); + } catch (Exception e) { + logger.log(Level.WARNING, "remote " + targetAddress + " websocket getOnlineRemoteAddresses error", e); + return null; + } + } + + /** + * 获取在线用户的节点地址列表 + * + * @param groupid groupid + * @return 地址列表 + */ + public Collection getOnlineNodes(final Serializable groupid) { + return source.getCollection(groupid); + } + + /** + * 获取在线用户的详细连接信息 + * + * @param groupid groupid + * @return 地址集合 + */ + public Map> getOnlineRemoteAddress(final Serializable groupid) { + Collection nodes = getOnlineNodes(groupid); + if (nodes == null) return null; + final Map> map = new HashMap(); + for (InetSocketAddress nodeAddress : nodes) { + List list = getOnlineRemoteAddresses(nodeAddress, groupid); + if (list == null) list = new ArrayList(); + map.put(nodeAddress, list); + } + return map; + } + + public final void connect(Serializable groupid, String engineid, String wsinfo) { + if (finest) logger.finest(localSncpAddress + " receive websocket connect event (" + groupid + " on " + engineid + ")."); + Set engineids = localNodes.get(groupid); + if (engineids == null) { + engineids = new CopyOnWriteArraySet<>(); + localNodes.putIfAbsent(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); + } + } + + 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); + if (finest) logger.finest("websocket want send message {groupid:" + groupid + ", content:'" + message + "'} from locale node to " + engineids); + 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()) { + engineids.remove(engineid); + if (finest) logger.finest("websocket want send message {engineid:'" + engineid + "', groupid:" + groupid + ", content:'" + message + "'} but websocket group is empty "); + rscode = RETCODE_GROUP_EMPTY; + break; + } + rscode = group.send(recent, message, last); + } + } + } + if ((recent && rscode == 0) || remoteNode == null) { + if (finest) { + if ((recent && rscode == 0)) { + logger.finest("websocket want send recent message success"); + } else { + logger.finest("websocket remote node is null"); + } + } + return rscode; + } + //-----------------------发送远程的----------------------------- + Collection addrs = source.getCollection(groupid); + if (finest) logger.finest("websocket found groupid:" + groupid + " on " + addrs); + if (addrs != null && !addrs.isEmpty()) { //对方连接在远程节点(包含本地节点),所以正常情况下addrs不会为空。 + 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..e5fd02622 --- /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.*; + +/** + * + *

详情见: http://www.redkale.org + * @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=[" + bytes.length + ']') : "") + "]"; + } +} diff --git a/src/org/redkale/net/http/WebSocketRunner.java b/src/org/redkale/net/http/WebSocketRunner.java new file mode 100644 index 000000000..362ae707d --- /dev/null +++ b/src/org/redkale/net/http/WebSocketRunner.java @@ -0,0 +1,526 @@ +/* + * 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.*; + +/** + * + *

详情见: http://www.redkale.org + * @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 { + 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); + 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..aace0cfee --- /dev/null +++ b/src/org/redkale/net/http/WebSocketServlet.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.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.util.*; + +/** + *

+ * 当WebSocketServlet接收一个TCP连接后,进行协议判断,如果成功就会创建一个WebSocket。
+ *
+ *                                    WebSocketServlet
+ *                                            |
+ *                                            |
+ *                                    WebSocketEngine
+ *                                    /             \
+ *                                 /                  \
+ *                              /                       \
+ *                     WebSocketGroup1            WebSocketGroup2
+ *                        /        \                /        \
+ *                      /           \             /           \
+ *               WebSocket1     WebSocket2   WebSocket3    WebSocket4
+ *
+ * 
+ * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public abstract class WebSocketServlet extends HttpServlet { + + 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; + + public final void preInit(HttpContext context, AnyValue conf) { + InetSocketAddress addr = context.getServerAddress(); + this.engine = new WebSocketEngine(addr.getHostString() + ":" + addr.getPort() + "-[" + name() + "]", this.node, logger); + this.node.putWebSocketEngine(engine); + this.node.init(conf); + this.engine.init(conf); + } + + public final void postDestroy(HttpContext context, AnyValue conf) { + this.node.destroy(conf); + super.destroy(context, conf); + engine.close(); + } + + 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._remoteAddress = request.getRemoteAddress(); + webSocket._remoteAddr = request.getRemoteAddr(); + 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/http/package-info.java b/src/org/redkale/net/http/package-info.java new file mode 100644 index 000000000..3d749a21f --- /dev/null +++ b/src/org/redkale/net/http/package-info.java @@ -0,0 +1,4 @@ +/** + * HTTP协议包,提供HTTP协议服务器 + */ +package org.redkale.net.http; diff --git a/src/org/redkale/net/package-info.java b/src/org/redkale/net/package-info.java new file mode 100644 index 000000000..5f10d5ca0 --- /dev/null +++ b/src/org/redkale/net/package-info.java @@ -0,0 +1,4 @@ +/** + * 网络TCP/UDP基础服务包 + */ +package org.redkale.net; diff --git a/src/org/redkale/net/sncp/ServiceWrapper.java b/src/org/redkale/net/sncp/ServiceWrapper.java new file mode 100644 index 000000000..072666f84 --- /dev/null +++ b/src/org/redkale/net/sncp/ServiceWrapper.java @@ -0,0 +1,143 @@ +/* + * 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 java.util.stream.*; +import org.redkale.util.*; + +/** + * Service对象的封装类 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Service的子类 + */ +public final class ServiceWrapper implements Comparable { + + private static volatile int maxClassNameLength = 0; + + private static volatile int maxNameLength = 0; + + private final T service; + + private final AnyValue conf; + + private final String sncpGroup; //自身的组节点名 可能为null + + private final Set groups; //所有的组节点,包含自身 + + private final String name; + + private final boolean remote; + + private final Class[] types; + + @SuppressWarnings("unchecked") + public ServiceWrapper(Class type, T service, String name, String sncpGroup, Set groups, AnyValue conf) { + this.service = service; + this.conf = conf; + this.sncpGroup = sncpGroup; + this.groups = groups; + this.name = name; + this.remote = Sncp.isRemote(service); + ResourceType rty = service.getClass().getAnnotation(ResourceType.class); + this.types = rty == null ? new Class[]{type == null ? (Class) service.getClass() : type} : rty.value(); + + maxNameLength = Math.max(maxNameLength, name.length()); + StringBuilder s = new StringBuilder(); + if (this.types.length == 1) { + s.append(types[0].getName()); + } else { + s.append('['); + s.append(Arrays.asList(this.types).stream().map((Class t) -> t.getName()).collect(Collectors.joining(","))); + s.append(']'); + } + maxClassNameLength = Math.max(maxClassNameLength, s.length() + 1); + } + + public String toSimpleString() { + StringBuilder sb = new StringBuilder(); + sb.append(remote ? "RemoteService" : "LocalService "); + int len; + if (types.length == 1) { + sb.append("(type= ").append(types[0].getName()); + len = maxClassNameLength - types[0].getName().length(); + } else { + StringBuilder s = new StringBuilder(); + s.append('['); + s.append(Arrays.asList(this.types).stream().map((Class t) -> t.getName()).collect(Collectors.joining(","))); + s.append(']'); + sb.append("(types=").append(s); + len = maxClassNameLength - s.length(); + } + + for (int i = 0; i < len; i++) { + sb.append(' '); + } + sb.append(", name='").append(name).append("'"); + for (int i = 0; i < maxNameLength - name.length(); i++) { + sb.append(' '); + } + sb.append(")"); + return sb.toString(); + } + + @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.types[0].equals(other.types[0]) && this.remote == other.remote && this.name.equals(other.name) && Objects.equals(this.sncpGroup, other.sncpGroup)); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 67 * hash + Objects.hashCode(this.types[0]); + hash = 67 * hash + Objects.hashCode(this.sncpGroup); + hash = 67 * hash + Objects.hashCode(this.name); + hash = 67 * hash + (this.remote ? 1 : 0); + return hash; + } + + @Override + public int compareTo(ServiceWrapper o) { + int rs = this.types[0].getName().compareTo(o.types[0].getName()); + if (rs == 0) rs = this.name.compareTo(o.name); + return rs; + } + + public Class[] getTypes() { + return types; + } + + public Service getService() { + return service; + } + + public AnyValue getConf() { + return conf; + } + + public String getName() { + return 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..27b50a267 --- /dev/null +++ b/src/org/redkale/net/sncp/Sncp.java @@ -0,0 +1,1102 @@ +/* + * 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.security.*; +import java.util.*; +import java.util.function.*; +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.*; +import org.redkale.service.DynRemote; + +/** + * Service Node Communicate Protocol + * 生成Service的本地模式或远程模式Service-Class的工具类 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public abstract class Sncp { + + static final String LOCALPREFIX = "_DynLocal"; + + static final String REMOTEPREFIX = "_DynRemote"; + + private static final MessageDigest md5; + + static { //64进制 + MessageDigest d = null; + try { + d = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + } + md5 = d; + } + + 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 DLong hash(final java.lang.reflect.Method method) { + if (method == null) return DLong.ZERO; + StringBuilder sb = new StringBuilder(); //不能使用method.toString() 因为包含declaringClass信息导致接口与实现类的方法hash不一致 + sb.append(method.getReturnType().getName()).append(' '); + sb.append(method.getName()); + sb.append('('); + boolean first = true; + for (Class pt : method.getParameterTypes()) { + if (!first) sb.append(','); + sb.append(pt.getName()); + first = false; + } + sb.append(')'); + return hash(sb.toString()); + } + + /** + * 对类名或者name字符串进行hash。 + * + * @param name String + * @return hash值 + */ + public static DLong hash(final String name) { + if (name == null || name.isEmpty()) return DLong.ZERO; + byte[] bytes = name.trim().getBytes(); + synchronized (md5) { + bytes = md5.digest(bytes); + } + return DLong.create(bytes); + } + + public static boolean isRemote(Service service) { + SncpDyn dyn = service.getClass().getAnnotation(SncpDyn.class); + return dyn != null && dyn.remote(); + } + + /** + *

+     * public class TestService implements Service{
+     *
+     *      public String findSomeThing(){
+     *          return "hello";
+     *      }
+     *
+     *      @MultiRun(selfrun = false)
+     *      public void createSomeThing(TestBean bean){
+     *          //do something
+     *      }
+     *
+     *      @MultiRun
+     *      public String updateSomeThing(String id){
+     *          return "hello" + id;
+     *      }
+     * }
+     * 
+ * + *
+     * @Resource(name = "")
+     * @SncpDyn(remote = false)
+     * @ResourceType({TestService.class})
+     * public final class _DynLocalTestService extends TestService{
+     *
+     *      @Resource
+     *      private BsonConvert _convert;
+     *
+     *      private Transport _sameGroupTransport;
+     *
+     *      private Transport[] _diffGroupTransports;
+     *
+     *      private SncpClient _client;
+     *
+     *      private String _selfstring;
+     *
+     *      @Override
+     *      public String toString() {
+     *          return _selfstring == null ? super.toString() : _selfstring;
+     *      }
+     *
+     *      @Override
+     *      public void createSomeThing(TestBean bean){
+     *          this._createSomeThing(false, true, true, bean);
+     *      }
+     *
+     *      @SncpDyn(remote = false, index = 0)
+     *      public void _createSomeThing(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, TestBean bean){
+     *          if(selfrunnable) super.createSomeThing(bean);
+     *          if (_client== null) return;
+     *          if (samerunnable) _client.remoteSameGroup(_convert, _sameGroupTransport, 0, true, false, false, bean);
+     *          if (diffrunnable) _client.remoteDiffGroup(_convert, _diffGroupTransports, 0, true, true, false, bean);
+     *      }
+     *
+     *      @Override
+     *      public String updateSomeThing(String id){
+     *          return this._updateSomeThing(true, true, true, id);
+     *      }
+     *
+     *      @SncpDyn(remote = false, index = 1)
+     *      public String _updateSomeThing(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, String id){
+     *          String rs = super.updateSomeThing(id);
+     *          if (_client== null) return;
+     *          if (samerunnable) _client.remoteSameGroup(_convert, _sameGroupTransport, 1, true, false, false, id);
+     *          if (diffrunnable) _client.remoteDiffGroup(_convert, _diffGroupTransports, 1, true, true, false, id);
+     *          return rs;
+     *      }
+     * }
+     * 
+ * + * 创建Service的本地模式Class + * + * @param Service子类 + * @param name 资源名 + * @param serviceClass Service类 + * @return Service实例 + */ + @SuppressWarnings("unchecked") + protected 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); + 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 transportDesc = Type.getDescriptor(Transport.class); + final String transportsDesc = Type.getDescriptor(Transport[].class); + ClassLoader loader = Sncp.class.getClassLoader(); + String newDynName = supDynName.substring(0, supDynName.lastIndexOf('/') + 1) + LOCALPREFIX + serviceClass.getSimpleName(); + if (!name.isEmpty()) { + boolean normal = true; + for (char ch : name.toCharArray()) { + if (!((ch >= '0' && ch <= '9') || ch == '_' || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) normal = false; + } + newDynName += "_" + (normal ? name : hash(name)); + } + try { + return (Class) Class.forName(newDynName.replace('/', '.')); + } catch (Exception ex) { + } + //------------------------------------------------------------------------------ + ClassWriter cw = new ClassWriter(COMPUTE_FRAMES); + FieldVisitor fv; + AsmMethodVisitor mv; + AnnotationVisitor av0; + + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, supDynName, null); + { + av0 = cw.visitAnnotation("Ljavax/annotation/Resource;", true); + av0.visit("name", name); + av0.visitEnd(); + } + { + av0 = cw.visitAnnotation(sncpDynDesc, true); + av0.visit("remote", Boolean.FALSE); + av0.visitEnd(); + } + { + av0 = cw.visitAnnotation(Type.getDescriptor(ResourceType.class), true); + { + AnnotationVisitor av1 = av0.visitArray("value"); + ResourceType rty = serviceClass.getAnnotation(ResourceType.class); + if (rty == null) { + av1.visit(null, Type.getType(Type.getDescriptor(serviceClass))); + } else { + for (Class cl : rty.value()) { + av1.visit(null, Type.getType(Type.getDescriptor(cl))); + } + } + av1.visitEnd(); + } + 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, "_sameGroupTransport", transportDesc, 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 AsmMethodVisitor(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(); + } + { // toString() + mv = new AsmMethodVisitor(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 AsmMethodVisitor(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]) { + if (ann instanceof SncpDyn || ann instanceof MultiRun) continue; //必须过滤掉 MultiRun、SncpDyn,否则生成远程模式Service时会出错 + 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 AsmMethodVisitor(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]) { + if (ann instanceof SncpDyn || ann instanceof MultiRun) continue; //必须过滤掉 MultiRun、SncpDyn,否则生成远程模式Service时会出错 + visitAnnotation(mv.visitParameterAnnotation(k, Type.getDescriptor(ann.annotationType()), true), ann); + } + } + } + av0 = mv.visitAnnotation(sncpDynDesc, true); + av0.visit("remote", Boolean.FALSE); + av0.visit("index", index); + av0.visitEnd(); + //---------------------------- 调用selfrun --------------------------------- + Label selfLabel = new Label(); + if (returnType == void.class) { // if + mv.visitVarInsn(ILOAD, 1); + mv.visitJumpInsn(IFEQ, selfLabel); + } + mv.visitVarInsn(ALOAD, 0); + int varindex = 3; //空3给selfrunnable、samerunnable、diffrunnable + 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(selfLabel); + } + 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 clientLabel = new Label(); + mv.visitJumpInsn(IFNONNULL, clientLabel); + 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(clientLabel); + //---------------------------- 调用samerun --------------------------------- + mv.visitVarInsn(ILOAD, 2); //读取 samerunnable + Label sameLabel = new Label(); + mv.visitJumpInsn(IFEQ, sameLabel); //判断 samerunnable + + 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); //传递 _sameGroupTransport + mv.visitFieldInsn(GETFIELD, newDynName, "_sameGroupTransport", transportDesc); + + 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); //第一个参数 selfrunnable + 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); //第一个参数 samerunnable + 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); //第二个参数 diffrunnable + 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() ? "asyncRemoteSameGroup" : "remoteSameGroup", "(" + convertDesc + transportDesc + "I[Ljava/lang/Object;)V", false); + mv.visitLabel(sameLabel); + //---------------------------- 调用diffrun --------------------------------- + mv.visitVarInsn(ILOAD, 3); //读取 diffrunnable + Label diffLabel = new Label(); + mv.visitJumpInsn(IFEQ, diffLabel); //判断 diffrunnable + + 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); + + 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); //第一个参数 samerunnable + 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); //第二个参数 diffrunnable + 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); //第二个参数 diffrunnable + 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() ? "asyncRemoteDiffGroup" : "remoteDiffGroup", "(" + convertDesc + transportsDesc + "I[Ljava/lang/Object;)V", false); + mv.visitLabel(diffLabel); + + 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 Service泛型 + * @param name 资源名 + * @param executor 线程池 + * @param resourceFactory 资源容器 + * @param serviceClass Service类 + * @param clientAddress 本地IP地址 + * @param sameGroupTransport 同组的通信组件 + * @param diffGroupTransports 异组的通信组件列表 + * @return Service的本地模式实例 + */ + @SuppressWarnings("unchecked") + public static T createLocalService(final String name, final Consumer executor, final ResourceFactory resourceFactory, + final Class serviceClass, final InetSocketAddress clientAddress, final Transport sameGroupTransport, final Collection diffGroupTransports) { + try { + final Class newClazz = createLocalServiceClass(name, serviceClass); + T rs = (T) newClazz.newInstance(); + //-------------------------------------- + Service remoteService = null; + Transport remoteTransport = null; + { + Class loop = newClazz; + do { + for (Field field : loop.getDeclaredFields()) { + int mod = field.getModifiers(); + if (Modifier.isFinal(mod) || Modifier.isStatic(mod)) continue; + if (field.getAnnotation(DynRemote.class) == null) continue; + if (!field.getType().isAssignableFrom(newClazz)) continue; + field.setAccessible(true); + if (remoteTransport == null) { + List list = new ArrayList<>(); + if (sameGroupTransport != null) list.add(sameGroupTransport); + if (diffGroupTransports != null) list.addAll(diffGroupTransports); + if (!list.isEmpty()) { + Transport tmp = new Transport(list); + synchronized (resourceFactory) { + Transport old = resourceFactory.find(tmp.getName(), Transport.class); + if (old != null) { + remoteTransport = old; + } else { + remoteTransport = tmp; + resourceFactory.register(tmp.getName(), tmp); + } + } + } + } + if (remoteService == null && remoteTransport != null) { + remoteService = createRemoteService(name, executor, serviceClass, clientAddress, remoteTransport); + } + if (remoteService != null) field.set(rs, remoteService); + } + } while ((loop = loop.getSuperclass()) != Object.class); + } + SncpClient client = null; + { + try { + Field e = newClazz.getDeclaredField("_client"); + e.setAccessible(true); + client = new SncpClient(name, serviceClass, executor, false, newClazz, clientAddress); + e.set(rs, client); + } catch (NoSuchFieldException ne) { + } + } + { + StringBuilder sb = new StringBuilder(); + sb.append(newClazz.getName()).append("{name = '").append(name).append("'"); + if (client != null) { + sb.append(", serviceid = ").append(client.getServiceid()); + sb.append(", action.size = ").append(client.getActionCount()); + List groups = new ArrayList<>(); + if (sameGroupTransport != null) groups.add(sameGroupTransport.getName()); + if (diffGroupTransports != null) { + for (Transport t : diffGroupTransports) { + groups.add(t.getName()); + } + } + sb.append(", address = ").append(clientAddress).append(", groups = ").append(groups); + sb.append(", sameaddrs = ").append(sameGroupTransport == null ? null : Arrays.asList(sameGroupTransport.getRemoteAddresses())); + + List addrs = new ArrayList<>(); + if (diffGroupTransports != null) { + for (Transport t : diffGroupTransports) { + addrs.addAll(Arrays.asList(t.getRemoteAddresses())); + } + } + sb.append(", diffaddrs = ").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("_sameGroupTransport"); + c.setAccessible(true); + c.set(rs, sameGroupTransport); + } + if (diffGroupTransports != null) { + 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); + } + + } + + /** + *
+     * @Resource(name = "")
+     * @SncpDyn(remote = true)
+     * @ResourceType({TestService.class})
+     * public final class _DynRemoteTestService extends TestService{
+     *
+     *      @Resource
+     *      private BsonConvert _convert;
+     *
+     *      private Transport _transport;
+     *
+     *      private SncpClient _client;
+     *
+     *      private String _selfstring;
+     *
+     *      @Override
+     *      public String toString() {
+     *          return _selfstring == null ? super.toString() : _selfstring;
+     *      }
+     *
+     *      @SncpDyn(remote = false, index = 0)
+     *      public void _createSomeThing(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, TestBean bean){
+     *          _client.remote(_convert, _transport, 0, selfrunnable, samerunnable, diffrunnable, bean);
+     *      }
+     *
+     *      @SncpDyn(remote = false, index = 1)
+     *      public String _updateSomeThing(boolean selfrunnable, boolean samerunnable, boolean diffrunnable, String id){
+     *          return _client.remote(_convert, _transport, 1, selfrunnable, samerunnable, diffrunnable, id);
+     *      }
+     *
+     *      @Override
+     *      public void createSomeThing(TestBean bean){
+     *          _client.remote(_convert, _transport, 2, bean);
+     *      }
+     *
+     *      @Override
+     *      public String findSomeThing(){
+     *          return _client.remote(_convert, _transport, 3);
+     *      }
+     *
+     *      @Override
+     *      public String updateSomeThing(String id){
+     *          return  _client.remote(_convert, _transport, 4, id);
+     *      }
+     * }
+     * 
+ * + * 创建远程模式的Service实例 + * + * @param Service泛型 + * @param name 资源名 + * @param executor 线程池 + * @param serviceClass Service类 + * @param clientAddress 本地IP地址 + * @param transport 通信组件 + * + * @return Service的远程模式实例 + */ + @SuppressWarnings("unchecked") + public static T createRemoteService(final String name, final Consumer executor, final Class serviceClass, + final InetSocketAddress clientAddress, final Transport transport) { + if (serviceClass == null) return null; + if (!Service.class.isAssignableFrom(serviceClass)) return null; + int mod = serviceClass.getModifiers(); + boolean realed = !(java.lang.reflect.Modifier.isAbstract(mod) || serviceClass.isInterface()); + if (!java.lang.reflect.Modifier.isPublic(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, serviceClass, executor, true, realed ? createLocalServiceClass(name, serviceClass) : serviceClass, clientAddress); + 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("', serviceid = ").append(client.getServiceid()); + sb.append(", action.size = ").append(client.getActionCount()); + sb.append(", address = ").append(clientAddress).append(", groups = ").append(transport == null ? null : transport.getName()); + sb.append(", remoteaddrs = ").append(transport == null ? null : Arrays.asList(transport.getRemoteAddresses())); + 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; + AsmMethodVisitor mv; + AnnotationVisitor av0; + + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, serviceClass.isInterface() ? "java/lang/Object" : supDynName, serviceClass.isInterface() ? new String[]{supDynName} : null); + { + av0 = cw.visitAnnotation("Ljavax/annotation/Resource;", true); + av0.visit("name", name); + av0.visitEnd(); + } + { + av0 = cw.visitAnnotation(Type.getDescriptor(ResourceType.class), true); + { + AnnotationVisitor av1 = av0.visitArray("value"); + ResourceType rty = serviceClass.getAnnotation(ResourceType.class); + if (rty == null) { + av1.visit(null, Type.getType(Type.getDescriptor(serviceClass))); + } else { + for (Class cl : rty.value()) { + av1.visit(null, Type.getType(Type.getDescriptor(cl))); + } + } + av1.visitEnd(); + } + av0.visitEnd(); + } + { + av0 = cw.visitAnnotation(sncpDynDesc, true); + av0.visit("remote", Boolean.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 AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "", "()V", null, null)); + //mv.setDebug(true); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, serviceClass.isInterface() ? "java/lang/Object" : supDynName, "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { //init + mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "init", "(" + anyValueDesc + ")V", null, null)); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 2); + mv.visitEnd(); + } + { //destroy + mv = new AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, "destroy", "(" + anyValueDesc + ")V", null, null)); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 2); + mv.visitEnd(); + } + { // toString() + mv = new AsmMethodVisitor(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 AsmMethodVisitor(cw.visitMethod(ACC_PUBLIC, method.getName(), Type.getMethodDescriptor(method), 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.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("', serviceid = ").append(client.getServiceid()); + sb.append(", action.size = ").append(client.getActionCount()); + sb.append(", address = ").append(clientAddress).append(", groups = ").append(transport == null ? null : transport.getName()); + sb.append(", remotes = ").append(transport == null ? null : Arrays.asList(transport.getRemoteAddresses())); + 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/SncpClient.java b/src/org/redkale/net/sncp/SncpClient.java new file mode 100644 index 000000000..f63662f7c --- /dev/null +++ b/src/org/redkale/net/sncp/SncpClient.java @@ -0,0 +1,544 @@ +/* + * To change this license header, choose License Headers reader Project Properties. + * To change this template file, choose Tools | Templates + * and open the template reader the editor. + */ +package org.redkale.net.sncp; + +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 static org.redkale.net.sncp.SncpRequest.*; +import org.redkale.service.*; +import org.redkale.util.*; +import org.redkale.service.DynCall; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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表示无DynCall处理,index=0固定为null, 其他为参数标记的DynCall回调方法 + + protected final int handlerFuncParamIndex; + + protected final int handlerAttachParamIndex; + + protected final int addressTargetParamIndex; + + protected final int addressSourceParamIndex; + + 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 targetAddrIndex = -1; + int sourceAddrIndex = -1; + int handlerAttachIndex = -1; + int handlerFuncIndex = -1; + boolean hasattr = false; + Attribute[] atts = new Attribute[paramTypes.length + 1]; + if (anns.length > 0) { + Class[] params = method.getParameterTypes(); + for (int i = 0; i < params.length; i++) { + if (CompletionHandler.class.isAssignableFrom(params[i])) { + handlerFuncIndex = i; + break; + } + } + for (int i = 0; i < anns.length; i++) { + if (anns[i].length > 0) { + for (Annotation ann : anns[i]) { + if (ann.annotationType() == DynAttachment.class) { + handlerAttachIndex = i; + } else if (ann.annotationType() == DynTargetAddress.class && SocketAddress.class.isAssignableFrom(params[i])) { + targetAddrIndex = i; + } else if (ann.annotationType() == DynSourceAddress.class && SocketAddress.class.isAssignableFrom(params[i])) { + sourceAddrIndex = i; + } + } + for (Annotation ann : anns[i]) { + if (ann.annotationType() == DynCall.class) { + try { + atts[i + 1] = ((DynCall) ann).value().newInstance(); + hasattr = true; + } catch (Exception e) { + logger.log(Level.SEVERE, DynCall.class.getSimpleName() + ".attribute cannot a newInstance for" + method, e); + } + break; + } + } + } + } + } + this.addressTargetParamIndex = targetAddrIndex; + this.addressSourceParamIndex = sourceAddrIndex; + this.handlerFuncParamIndex = handlerFuncIndex; + this.handlerAttachParamIndex = handlerAttachIndex; + this.paramAttrs = hasattr ? atts : null; + if (this.handlerFuncParamIndex >= 0 && method.getReturnType() != void.class) throw new RuntimeException(method + " has CompletionHandler type parameter but return type is not void"); + } + + @Override + public String toString() { + return "{" + actionid + "," + (method == null ? "null" : method.getName()) + "}"; + } + } + + protected static final Logger logger = Logger.getLogger(SncpClient.class.getSimpleName()); + + protected 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 clientAddress; + + private final byte[] addrBytes; + + private final int addrPort; + + protected final DLong serviceid; + + protected final SncpAction[] actions; + + protected final Consumer executor; + + public SncpClient(final String serviceName, final Class serviceType, final Consumer executor, + final boolean remote, final Class serviceClass, final InetSocketAddress clientAddress) { + this.remote = remote; + this.executor = executor; + this.serviceClass = serviceClass; + this.clientAddress = clientAddress; + this.name = serviceName; + this.serviceid = Sncp.hash(serviceType.getName() + ':' + serviceName); + final List methodens = new ArrayList<>(); + //------------------------------------------------------------------------------ + for (java.lang.reflect.Method method : parseMethod(serviceClass)) { + methodens.add(new SncpAction(method, Sncp.hash(method))); + } + 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 InetSocketAddress getClientAddress() { + return clientAddress; + } + + public DLong 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 + + "', address = " + (clientAddress == null ? "" : (clientAddress.getHostString() + ":" + clientAddress.getPort())) + + ", actions.size = " + actions.length + ")"; + } + + public static List parseMethod(final Class serviceClass) { + 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")) 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); + } + } + multis.sort((m1, m2) -> m1.getAnnotation(SncpDyn.class).index() - m2.getAnnotation(SncpDyn.class).index()); + list.sort((Method o1, Method o2) -> { + if (!o1.getName().equals(o2.getName())) return o1.getName().compareTo(o2.getName()); + if (o1.getParameterCount() != o2.getParameterCount()) return o1.getParameterCount() - o2.getParameterCount(); + return 0; + }); + //带SncpDyn必须排在前面 + multis.addAll(list); + return multis; + } + + public void remoteSameGroup(final BsonConvert convert, Transport transport, final int index, final Object... params) { + final SncpAction action = actions[index]; + if (action.handlerFuncParamIndex >= 0) params[action.handlerFuncParamIndex] = null; //不能让远程调用handler,因为之前本地方法已经调用过了 + for (InetSocketAddress addr : transport.getRemoteAddresses()) { + remote0(null, convert, transport, addr, action, params); + } + } + + public void asyncRemoteSameGroup(final BsonConvert convert, Transport transport, final int index, final Object... params) { + if (executor != null) { + executor.accept(() -> { + remoteSameGroup(convert, transport, index, params); + }); + } else { + remoteSameGroup(convert, transport, index, params); + } + } + + public void remoteDiffGroup(final BsonConvert convert, Transport[] transports, final int index, final Object... params) { + if (transports == null || transports.length < 1) return; + final SncpAction action = actions[index]; + if (action.handlerFuncParamIndex >= 0) params[action.handlerFuncParamIndex] = null; //不能让远程调用handler,因为之前本地方法已经调用过了 + for (Transport transport : transports) { + remote0(null, convert, transport, null, action, params); + } + } + + public void asyncRemoteDiffGroup(final BsonConvert convert, Transport[] transports, final int index, final Object... params) { + if (transports == null || transports.length < 1) return; + if (executor != null) { + executor.accept(() -> { + remoteDiffGroup(convert, transports, index, params); + }); + } else { + remoteDiffGroup(convert, transports, index, params); + } + } + + //只给远程模式调用的 + public T remote(final BsonConvert convert, Transport transport, final int index, final Object... params) { + final SncpAction action = actions[index]; + final CompletionHandler handlerFunc = action.handlerFuncParamIndex >= 0 ? (CompletionHandler) params[action.handlerFuncParamIndex] : null; + if (action.handlerFuncParamIndex >= 0) params[action.handlerFuncParamIndex] = null; + Future future = remote0(handlerFunc, convert, transport, null, action, params); + if (handlerFunc != null) return null; + final BsonReader reader = convert.pollBsonReader(); + try { + reader.setBytes(future.get(5, TimeUnit.SECONDS)); + byte i; + while ((i = reader.readByte()) != 0) { + final Attribute attr = action.paramAttrs[i]; + attr.set(params[i - 1], convert.convertFrom(attr.type(), reader)); + } + return convert.convertFrom(action.resultTypes, reader); + } 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(reader); + } + } + + public void remote(final BsonConvert convert, Transport[] transports, final int index, final Object... params) { + if (transports == null || transports.length < 1) return; + remote(convert, transports[0], index, params); + for (int i = 1; i < transports.length; i++) { + remote0(null, convert, transports[i], null, actions[index], params); + } + } + + private Future remote0(final CompletionHandler handler, final BsonConvert convert, final Transport transport, final SocketAddress addr0, final SncpAction action, final Object... params) { + Type[] myparamtypes = action.paramTypes; + if (action.addressSourceParamIndex >= 0) params[action.addressSourceParamIndex] = this.clientAddress; + 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 = addr0 == null ? (action.addressTargetParamIndex >= 0 ? (SocketAddress) params[action.addressTargetParamIndex] : null) : addr0; + 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); + + 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(); + 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); + if (handler != null) { + final Object handlerAttach = action.handlerAttachParamIndex >= 0 ? params[action.handlerAttachParamIndex] : null; + final BsonReader reader = convert.pollBsonReader(); + try { + reader.setBytes(this.body); + int i; + while ((i = (reader.readByte() & 0xff)) != 0) { + final Attribute attr = action.paramAttrs[i]; + attr.set(params[i - 1], convert.convertFrom(attr.type(), reader)); + } + Object rs = convert.convertFrom(action.resultTypes, reader); + handler.completed(rs, handlerAttach); + } catch (Exception e) { + handler.failed(e, handlerAttach); + } finally { + convert.offerBsonReader(reader); + } + } + } + + @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); + if (handler != null) { + final Object handlerAttach = action.handlerAttachParamIndex >= 0 ? params[action.handlerAttachParamIndex] : null; + handler.failed(exc, handlerAttach); + } + } + }); + } + + @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); + DLong rserviceid = DLong.read(buffer); + if (!rserviceid.equals(serviceid)) throw new RuntimeException("sncp(" + action.method + ") response.serviceid = " + serviceid + ", but request.serviceid =" + rserviceid); + DLong raction = DLong.read(buffer); + if (!action.actionid.equals(raction)) throw new RuntimeException("sncp(" + action.method + ") response.actionid = " + action.actionid + ", but request.actionid =(" + raction + ")"); + buffer.getInt(); //地址 + buffer.getChar(); //端口 + } + + private void fillHeader(ByteBuffer buffer, long seqid, DLong actionid, int bodyLength) { + //---------------------head---------------------------------- + final int currentpos = buffer.position(); + buffer.position(0); + buffer.putLong(seqid); //序列号 + buffer.putChar((char) HEADER_SIZE); //header长度 + DLong.write(buffer, this.serviceid); + DLong.write(buffer, actionid); + buffer.put(addrBytes); + buffer.putChar((char) this.addrPort); + buffer.putInt(bodyLength); //body长度 + buffer.putInt(0); //结果码, 请求方固定传0 + buffer.position(currentpos); + } + + protected static final 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/SncpContext.java b/src/org/redkale/net/sncp/SncpContext.java new file mode 100644 index 000000000..54bfdb470 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpContext.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.sncp; + +import java.net.*; +import java.nio.*; +import java.nio.charset.*; +import java.util.concurrent.*; +import java.util.logging.*; +import org.redkale.net.*; +import org.redkale.util.*; +import org.redkale.watch.*; + +/** + * + * @author zhangjx + */ +public class SncpContext extends Context { + + public SncpContext(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) { + super(serverStartTime, logger, executor, bufferCapacity, bufferPool, responsePool, maxbody, charset, + address, prepare, watch, readTimeoutSecond, writeTimeoutSecond); + } +} diff --git a/src/org/redkale/net/sncp/SncpDyn.java b/src/org/redkale/net/sncp/SncpDyn.java new file mode 100644 index 000000000..ea5a74866 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpDyn.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.sncp; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 修饰由SNCP协议动态生成的class、和method + * 本地模式:动态生成的_DynLocalXXXXService类其带有@MultiRun方法均会打上@SncpDyn(remote = false, index=N) 的注解 + * 远程模式:动态生成的_DynRemoteXXXService类会打上@SncpDyn(remote = true) 的注解 + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@Inherited +@Documented +@Target({METHOD, TYPE}) +@Retention(RUNTIME) +public @interface SncpDyn { + + boolean remote(); + + 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..846ac094c --- /dev/null +++ b/src/org/redkale/net/sncp/SncpDynServlet.java @@ -0,0 +1,428 @@ +/* + * 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.*; +import org.redkale.service.DynCall; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public final class SncpDynServlet extends SncpServlet { + + private static volatile int maxClassNameLength = 0; + + private static volatile int maxNameLength = 0; + + 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 DLong serviceid; + + private final HashMap actions = new HashMap<>(); + + private Supplier bufferSupplier; + + public SncpDynServlet(final BsonConvert convert, final String serviceName, final Class type, final Service service) { + this.serviceName = serviceName; + this.type = type; + this.serviceid = Sncp.hash(type.getName() + ':' + serviceName); + 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); + } + maxNameLength = Math.max(maxNameLength, serviceName.length() + 1); + maxClassNameLength = Math.max(maxClassNameLength, type.getName().length()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(this.getClass().getSimpleName()).append("(type=").append(type.getName()); + int len = maxClassNameLength - type.getName().length(); + for (int i = 0; i < len; i++) { + sb.append(' '); + } + sb.append(", serviceid=").append(serviceid).append(", name='").append(serviceName).append("'"); + for (int i = 0; i < maxNameLength - serviceName.length(); i++) { + sb.append(' '); + } + sb.append(", actions.size=").append(actions.size() > 9 ? "" : " ").append(actions.size()).append(")"); + return sb.toString(); + } + + + @Override + public DLong getServiceid() { + return serviceid; + } + + @Override + public int compareTo(SncpServlet o0) { + if (!(o0 instanceof SncpDynServlet)) return 1; + SncpDynServlet o = (SncpDynServlet) o0; + int rs = this.type.getName().compareTo(o.type.getName()); + if (rs == 0) rs = this.serviceName.compareTo(o.serviceName); + return rs; + } + + @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表示无DynCall处理,index=0固定为null, 其他为参数标记的DynCall回调方法 + + 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(paramTypes[1], in);
+         *          String arg2 = convert.convertFrom(paramTypes[2], in);
+         *          int arg3 = convert.convertFrom(paramTypes[3], in);
+         *          Object rs = service.change(arg1, arg2, arg3);
+         *          callParameter(out, arg1, arg2, arg3);
+         *          convert.convertTo(out, paramTypes[0], rs);
+         *      }
+         *  }
+         * 
+ * + * @param service Service + * @param actionid 操作ID + * @param method 方法 + * @return SncpServletAction + */ + @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; + AsmMethodVisitor 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 AsmMethodVisitor(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 = "(Ljava/lang/reflect/Type;" + convertReaderDesc + ")Ljava/lang/Object;"; + try { + convertFromDesc = Type.getMethodDescriptor(BsonConvert.class.getMethod("convertFrom", java.lang.reflect.Type.class, BsonReader.class)); + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + { // action方法 + mv = new AsmMethodVisitor(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, 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.visitVarInsn(ALOAD, 1); + + 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() == DynCall.class) { + try { + atts[i + 1] = ((DynCall) ann).value().newInstance(); + hasattr = true; + } catch (Exception e) { + logger.log(Level.SEVERE, DynCall.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/SncpPrepareServlet.java b/src/org/redkale/net/sncp/SncpPrepareServlet.java new file mode 100644 index 000000000..626e83814 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpPrepareServlet.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.net.sncp; + +import org.redkale.net.PrepareServlet; +import org.redkale.util.AnyValue; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public class SncpPrepareServlet extends PrepareServlet { + + private static final ByteBuffer pongBuffer = ByteBuffer.wrap("PONG".getBytes()).asReadOnlyBuffer(); + + @Override + public void addServlet(SncpServlet servlet, Object attachment, AnyValue conf, DLong... mappings) { + addServlet((SncpServlet) servlet, conf); + } + + public void addServlet(SncpServlet servlet, AnyValue conf) { + setServletConf(servlet, conf); + synchronized (mappings) { + mappings.put(servlet.getServiceid(), servlet); + servlets.add(servlet); + } + } + + public List getSncpServlets() { + ArrayList list = new ArrayList<>(servlets.size()); + servlets.forEach(x -> list.add((SncpServlet) x)); + return list; + } + + @Override + public void init(SncpContext context, AnyValue config) { + servlets.forEach(s -> s.init(context, getServletConf(s))); + } + + @Override + public void destroy(SncpContext context, AnyValue config) { + servlets.forEach(s -> s.destroy(context, getServletConf(s))); + } + + @Override + public void execute(SncpRequest request, SncpResponse response) throws IOException { + if (request.isPing()) { + response.finish(pongBuffer.duplicate()); + return; + } + SncpServlet servlet = (SncpServlet) mappings.get(request.getServiceid()); + if (servlet == null) { + response.finish(SncpResponse.RETCODE_ILLSERVICEID, null); //无效serviceid + } else { + servlet.execute(request, response); + } + } + +} diff --git a/src/org/redkale/net/sncp/SncpRequest.java b/src/org/redkale/net/sncp/SncpRequest.java new file mode 100644 index 000000000..c465870fb --- /dev/null +++ b/src/org/redkale/net/sncp/SncpRequest.java @@ -0,0 +1,139 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public final class SncpRequest extends Request { + + public static final int HEADER_SIZE = 56; + + public static final byte[] DEFAULT_HEADER = new byte[HEADER_SIZE]; + + protected final BsonConvert convert; + + private long seqid; + + private DLong serviceid; + + private DLong actionid; + + private int bodylength; + + private int bodyoffset; + + private boolean ping; + + private byte[] body; + + private byte[] bufferbytes = new byte[6]; + + protected SncpRequest(SncpContext 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 = DLong.read(buffer); + this.actionid = DLong.read(buffer); + buffer.get(bufferbytes); + this.bodylength = 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 + + ",remoteAddress=" + getRemoteAddress() + "}"; + } + + @Override + protected void recycle() { + this.seqid = 0; + this.serviceid = null; + 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 DLong getServiceid() { + return serviceid; + } + + 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..422e576de --- /dev/null +++ b/src/org/redkale/net/sncp/SncpResponse.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.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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public final class SncpResponse extends Response { + + public static final int RETCODE_ILLSERVICEID = (1 << 10); //无效serviceid + + public static final int RETCODE_ILLACTIONID = (1 << 11); //无效actionid + + public static final int RETCODE_THROWEXCEPTION = (1 << 30); //内部异常 + + 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_ILLACTIONID) return "actionid is invalid"; + if (retcode == RETCODE_THROWEXCEPTION) return "Inner exception"; + return null; + } + + protected SncpResponse(SncpContext 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, retcode); + finish(buffer); + return; + } + final int respBodyLength = out.count(); //body总长度 + final ByteBuffer[] buffers = out.toBuffers(); + fillHeader(buffers[0], respBodyLength - HEADER_SIZE, retcode); + finish(buffers); + } + + private void fillHeader(ByteBuffer buffer, int bodyLength, int retcode) { + //---------------------head---------------------------------- + final int currentpos = buffer.position(); + buffer.position(0); + buffer.putLong(request.getSeqid()); + buffer.putChar((char) SncpRequest.HEADER_SIZE); + DLong.write(buffer, request.getServiceid()); + DLong.write(buffer, request.getActionid()); + buffer.put(addrBytes); + buffer.putChar((char) this.addrPort); + buffer.putInt(bodyLength); + 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..b223d7ef0 --- /dev/null +++ b/src/org/redkale/net/sncp/SncpServer.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.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 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +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) { + for (Class type : entry.getTypes()) { + SncpDynServlet sds = new SncpDynServlet(BsonFactory.root().getConvert(), entry.getName(), type, entry.getService()); + this.prepare.addServlet(sds, null, entry.getConf()); + } + } + + public List getSncpServlets() { + return ((SncpPrepareServlet) this.prepare).getSncpServlets(); + } + + @Override + @SuppressWarnings("unchecked") + protected SncpContext 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.bufferCapacity, 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); + SncpContext sncpcontext = new SncpContext(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..bfe46615a --- /dev/null +++ b/src/org/redkale/net/sncp/SncpServlet.java @@ -0,0 +1,36 @@ +/* + * 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.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public abstract class SncpServlet extends Servlet implements Comparable { + + public abstract DLong getServiceid(); + + @Override + public final boolean equals(Object obj) { + return obj != null && obj.getClass() == this.getClass(); + } + + @Override + public final int hashCode() { + return this.getClass().hashCode(); + } + + @Override + public int compareTo(SncpServlet o) { + return 0; + } +} diff --git a/src/org/redkale/net/sncp/package-info.java b/src/org/redkale/net/sncp/package-info.java new file mode 100644 index 000000000..60085c7d0 --- /dev/null +++ b/src/org/redkale/net/sncp/package-info.java @@ -0,0 +1,4 @@ +/** + * SNCP协议包,提供SNCP协议服务器 + */ +package org.redkale.net.sncp; diff --git a/src/org/redkale/service/CacheSourceService.java b/src/org/redkale/service/CacheSourceService.java new file mode 100644 index 000000000..22e43c63e --- /dev/null +++ b/src/org/redkale/service/CacheSourceService.java @@ -0,0 +1,492 @@ +/* + * 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.beans.*; +import java.io.*; +import java.lang.reflect.*; +import java.nio.channels.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.function.*; +import java.util.logging.*; +import javax.annotation.*; +import org.redkale.convert.*; +import org.redkale.convert.json.*; +import org.redkale.net.sncp.*; +import org.redkale.source.*; +import org.redkale.util.*; + +/** + * + * @param key类型 + * @param value类型 + *

+ * 详情见: http://www.redkale.org + * @author zhangjx + */ +@AutoLoad(false) +@ResourceType({CacheSourceService.class, CacheSource.class}) +public class CacheSourceService implements CacheSource, Service, AutoCloseable { + + @Resource(name = "APP_HOME") + private File home; + + @Resource + private JsonConvert convert; + + private boolean needStore; + + private Class keyType; + + private Type objValueType; + + private Type setValueType; + + private Type listValueType; + + private ScheduledThreadPoolExecutor scheduler; + + private Consumer expireHandler; + + private final Logger logger = Logger.getLogger(this.getClass().getSimpleName()); + + protected final ConcurrentHashMap> container = new ConcurrentHashMap<>(); + + public CacheSourceService() { + } + + public final CacheSourceService setStoreType(Class keyType, Class valueType) { + this.keyType = keyType; + this.objValueType = valueType; + this.setValueType = TypeToken.createParameterizedType(null, CopyOnWriteArraySet.class, valueType); + this.listValueType = TypeToken.createParameterizedType(null, ConcurrentLinkedQueue.class, valueType); + this.setNeedStore(this.keyType != null && this.keyType != Serializable.class && this.objValueType != null); + return this; + } + + public final void setNeedStore(boolean needStore) { + this.needStore = needStore; + } + + @Override + public void init(AnyValue conf) { + final CacheSourceService self = this; + AnyValue prop = conf == null ? null : conf.getAnyValue("property"); + if (keyType == null && prop != null) { + String storeKeyStr = prop.getValue("key-type"); + String storeValueStr = prop.getValue("value-type"); + if (storeKeyStr != null && storeValueStr != null) { + try { + this.setStoreType(Class.forName(storeKeyStr), Class.forName(storeValueStr)); + } catch (Exception e) { + logger.log(Level.SEVERE, self.getClass().getSimpleName() + " load key & value store class (" + storeKeyStr + ", " + storeValueStr + ") error", e); + } + } + if (prop.getBoolValue("store-ignore", false)) setNeedStore(false); + } + String expireHandlerClass = prop == null ? null : prop.getValue("expirehandler"); + if (expireHandlerClass != null) { + try { + this.expireHandler = (Consumer) Class.forName(expireHandlerClass).newInstance(); + } catch (Exception e) { + logger.log(Level.SEVERE, self.getClass().getSimpleName() + " new expirehandler class (" + expireHandlerClass + ") instance error", e); + } + } + if (scheduler == null) { + this.scheduler = new ScheduledThreadPoolExecutor(1, (Runnable r) -> { + final Thread t = new Thread(r, self.getClass().getSimpleName() + "-Expirer-Thread"); + t.setDaemon(true); + return t; + }); + final List keys = new ArrayList<>(); + scheduler.scheduleWithFixedDelay(() -> { + keys.clear(); + int now = (int) (System.currentTimeMillis() / 1000); + container.forEach((k, x) -> { + if (x.expireSeconds > 0 && (now > (x.lastAccessed + x.expireSeconds))) { + keys.add(x.key); + } + }); + for (K key : keys) { + CacheEntry entry = container.remove(key); + if (expireHandler != null && entry != null) expireHandler.accept(entry); + } + }, 10, 10, TimeUnit.SECONDS); + logger.finest(self.getClass().getSimpleName() + ":" + self.name() + " start schedule expire executor"); + } + if (Sncp.isRemote(self)) return; + + boolean datasync = false; //是否从远程同步过数据 + //----------同步数据……----------- + // TODO + if (!this.needStore) return; + try { + File store = new File(home, "cache/" + name()); + if (!store.isFile() || !store.canRead()) return; + LineNumberReader reader = new LineNumberReader(new FileReader(store)); + if (this.keyType == null) this.keyType = Serializable.class; + if (this.objValueType == null) { + this.objValueType = Object.class; + this.setValueType = TypeToken.createParameterizedType(null, CopyOnWriteArraySet.class, this.objValueType); + this.listValueType = TypeToken.createParameterizedType(null, ConcurrentLinkedQueue.class, this.objValueType); + } + final Type storeObjType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, objValueType); + final Type storeSetType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, setValueType); + final Type storeListType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, listValueType); + String line; + while ((line = reader.readLine()) != null) { + if (line.isEmpty()) continue; + CacheEntry entry = convert.convertFrom(line.startsWith(CacheEntry.JSON_SET_KEY) ? storeSetType : (line.startsWith(CacheEntry.JSON_LIST_KEY) ? storeListType : storeObjType), line); + if (entry.isExpired()) continue; + if (datasync && container.containsKey(entry.key)) continue; //已经同步了 + container.put(entry.key, entry); + } + reader.close(); + store.delete(); + } catch (Exception e) { + logger.log(Level.SEVERE, CacheSource.class.getSimpleName() + "(" + name() + ") load store file error ", e); + } + } + + @Override + public void close() throws Exception { //给Application 关闭时调用 + destroy(null); + } + + public String name() { + return this.getClass().getAnnotation(Resource.class).name(); + } + + @Override + public void destroy(AnyValue conf) { + if (scheduler != null) scheduler.shutdownNow(); + if (!this.needStore || Sncp.isRemote(this) || container.isEmpty()) return; + try { + File store = new File(home, "cache/" + name()); + store.getParentFile().mkdirs(); + PrintStream stream = new PrintStream(store, "UTF-8"); + final Type storeObjType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, objValueType); + final Type storeSetType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, setValueType); + final Type storeListType = TypeToken.createParameterizedType(null, CacheEntry.class, keyType, listValueType); + Collection> entrys = (Collection>) container.values(); + for (CacheEntry entry : entrys) { + stream.println(convert.convertTo(entry.isSetCacheType() ? storeSetType : (entry.isListCacheType() ? storeListType : storeObjType), entry)); + } + container.clear(); + stream.close(); + } catch (Exception e) { + logger.log(Level.SEVERE, CacheSource.class.getSimpleName() + "(" + name() + ") store to file error ", e); + } + } + + @Override + public boolean exists(K key) { + if (key == null) return false; + CacheEntry entry = container.get(key); + if (entry == null) return false; + return !entry.isExpired(); + } + + @Override + public void exists(final CompletionHandler handler, @DynAttachment final K key) { + if (handler != null) handler.completed(exists(key), key); + } + + @Override + public V get(K key) { + if (key == null) return null; + CacheEntry entry = container.get(key); + if (entry == null || entry.isExpired() || entry.value == null) return null; + if (entry.isListCacheType()) return (V) new ArrayList((Collection) entry.value); + if (entry.isSetCacheType()) return (V) new HashSet((Collection) entry.value); + return (V) entry.getValue(); + } + + @Override + public void get(final CompletionHandler handler, @DynAttachment final K key) { + if (handler != null) handler.completed(get(key), key); + } + + @Override + @MultiRun + public V getAndRefresh(K key, final int expireSeconds) { + if (key == null) return null; + CacheEntry entry = container.get(key); + if (entry == null || entry.isExpired() || entry.value == null) return null; + entry.lastAccessed = (int) (System.currentTimeMillis() / 1000); + entry.expireSeconds = expireSeconds; + if (entry.isListCacheType()) return (V) new ArrayList((Collection) entry.value); + if (entry.isSetCacheType()) return (V) new HashSet((Collection) entry.value); + return (V) entry.getValue(); + } + + @Override + public void getAndRefresh(final CompletionHandler handler, @DynAttachment final K key, final int expireSeconds) { + V rs = getAndRefresh(key, expireSeconds); + if (handler != null) handler.completed(rs, key); + } + + @Override + @MultiRun + public void refresh(K key, final int expireSeconds) { + if (key == null) return; + CacheEntry entry = container.get(key); + if (entry == null) return; + entry.lastAccessed = (int) (System.currentTimeMillis() / 1000); + entry.expireSeconds = expireSeconds; + } + + @Override + public void refresh(final CompletionHandler handler, final K key, final int expireSeconds) { + refresh(key, expireSeconds); + if (handler != null) handler.completed(null, key); + } + + @Override + @MultiRun + public void set(K key, V value) { + if (key == null) return; + CacheEntry entry = container.get(key); + if (entry == null) { + entry = new CacheEntry(CacheEntryType.OBJECT, key, value); + container.putIfAbsent(key, entry); + } else { + entry.expireSeconds = 0; + entry.value = value; + entry.lastAccessed = (int) (System.currentTimeMillis() / 1000); + } + } + + @Override + public void set(final CompletionHandler handler, @DynAttachment final K key, final V value) { + set(key, value); + if (handler != null) handler.completed(null, key); + } + + @Override + @MultiRun + public void set(int expireSeconds, K key, V value) { + if (key == null) return; + CacheEntry entry = container.get(key); + if (entry == null) { + entry = new CacheEntry(CacheEntryType.OBJECT, expireSeconds, key, value); + container.putIfAbsent(key, entry); + } else { + if (expireSeconds > 0) entry.expireSeconds = expireSeconds; + entry.lastAccessed = (int) (System.currentTimeMillis() / 1000); + entry.value = value; + } + } + + @Override + public void set(final CompletionHandler handler, final int expireSeconds, @DynAttachment final K key, final V value) { + set(expireSeconds, key, value); + if (handler != null) handler.completed(null, key); + } + + @Override + @MultiRun + public void setExpireSeconds(K key, int expireSeconds) { + if (key == null) return; + CacheEntry entry = container.get(key); + if (entry == null) return; + entry.expireSeconds = expireSeconds; + } + + @Override + public void setExpireSeconds(final CompletionHandler handler, @DynAttachment final K key, final int expireSeconds) { + setExpireSeconds(key, expireSeconds); + if (handler != null) handler.completed(null, key); + } + + @Override + @MultiRun + public void remove(K key) { + if (key == null) return; + container.remove(key); + } + + @Override + public void remove(final CompletionHandler handler, @DynAttachment final K key) { + remove(key); + if (handler != null) handler.completed(null, key); + } + + @Override + public Collection getCollection(final K key) { + return (Collection) get(key); + } + + @Override + public void getCollection(final CompletionHandler, K> handler, @DynAttachment final K key) { + if (handler != null) handler.completed(getCollection(key), key); + } + + @Override + public Collection getCollectionAndRefresh(final K key, final int expireSeconds) { + return (Collection) getAndRefresh(key, expireSeconds); + } + + @Override + public void getCollectionAndRefresh(final CompletionHandler, K> handler, @DynAttachment final K key, final int expireSeconds) { + if (handler != null) handler.completed(getCollectionAndRefresh(key, expireSeconds), key); + } + + @Override + @MultiRun + public void appendListItem(K key, V value) { + if (key == null) return; + CacheEntry entry = container.get(key); + if (entry == null || !entry.isListCacheType()) { + Collection list = new ConcurrentLinkedQueue<>(); + entry = new CacheEntry(CacheEntryType.LIST, key, list); + CacheEntry old = container.putIfAbsent(key, entry); + if (old != null) list = (Collection) old.value; + list.add(value); + } else { + ((Collection) entry.getValue()).add(value); + } + } + + @Override + public void appendListItem(final CompletionHandler handler, @DynAttachment final K key, final V value) { + appendListItem(key, value); + if (handler != null) handler.completed(null, key); + } + + @Override + @MultiRun + public void removeListItem(K key, V value) { + if (key == null) return; + CacheEntry entry = container.get(key); + if (entry == null || !entry.isListCacheType()) return; + ((Collection) entry.getValue()).remove(value); + } + + @Override + public void removeListItem(final CompletionHandler handler, @DynAttachment final K key, final V value) { + removeListItem(key, value); + if (handler != null) handler.completed(null, key); + } + + @Override + @MultiRun + public void appendSetItem(K key, V value) { + if (key == null) return; + CacheEntry entry = container.get(key); + if (entry == null || !entry.isSetCacheType()) { + Collection set = new CopyOnWriteArraySet(); + entry = new CacheEntry(CacheEntryType.SET, key, set); + CacheEntry old = container.putIfAbsent(key, entry); + if (old != null) set = (Collection) old.value; + set.add(value); + } else { + ((Collection) entry.getValue()).add(value); + } + } + + @Override + public void appendSetItem(final CompletionHandler handler, @DynAttachment final K key, final V value) { + appendSetItem(key, value); + if (handler != null) handler.completed(null, key); + } + + @Override + @MultiRun + public void removeSetItem(K key, V value) { + if (key == null) return; + CacheEntry entry = container.get(key); + if (entry == null || !(entry.value instanceof Set)) return; + ((Set) entry.getValue()).remove(value); + } + + @Override + public void removeSetItem(final CompletionHandler handler, @DynAttachment final K key, final V value) { + removeSetItem(key, value); + if (handler != null) handler.completed(null, key); + } + + public static enum CacheEntryType { + OBJECT, SET, LIST; + } + + public static final class CacheEntry { + + public static final String JSON_SET_KEY = "{\"cacheType\":\"" + CacheEntryType.SET + "\""; + + public static final String JSON_LIST_KEY = "{\"cacheType\":\"" + CacheEntryType.LIST + "\""; + + private final CacheEntryType cacheType; + + private final K key; + + //<=0表示永久保存 + private int expireSeconds; + + private volatile int lastAccessed; //最后刷新时间 + + private T value; + + public CacheEntry(CacheEntryType cacheType, K key, T value) { + this(cacheType, 0, key, value); + } + + public CacheEntry(CacheEntryType cacheType, int expireSeconds, K key, T value) { + this(cacheType, expireSeconds, (int) (System.currentTimeMillis() / 1000), key, value); + } + + @ConstructorProperties({"cacheType", "expireSeconds", "lastAccessed", "key", "value"}) + public CacheEntry(CacheEntryType cacheType, int expireSeconds, int lastAccessed, K key, T value) { + this.cacheType = cacheType; + this.expireSeconds = expireSeconds; + this.lastAccessed = lastAccessed; + this.key = key; + this.value = value; + } + + @Override + public String toString() { + return JsonFactory.root().getConvert().convertTo(this); + } + + @ConvertColumn(ignore = true) + public boolean isListCacheType() { + return cacheType == CacheEntryType.LIST; + } + + @ConvertColumn(ignore = true) + public boolean isSetCacheType() { + return cacheType == CacheEntryType.SET; + } + + @ConvertColumn(ignore = true) + public boolean isExpired() { + return (expireSeconds > 0 && lastAccessed + expireSeconds < (System.currentTimeMillis() / 1000)); + } + + public CacheEntryType getCacheType() { + return cacheType; + } + + public int getExpireSeconds() { + return expireSeconds; + } + + public int getLastAccessed() { + return lastAccessed; + } + + public T getValue() { + return value; + } + + public K getKey() { + return key; + } + + } +} diff --git a/src/org/redkale/service/DataCacheListenerService.java b/src/org/redkale/service/DataCacheListenerService.java new file mode 100644 index 000000000..1e5bf37be --- /dev/null +++ b/src/org/redkale/service/DataCacheListenerService.java @@ -0,0 +1,45 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@AutoLoad(false) +@ResourceType({DataCacheListenerService.class, DataCacheListener.class}) +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..b1b356467 --- /dev/null +++ b/src/org/redkale/service/DataSQLListenerService.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.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; +import org.redkale.util.*; + +/** + * 暂时不实现 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@Deprecated +@AutoLoad(false) +@ResourceType({DataSQLListenerService.class, DataSQLListener.class}) +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(); + + } + + public String name() { + return this.getClass().getAnnotation(Resource.class).name(); + } + + @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(selfrun = false, async = true) + public void send(String... sqls) { + ((DataDefaultSource) source).directExecute(sqls); + } + +} diff --git a/src/org/redkale/service/DataSourceService.java b/src/org/redkale/service/DataSourceService.java new file mode 100644 index 000000000..ef3fcfaf9 --- /dev/null +++ b/src/org/redkale/service/DataSourceService.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.service; + +import java.io.*; +import java.nio.channels.*; +import java.sql.*; +import java.util.*; +import java.util.function.*; +import javax.annotation.*; +import org.redkale.source.*; +import org.redkale.util.*; + +/** + * DataSource对应的Service类, 该类主要特点是将所有含FilterBean参数的方法重载成FilterNode对应的方法。 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@AutoLoad(false) +@ResourceType({DataSourceService.class, DataSource.class}) +public class DataSourceService implements DataSource, Service, AutoCloseable { + + @Resource(name = "$") + private DataSource source; + + @Override + public void insert(@DynCall(DataCallArrayAttribute.class) T... values) { + source.insert(values); + } + + @Override + public void insert(final CompletionHandler handler, @DynAttachment @DynCall(DataCallArrayAttribute.class) final T... values) { + source.insert(values); + if (handler != null) handler.completed(null, values); + } + + @Override + public void delete(T... values) { + source.delete(values); + } + + @Override + public void delete(final CompletionHandler handler, @DynAttachment final T... values) { + source.delete(values); + if (handler != null) handler.completed(null, values); + } + + @Override + public void delete(final Class clazz, final Serializable... ids) { + source.delete(clazz, ids); + } + + @Override + public void delete(final CompletionHandler handler, final Class clazz, @DynAttachment final Serializable... ids) { + source.delete(clazz, ids); + if (handler != null) handler.completed(null, ids); + } + + @Override + public void delete(final Class clazz, FilterNode node) { + source.delete(clazz, node); + } + + @Override + public void delete(final CompletionHandler handler, final Class clazz, @DynAttachment final FilterNode node) { + source.delete(clazz, node); + if (handler != null) handler.completed(null, node); + } + + @Override + public void update(T... values) { + source.update(values); + } + + @Override + public void update(final CompletionHandler handler, @DynAttachment final T... values) { + source.update(values); + if (handler != null) handler.completed(null, values); + } + + @Override + public void updateColumn(final Class clazz, final Serializable id, final String column, final Serializable value) { + source.updateColumn(clazz, id, column, value); + } + + @Override + public void updateColumn(final CompletionHandler handler, final Class clazz, @DynAttachment final Serializable id, final String column, final Serializable value) { + source.updateColumn(clazz, id, column, value); + if (handler != null) handler.completed(null, id); + } + + @Override + public void updateColumnIncrement(final Class clazz, final Serializable id, final String column, long incvalue) { + source.updateColumnIncrement(clazz, id, column, incvalue); + } + + @Override + public void updateColumnIncrement(final CompletionHandler handler, final Class clazz, @DynAttachment final Serializable id, final String column, long incvalue) { + source.updateColumnIncrement(clazz, id, column, incvalue); + if (handler != null) handler.completed(null, id); + } + + @Override + public void updateColumnAnd(final Class clazz, final Serializable id, final String column, long incvalue) { + source.updateColumnAnd(clazz, id, column, incvalue); + } + + @Override + public void updateColumnAnd(final CompletionHandler handler, final Class clazz, @DynAttachment final Serializable id, final String column, long incvalue) { + source.updateColumnAnd(clazz, id, column, incvalue); + if (handler != null) handler.completed(null, id); + } + + @Override + public void updateColumnOr(final Class clazz, final Serializable id, final String column, long incvalue) { + source.updateColumnOr(clazz, id, column, incvalue); + } + + @Override + public void updateColumnOr(final CompletionHandler handler, final Class clazz, @DynAttachment final Serializable id, final String column, long incvalue) { + source.updateColumnOr(clazz, id, column, incvalue); + if (handler != null) handler.completed(null, id); + } + + @Override + public void updateColumns(T value, final String... columns) { + source.updateColumns(value, columns); + } + + @Override + public void updateColumns(final CompletionHandler handler, @DynAttachment final T value, final String... columns) { + source.updateColumns(value, columns); + if (handler != null) handler.completed(null, value); + } + + @Override + public Number getNumberResult(final Class entityClass, FilterFunc func, final String column) { + return source.getNumberResult(entityClass, func, column); + } + + @Override + public void getNumberResult(final CompletionHandler handler, final Class entityClass, final FilterFunc func, @DynAttachment final String column) { + Number rs = source.getNumberResult(entityClass, func, column); + if (handler != null) handler.completed(rs, column); + } + + @Override + public final Number getNumberResult(final Class entityClass, FilterFunc func, final String column, FilterBean bean) { + return getNumberResult(entityClass, func, column, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void getNumberResult(final CompletionHandler handler, final Class entityClass, final FilterFunc func, final String column, final FilterBean bean) { + getNumberResult(handler, entityClass, func, column, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Number getNumberResult(final Class entityClass, FilterFunc func, final String column, FilterNode node) { + return source.getNumberResult(entityClass, func, column, node); + } + + @Override + public void getNumberResult(final CompletionHandler handler, final Class entityClass, final FilterFunc func, final String column, @DynAttachment final FilterNode node) { + Number rs = source.getNumberResult(entityClass, func, column, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public Map queryColumnMap(final Class entityClass, final String keyColumn, FilterFunc func, final String funcColumn) { + return source.queryColumnMap(entityClass, keyColumn, func, funcColumn); + } + + @Override + public void queryColumnMap(final CompletionHandler, String> handler, final Class entityClass, final String keyColumn, final FilterFunc func, @DynAttachment final String funcColumn) { + Map map = source.queryColumnMap(entityClass, keyColumn, func, funcColumn); + if (handler != null) handler.completed(map, funcColumn); + } + + @Override + public final Map queryColumnMap(final Class entityClass, final String keyColumn, FilterFunc func, final String funcColumn, FilterBean bean) { + return queryColumnMap(entityClass, keyColumn, func, funcColumn, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void queryColumnMap(final CompletionHandler, FilterNode> handler, final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn, final FilterBean bean) { + queryColumnMap(handler, entityClass, keyColumn, func, funcColumn, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Map queryColumnMap(final Class entityClass, final String keyColumn, FilterFunc func, final String funcColumn, FilterNode node) { + return source.queryColumnMap(entityClass, keyColumn, func, funcColumn, node); + } + + @Override + public void queryColumnMap(final CompletionHandler, FilterNode> handler, final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn, @DynAttachment final FilterNode node) { + Map map = source.queryColumnMap(entityClass, keyColumn, func, funcColumn, node); + if (handler != null) handler.completed(map, node); + } + + @Override + public T find(final Class clazz, final Serializable pk) { + return source.find(clazz, pk); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, @DynAttachment final Serializable pk) { + T rs = source.find(clazz, pk); + if (handler != null) handler.completed(rs, pk); + } + + @Override + public T find(final Class clazz, SelectColumn selects, final Serializable pk) { + return source.find(clazz, selects, pk); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, final SelectColumn selects, @DynAttachment final Serializable pk) { + T rs = source.find(clazz, selects, pk); + if (handler != null) handler.completed(rs, pk); + } + + @Override + public T find(final Class clazz, final String column, final Serializable key) { + return source.find(clazz, column, key); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, final String column, @DynAttachment final Serializable key) { + T rs = source.find(clazz, column, key); + if (handler != null) handler.completed(rs, key); + } + + @Override + public final T find(final Class clazz, FilterBean bean) { + return find(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void find(final CompletionHandler handler, final Class clazz, final FilterBean bean) { + find(handler, clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public T find(final Class clazz, FilterNode node) { + return source.find(clazz, node); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, @DynAttachment final FilterNode node) { + T rs = source.find(clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public final T find(final Class clazz, final SelectColumn selects, FilterBean bean) { + return find(clazz, selects, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void find(final CompletionHandler handler, final Class clazz, final SelectColumn selects, final FilterBean bean) { + find(handler, clazz, selects, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public T find(final Class clazz, final SelectColumn selects, final FilterNode node) { + return source.find(clazz, selects, node); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, final SelectColumn selects, @DynAttachment final FilterNode node) { + T rs = source.find(clazz, selects, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public boolean exists(final Class clazz, final Serializable pk) { + return source.exists(clazz, pk); + } + + @Override + public void exists(final CompletionHandler handler, final Class clazz, @DynAttachment final Serializable pk) { + boolean rs = source.exists(clazz, pk); + if (handler != null) handler.completed(rs, pk); + } + + @Override + public final boolean exists(final Class clazz, FilterBean bean) { + return exists(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void exists(final CompletionHandler handler, final Class clazz, final FilterBean bean) { + exists(handler, clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public boolean exists(final Class clazz, FilterNode node) { + return source.exists(clazz, node); + } + + @Override + public void exists(final CompletionHandler handler, final Class clazz, @DynAttachment final FilterNode node) { + boolean rs = source.exists(clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public HashSet queryColumnSet(String selectedColumn, Class clazz, final String column, final Serializable key) { + return source.queryColumnSet(selectedColumn, clazz, column, key); + } + + @Override + public void queryColumnSet(final CompletionHandler, Serializable> handler, final String selectedColumn, final Class clazz, final String column, @DynAttachment final Serializable key) { + HashSet rs = source.queryColumnSet(selectedColumn, clazz, column, key); + if (handler != null) handler.completed(rs, key); + } + + @Override + public final HashSet queryColumnSet(String selectedColumn, Class clazz, FilterBean bean) { + return queryColumnSet(selectedColumn, clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void queryColumnSet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final FilterBean bean) { + queryColumnSet(handler, selectedColumn, clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public HashSet queryColumnSet(String selectedColumn, Class clazz, FilterNode node) { + return source.queryColumnSet(selectedColumn, clazz, node); + } + + @Override + public void queryColumnSet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, @DynAttachment final FilterNode node) { + HashSet rs = source.queryColumnSet(selectedColumn, clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public List queryColumnList(String selectedColumn, Class clazz, final String column, final Serializable key) { + return source.queryColumnList(selectedColumn, clazz, column, key); + } + + @Override + public void queryColumnList(final CompletionHandler, Serializable> handler, final String selectedColumn, final Class clazz, final String column, @DynAttachment final Serializable key) { + List rs = source.queryColumnList(selectedColumn, clazz, column, key); + if (handler != null) handler.completed(rs, key); + } + + @Override + public final List queryColumnList(String selectedColumn, Class clazz, FilterBean bean) { + return queryColumnList(selectedColumn, clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void queryColumnList(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, @DynAttachment final FilterBean bean) { + queryColumnList(handler, selectedColumn, clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryColumnList(String selectedColumn, Class clazz, FilterNode node) { + return source.queryColumnList(selectedColumn, clazz, node); + } + + @Override + public void queryColumnList(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, @DynAttachment final FilterNode node) { + List rs = source.queryColumnList(selectedColumn, clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public final Sheet queryColumnSheet(String selectedColumn, Class clazz, Flipper flipper, FilterBean bean) { + return queryColumnSheet(selectedColumn, clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void queryColumnSheet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final Flipper flipper, final FilterBean bean) { + queryColumnSheet(handler, 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 void queryColumnSheet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final Flipper flipper, @DynAttachment final FilterNode node) { + Sheet rs = source.queryColumnSheet(selectedColumn, clazz, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public List queryList(final Class clazz, final String column, final Serializable key) { + return source.queryList(clazz, column, key); + } + + @Override + public void queryList(final CompletionHandler, Serializable> handler, final Class clazz, final String column, @DynAttachment final Serializable key) { + List rs = source.queryList(clazz, column, key); + if (handler != null) handler.completed(rs, key); + } + + @Override + public final List queryList(final Class clazz, final FilterBean bean) { + return queryList(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final FilterBean bean) { + queryList(handler, clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryList(final Class clazz, final FilterNode node) { + return source.queryList(clazz, node); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, @DynAttachment final FilterNode node) { + List rs = source.queryList(clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public final List queryList(final Class clazz, final SelectColumn selects, final FilterBean bean) { + return queryList(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final FilterBean bean) { + queryList(handler, clazz, selects, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryList(final Class clazz, final SelectColumn selects, final FilterNode node) { + return source.queryList(clazz, selects, node); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, @DynAttachment final FilterNode node) { + List rs = source.queryList(clazz, selects, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public List queryList(final Class clazz, final Flipper flipper, final String column, final Serializable key) { + return source.queryList(clazz, flipper, column, key); + } + + @Override + public void queryList(final CompletionHandler, Serializable> handler, final Class clazz, final Flipper flipper, final String column, @DynAttachment final Serializable key) { + List rs = source.queryList(clazz, flipper, column, key); + if (handler != null) handler.completed(rs, key); + } + + @Override + public final List queryList(final Class clazz, final Flipper flipper, final FilterBean bean) { + return queryList(clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterBean bean) { + queryList(handler, clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryList(final Class clazz, final Flipper flipper, final FilterNode node) { + return source.queryList(clazz, flipper, node); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, @DynAttachment final FilterNode node) { + List rs = source.queryList(clazz, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public final List queryList(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean) { + return queryList(clazz, selects, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, SelectColumn selects, final Flipper flipper, final FilterBean bean) { + queryList(handler, clazz, selects, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public List queryList(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) { + return source.queryList(clazz, selects, flipper, node); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, SelectColumn selects, final Flipper flipper, @DynAttachment final FilterNode node) { + List rs = source.queryList(clazz, selects, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public final Sheet querySheet(final Class clazz, final Flipper flipper, final FilterBean bean) { + return querySheet(clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterBean bean) { + querySheet(handler, clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Sheet querySheet(final Class clazz, final Flipper flipper, final FilterNode node) { + return source.querySheet(clazz, flipper, node); + } + + @Override + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, @DynAttachment final FilterNode node) { + Sheet rs = source.querySheet(clazz, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public final Sheet querySheet(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean) { + return querySheet(clazz, selects, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public final void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean) { + querySheet(handler, clazz, selects, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Sheet querySheet(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) { + return source.querySheet(clazz, selects, flipper, node); + } + + @Override + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, @DynAttachment final FilterNode node) { + Sheet rs = source.querySheet(clazz, selects, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public void close() throws Exception { + source.getClass().getMethod("close").invoke(source); + } + + @Override + public final void directQuery(String sql, Consumer consumer) { + source.directQuery(sql, consumer); + } + + @Override + public final int[] directExecute(String... sqls) { + return source.directExecute(sqls); + } + +} diff --git a/src/org/redkale/service/DynAttachment.java b/src/org/redkale/service/DynAttachment.java new file mode 100644 index 000000000..713aa91b8 --- /dev/null +++ b/src/org/redkale/service/DynAttachment.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.service; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * SNCP协议中用于CompletionHandler回调函数中的attach字段。 + * + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@Inherited +@Documented +@Target({PARAMETER}) +@Retention(RUNTIME) +public @interface DynAttachment { + +} diff --git a/src/org/redkale/service/DynCall.java b/src/org/redkale/service/DynCall.java new file mode 100644 index 000000000..f92c423e3 --- /dev/null +++ b/src/org/redkale/service/DynCall.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.service; + +import java.lang.annotation.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import org.redkale.util.*; + +/** + * 参数回写, 当Service的方法需要更改参数对象内部的数据时,需要使用DynCall + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@Inherited +@Documented +@Target({ElementType.PARAMETER}) +@Retention(RUNTIME) +public @interface DynCall { + + Class value(); +} diff --git a/src/org/redkale/service/DynRemote.java b/src/org/redkale/service/DynRemote.java new file mode 100644 index 000000000..0b7b017e8 --- /dev/null +++ b/src/org/redkale/service/DynRemote.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.service; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 用于在 Service 中创建自身远程模式的对象 + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface DynRemote { + +} diff --git a/src/org/redkale/service/DynSourceAddress.java b/src/org/redkale/service/DynSourceAddress.java new file mode 100644 index 000000000..21c2d4c64 --- /dev/null +++ b/src/org/redkale/service/DynSourceAddress.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.service; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * SNCP协议中标记为来源地址参数, 该注解只能标记在类型为SocketAddress或InetSocketAddress的参数上。 + * + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@Inherited +@Documented +@Target({PARAMETER}) +@Retention(RUNTIME) +public @interface DynSourceAddress { + +} diff --git a/src/org/redkale/service/DynTargetAddress.java b/src/org/redkale/service/DynTargetAddress.java new file mode 100644 index 000000000..d600c5eb9 --- /dev/null +++ b/src/org/redkale/service/DynTargetAddress.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.service; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * SNCP协议中标记为目标地址参数, 该注解只能标记在类型为SocketAddress或InetSocketAddress的参数上。 + * + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@Inherited +@Documented +@Target({PARAMETER}) +@Retention(RUNTIME) +public @interface DynTargetAddress { + +} diff --git a/src/org/redkale/service/LocalService.java b/src/org/redkale/service/LocalService.java new file mode 100644 index 000000000..c944e4957 --- /dev/null +++ b/src/org/redkale/service/LocalService.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.service; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 本地模式注解。 + * 声明为LocalService的Service将不会变成远程模式,只能以本地模式存在, 无论配置文件中是否配置成远程模式都会被忽略。 + * + *

详情见: http://www.redkale.org + * @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..5e47f28c1 --- /dev/null +++ b/src/org/redkale/service/MultiRun.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.service; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * MultiRun 只对本地模式Service有效 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({METHOD}) +@Retention(RUNTIME) +public @interface MultiRun { + + boolean selfrun() default true; //当前本地实例是否运行指定操作;只有当指定操作的方法的返回值为void时,该值才能为true,否则忽略。 + + 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..4e8406026 --- /dev/null +++ b/src/org/redkale/service/RetResult.java @@ -0,0 +1,116 @@ +/* + * 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接口中返回的结果需要含结果码,错误信息,和实体对象。 + * + *

+ * 详情见: http://www.redkale.org + * + * @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..8e653c602 --- /dev/null +++ b/src/org/redkale/service/Service.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.service; + +import org.redkale.util.*; + +/** + * 所有Service的实现类不得声明为final, 允许远程模式的public方法都不能声明为final。 + * 注意: "$"是一个很特殊的Service.name值 。 被标记为@Resource(name = "$") 的Service的资源名与所属父Service的资源名一致。 + * + *

+ * Service的资源类型
+ * 业务逻辑的Service通常有两种编写方式:
+ *    1、只写一个Service实现类。
+ *    2、先定义业务的Service接口或抽象类,再编写具体实现类。
+ * 第二种方式需要在具体实现类上使用@ResourceType指明资源注入的类型。
+ * 
+ * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public interface Service { + + /** + * 该方法必须是可以重复调用, 当reload时需要重复调用init方法 + * + * @param config 配置参数 + */ + default void init(AnyValue config) { + + } + + /** + * 进程退出时,调用Service销毁 + * + * @param config 配置参数 + */ + default void destroy(AnyValue config) { + + } + +} diff --git a/src/org/redkale/service/WebSocketNodeService.java b/src/org/redkale/service/WebSocketNodeService.java new file mode 100644 index 000000000..2809debb1 --- /dev/null +++ b/src/org/redkale/service/WebSocketNodeService.java @@ -0,0 +1,82 @@ +/* + * 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.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@AutoLoad(false) +@ResourceType({WebSocketNodeService.class, WebSocketNode.class}) +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 List getOnlineRemoteAddresses(@DynTargetAddress InetSocketAddress targetAddress, Serializable groupid) { + if (localSncpAddress == null || !localSncpAddress.equals(targetAddress)) return remoteOnlineRemoteAddresses(targetAddress, groupid); + final Set engineids = localNodes.get(groupid); + if (engineids == null || engineids.isEmpty()) return null; + final List rs = new ArrayList<>(); + for (String engineid : engineids) { + final WebSocketEngine engine = engines.get(engineid); + if (engine == null) continue; + final WebSocketGroup group = engine.getWebSocketGroup(groupid); + group.getWebSockets().forEach(x -> rs.add("ws" + Objects.hashCode(x) + '@' + x.getRemoteAddr())); + } + return rs; + } + + @Override + public int sendMessage(@DynTargetAddress 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 + "'} from " + addr + " but send result is " + RETCODE_GROUP_EMPTY); + return RETCODE_GROUP_EMPTY; + } + code = group.send(recent, message, last); + if (finest) logger.finest("websocket node send message (" + message + ") from " + addr + " result is " + code); + } + } + return code; + } + + @Override + public void connect(Serializable groupid, InetSocketAddress addr) { + source.appendSetItem(groupid, addr); + if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + groupid + " connect from " + addr); + } + + @Override + public void disconnect(Serializable groupid, InetSocketAddress addr) { + source.removeSetItem(groupid, addr); + if (finest) logger.finest(WebSocketNodeService.class.getSimpleName() + ".event: " + groupid + " disconnect from " + addr); + } +} diff --git a/src/org/redkale/service/package-info.java b/src/org/redkale/service/package-info.java new file mode 100644 index 000000000..0bf67060d --- /dev/null +++ b/src/org/redkale/service/package-info.java @@ -0,0 +1,4 @@ +/** + * Service接口和模式配置包 + */ +package org.redkale.service; diff --git a/src/org/redkale/source/CacheSource.java b/src/org/redkale/source/CacheSource.java new file mode 100644 index 000000000..7fff58b61 --- /dev/null +++ b/src/org/redkale/source/CacheSource.java @@ -0,0 +1,86 @@ +/* + * 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.nio.channels.*; +import java.util.*; + +/** + * + * @param key的类型 + * @param value的类型 + *

+ * 详情见: http://www.redkale.org + * @author zhangjx + */ +public interface CacheSource { + + default boolean isOpen() { + return true; + } + + public boolean exists(final K key); + + public V get(final K key); + + public V getAndRefresh(final K key, final int expireSeconds); + + public void refresh(final K key, final int expireSeconds); + + public void set(final K key, final V value); + + public void set(final int expireSeconds, final K key, final V value); + + public void setExpireSeconds(final K key, final int expireSeconds); + + public void remove(final K key); + + public Collection getCollection(final K key); + + public Collection getCollectionAndRefresh(final K key, final int expireSeconds); + + public void appendListItem(final K key, final V value); + + public void removeListItem(final K key, final V value); + + public void appendSetItem(final K key, final V value); + + public void removeSetItem(final K key, final V value); + + //----------------------异步版--------------------------------- + public void exists(final CompletionHandler handler, final K key); + + public void get(final CompletionHandler handler, final K key); + + public void getAndRefresh(final CompletionHandler handler, final K key, final int expireSeconds); + + public void refresh(final CompletionHandler handler, final K key, final int expireSeconds); + + public void set(final CompletionHandler handler, final K key, final V value); + + public void set(final CompletionHandler handler, final int expireSeconds, final K key, final V value); + + public void setExpireSeconds(final CompletionHandler handler, final K key, final int expireSeconds); + + public void remove(final CompletionHandler handler, final K key); + + public void getCollection(final CompletionHandler, K> handler, final K key); + + public void getCollectionAndRefresh(final CompletionHandler, K> handler, final K key, final int expireSeconds); + + public void appendListItem(final CompletionHandler handler, final K key, final V value); + + public void removeListItem(final CompletionHandler handler, final K key, final V value); + + public void appendSetItem(final CompletionHandler handler, final K key, final V value); + + public void removeSetItem(final CompletionHandler handler, final K key, final V value); + + default void isOpen(final CompletionHandler handler) { + if (handler != null) handler.completed(Boolean.TRUE, null); + } +} diff --git a/src/org/redkale/source/DataCacheListener.java b/src/org/redkale/source/DataCacheListener.java new file mode 100644 index 000000000..d26b809ec --- /dev/null +++ b/src/org/redkale/source/DataCacheListener.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.source; + +import java.io.Serializable; + +/** + * + *

详情见: http://www.redkale.org + * @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..405707b2e --- /dev/null +++ b/src/org/redkale/source/DataCallArrayAttribute.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.source; + +import java.io.*; +import java.lang.reflect.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Entity类的类型 + * @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..478846e24 --- /dev/null +++ b/src/org/redkale/source/DataCallAttribute.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.source; + +import java.io.*; +import java.lang.reflect.*; +import java.util.concurrent.*; +import org.redkale.util.*; + +/** + * + *

详情见: http://www.redkale.org + * @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/DataDefaultSource.java b/src/org/redkale/source/DataDefaultSource.java new file mode 100644 index 000000000..dde52bf11 --- /dev/null +++ b/src/org/redkale/source/DataDefaultSource.java @@ -0,0 +1,1738 @@ +/* + * 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.nio.channels.*; +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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public final class DataDefaultSource implements DataSource, Function, AutoCloseable { + + public static final String DATASOURCE_CONFPATH = "DATASOURCE_CONFPATH"; + + static final String JDBC_CONNECTIONSMAX = "javax.persistence.connections.limit"; + + static final String JDBC_CONTAIN_SQLTEMPLATE = "javax.persistence.contain.sqltemplate"; + + static final String JDBC_NOTCONTAIN_SQLTEMPLATE = "javax.persistence.notcontain.sqltemplate"; + + 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 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 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.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.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; + } + + public final String name() { + return name; + } + + @Override + public void close() throws Exception { + 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); + } + } + + @Override + public final int[] directExecute(String... sqls) { + Connection conn = createWriteSQLConnection(); + try { + return directExecute(conn, sqls); + } finally { + closeSQLConnection(conn); + } + } + + private int[] directExecute(final Connection conn, String... sqls) { + if (sqls.length == 0) return new int[0]; + try { + final Statement stmt = conn.createStatement(); + final int[] rs = new int[sqls.length]; + int i = -1; + for (String sql : sqls) { + rs[++i] = stmt.execute(sql) ? 1 : 0; + + } + stmt.close(); + return rs; + } 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, this.readPool.props, fullloader); + } + + /** + * 将entity的对象全部加载到Cache中去,如果clazz没有被@javax.persistence.Cacheable注解则不做任何事 + * + * @param Entity类泛型 + * @param clazz Entity类 + */ + 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 Entity类泛型 + * @param values Entity对象 + */ + @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); + } + } + + @Override + public void insert(final CompletionHandler handler, final T... values) { + insert(values); + if (handler != null) handler.completed(null, values); + } + + private void insert(final Connection conn, final EntityInfo info, T... values) { + if (values.length == 0) return; + try { + 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")); + } + } + } + final EntityCache cache = info.getCache(); + 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 Entity类泛型 + * @param values Entity对象 + */ + @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 CompletionHandler handler, final T... values) { + delete(values); + if (handler != null) handler.completed(null, 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 CompletionHandler handler, final Class clazz, final Serializable... ids) { + delete(clazz, ids); + if (handler != null) handler.completed(null, 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 CompletionHandler handler, final Class clazz, final FilterNode node) { + delete(clazz, node); + if (handler != null) handler.completed(null, 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 " + (this.readPool.isMysql() ? "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 Entity类泛型 + * @param values Entity对象 + */ + @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 CompletionHandler handler, final T... values) { + update(values); + if (handler != null) handler.completed(null, 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 Entity类的泛型 + * @param clazz Entity类 + * @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(final CompletionHandler handler, final Class clazz, final Serializable id, final String column, final Serializable value) { + updateColumn(clazz, id, column, value); + if (handler != null) handler.completed(null, id); + } + + 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 + * 等价SQL: UPDATE {clazz} SET {column} = {column} + {incvalue} WHERE {primary} = {id} + * + * @param Entity类的泛型 + * @param clazz Entity类 + * @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(final CompletionHandler handler, final Class clazz, final Serializable id, final String column, long incvalue) { + updateColumnIncrement(clazz, id, column, incvalue); + if (handler != null) handler.completed(null, id); + } + + 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 + * 等价SQL: UPDATE {clazz} SET {column} = {column} & {incvalue} WHERE {primary} = {id} + * + * @param Entity类的泛型 + * @param clazz Entity类 + * @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(final CompletionHandler handler, final Class clazz, final Serializable id, final String column, long incvalue) { + updateColumnAnd(clazz, id, column, incvalue); + if (handler != null) handler.completed(null, id); + } + + 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 + * 等价SQL: UPDATE {clazz} SET {column} = {column} | {incvalue} WHERE {primary} = {id} + * + * @param Entity类的泛型 + * @param clazz Entity类 + * @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(final CompletionHandler handler, final Class clazz, final Serializable id, final String column, long incvalue) { + updateColumnOr(clazz, id, column, incvalue); + if (handler != null) handler.completed(null, id); + } + + 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 Entity类的泛型 + * @param value Entity对象 + * @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 CompletionHandler handler, final T value, final String... columns) { + updateColumns(value, columns); + if (handler != null) handler.completed(null, value); + } + + 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 FilterFunc func, final String column) { + return getNumberResult(entityClass, func, column, (FilterNode) null); + } + + @Override + public void getNumberResult(final CompletionHandler handler, final Class entityClass, final FilterFunc func, final String column) { + Number rs = getNumberResult(entityClass, func, column); + if (handler != null) handler.completed(rs, column); + } + + @Override + public Number getNumberResult(final Class entityClass, final FilterFunc func, final String column, FilterBean bean) { + return getNumberResult(entityClass, func, column, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public void getNumberResult(final CompletionHandler handler, final Class entityClass, final FilterFunc func, final String column, final FilterBean bean) { + getNumberResult(handler, entityClass, func, column, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Number getNumberResult(final Class entityClass, final FilterFunc func, 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(func, 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 " + func.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); + } + } + + @Override + public void getNumberResult(final CompletionHandler handler, final Class entityClass, final FilterFunc func, final String column, final FilterNode node) { + Number rs = getNumberResult(entityClass, func, column, node); + if (handler != null) handler.completed(rs, node); + } + + //-----------------------queryColumnMap----------------------------- + @Override + public Map queryColumnMap(final Class entityClass, final String keyColumn, FilterFunc func, final String funcColumn) { + return queryColumnMap(entityClass, keyColumn, func, funcColumn, (FilterNode) null); + } + + @Override + public void queryColumnMap(final CompletionHandler, String> handler, final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn) { + Map map = queryColumnMap(entityClass, keyColumn, func, funcColumn); + if (handler != null) handler.completed(map, funcColumn); + } + + @Override + public Map queryColumnMap(final Class entityClass, final String keyColumn, FilterFunc func, final String funcColumn, FilterBean bean) { + return queryColumnMap(entityClass, keyColumn, func, funcColumn, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public void queryColumnMap(final CompletionHandler, FilterNode> handler, final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn, final FilterBean bean) { + queryColumnMap(handler, entityClass, keyColumn, func, funcColumn, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public Map queryColumnMap(final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn, 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.queryColumnMap(keyColumn, func, funcColumn, 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 + ", " + func.getColumn((funcColumn == null || funcColumn.isEmpty() ? "*" : ("a." + funcColumn))) + + " 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)), (N) set.getObject(2)); + } + set.close(); + prestmt.close(); + return rs; + } catch (SQLException e) { + throw new RuntimeException(e); + } finally { + if (conn != null) closeSQLConnection(conn); + } + } + + @Override + public void queryColumnMap(final CompletionHandler, FilterNode> handler, final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn, final FilterNode node) { + Map map = queryColumnMap(entityClass, keyColumn, func, funcColumn, node); + if (handler != null) handler.completed(map, node); + } + + //-----------------------find---------------------------- + /** + * 根据主键获取对象 + * + * @param Entity类的泛型 + * @param clazz Entity类 + * @param pk 主键值 + * @return Entity对象 + */ + @Override + public T find(Class clazz, Serializable pk) { + return find(clazz, (SelectColumn) null, pk); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, final Serializable pk) { + T rs = find(clazz, pk); + if (handler != null) handler.completed(rs, 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 void find(final CompletionHandler handler, final Class clazz, final SelectColumn selects, final Serializable pk) { + T rs = find(clazz, selects, pk); + if (handler != null) handler.completed(rs, pk); + } + + @Override + public T find(final Class clazz, final String column, final Serializable key) { + return find(clazz, null, FilterNode.create(column, key)); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, final String column, final Serializable key) { + T rs = find(clazz, column, key); + if (handler != null) handler.completed(rs, key); + } + + @Override + public T find(final Class clazz, final FilterBean bean) { + return find(clazz, null, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + T rs = find(clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public T find(final Class clazz, final FilterNode node) { + return find(clazz, null, node); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, final FilterNode node) { + T rs = find(clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public T find(final Class clazz, final SelectColumn selects, final FilterBean bean) { + return find(clazz, selects, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public void find(final CompletionHandler handler, final Class clazz, final SelectColumn selects, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + T rs = find(clazz, selects, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + 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 void find(final CompletionHandler handler, final Class clazz, final SelectColumn selects, final FilterNode node) { + T rs = find(clazz, selects, node); + if (handler != null) handler.completed(rs, node); + } + + @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 void exists(final CompletionHandler handler, final Class clazz, final Serializable pk) { + boolean rs = exists(clazz, pk); + if (handler != null) handler.completed(rs, pk); + } + + @Override + public boolean exists(final Class clazz, final FilterBean bean) { + return exists(clazz, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public void exists(final CompletionHandler handler, final Class clazz, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + boolean rs = exists(clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @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); + } + } + + @Override + public void exists(final CompletionHandler handler, final Class clazz, final FilterNode node) { + boolean rs = exists(clazz, node); + if (handler != null) handler.completed(rs, node); + } + + //-----------------------list set---------------------------- + @Override + public HashSet queryColumnSet(String selectedColumn, Class clazz, String column, Serializable key) { + return queryColumnSet(selectedColumn, clazz, FilterNode.create(column, key)); + } + + @Override + public void queryColumnSet(final CompletionHandler, Serializable> handler, final String selectedColumn, final Class clazz, final String column, final Serializable key) { + HashSet rs = queryColumnSet(selectedColumn, clazz, column, key); + if (handler != null) handler.completed(rs, key); + } + + @Override + public HashSet queryColumnSet(final String selectedColumn, final Class clazz, final FilterBean bean) { + return new LinkedHashSet<>(queryColumnList(selectedColumn, clazz, bean)); + } + + @Override + public void queryColumnSet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + HashSet rs = queryColumnSet(selectedColumn, clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public HashSet queryColumnSet(String selectedColumn, Class clazz, FilterNode node) { + return new LinkedHashSet<>(queryColumnList(selectedColumn, clazz, node)); + } + + @Override + public void queryColumnSet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final FilterNode node) { + HashSet rs = queryColumnSet(selectedColumn, clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public List queryColumnList(final String selectedColumn, final Class clazz, final String column, final Serializable key) { + return queryColumnList(selectedColumn, clazz, FilterNode.create(column, key)); + } + + @Override + public void queryColumnList(final CompletionHandler, Serializable> handler, final String selectedColumn, final Class clazz, final String column, final Serializable key) { + List rs = queryColumnList(selectedColumn, clazz, column, key); + if (handler != null) handler.completed(rs, key); + } + + @Override + public List queryColumnList(final String selectedColumn, final Class clazz, final FilterBean bean) { + return (List) queryColumnSheet(selectedColumn, clazz, null, bean).list(true); + } + + @Override + public void queryColumnList(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final FilterBean bean) { + final FilterNode node = FilterNodeBean.createFilterNode(bean); + List rs = queryColumnList(selectedColumn, clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public List queryColumnList(final String selectedColumn, final Class clazz, final FilterNode node) { + return (List) queryColumnSheet(selectedColumn, clazz, null, node).list(true); + } + + @Override + public void queryColumnList(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final FilterNode node) { + List rs = queryColumnList(selectedColumn, clazz, node); + if (handler != null) handler.completed(rs, node); + } + + /** + * 根据指定参数查询对象某个字段的集合 + *

+ * @param Entity类的泛型 + * @param 字段值的类型 + * @param selectedColumn 字段名 + * @param clazz Entity类 + * @param flipper 翻页对象 + * @param bean 过滤Bean + * @return 字段集合 + */ + @Override + public Sheet queryColumnSheet(final String selectedColumn, Class clazz, final Flipper flipper, final FilterBean bean) { + return queryColumnSheet(selectedColumn, clazz, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public void queryColumnSheet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final Flipper flipper, final FilterBean bean) { + final FilterNode node = FilterNodeBean.createFilterNode(bean); + Sheet rs = queryColumnSheet(selectedColumn, clazz, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public Sheet queryColumnSheet(final String selectedColumn, final 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; + } + + @Override + public void queryColumnSheet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final Flipper flipper, final FilterNode node) { + Sheet rs = queryColumnSheet(selectedColumn, clazz, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + private Map formatMap(final Class clazz, final 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 Entity类的泛型 + * @param clazz Entity类 + * @param column 过滤字段名 + * @param key 过滤字段值 + * @return Entity对象的集合 + */ + @Override + public List queryList(final Class clazz, final String column, final Serializable key) { + return queryList(clazz, FilterNode.create(column, key)); + } + + @Override + public void queryList(final CompletionHandler, Serializable> handler, final Class clazz, final String column, final Serializable key) { + List rs = queryList(clazz, column, key); + if (handler != null) handler.completed(rs, key); + } + + /** + * 根据过滤对象FilterBean查询对象集合 + * + * @param Entity类的泛型 + * @param clazz Entity类 + * @param bean 过滤Bean + * @return Entity对象集合 + */ + @Override + public List queryList(final Class clazz, final FilterBean bean) { + return queryList(clazz, (SelectColumn) null, bean); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + List rs = queryList(clazz, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public List queryList(final Class clazz, final FilterNode node) { + return queryList(clazz, (SelectColumn) null, node); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final FilterNode node) { + List rs = queryList(clazz, node); + if (handler != null) handler.completed(rs, node); + } + + /** + * 根据过滤对象FilterBean查询对象集合, 对象只填充或排除SelectField指定的字段 + * + * @param Entity类的泛型 + * @param clazz Entity类 + * @param selects 收集的字段 + * @param bean 过滤Bean + * @return Entity对象的集合 + */ + @Override + public List queryList(final Class clazz, final SelectColumn selects, final FilterBean bean) { + return queryList(clazz, selects, (Flipper) null, bean); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + List rs = queryList(clazz, selects, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public List queryList(final Class clazz, final SelectColumn selects, final FilterNode node) { + return queryList(clazz, selects, (Flipper) null, node); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final FilterNode node) { + List rs = queryList(clazz, selects, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public List queryList(final Class clazz, final Flipper flipper, final String column, final Serializable key) { + return queryList(clazz, flipper, FilterNode.create(column, key)); + } + + @Override + public void queryList(final CompletionHandler, Serializable> handler, final Class clazz, final Flipper flipper, final String column, final Serializable key) { + List rs = queryList(clazz, flipper, column, key); + if (handler != null) handler.completed(rs, key); + } + + @Override + public List queryList(final Class clazz, final Flipper flipper, final FilterBean bean) { + return queryList(clazz, null, flipper, bean); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + List rs = queryList(clazz, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public List queryList(final Class clazz, final Flipper flipper, final FilterNode node) { + return queryList(clazz, null, flipper, node); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterNode node) { + List rs = queryList(clazz, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @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); + } + + @Override + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + List rs = queryList(clazz, selects, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @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 void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) { + List rs = queryList(clazz, selects, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + //-----------------------sheet---------------------------- + /** + * 根据过滤对象FilterBean和翻页对象Flipper查询一页的数据 + * + * @param Entity类的泛型 + * @param clazz Entity类 + * @param flipper 翻页对象 + * @param bean 过滤Bean + * @return Entity对象的集合 + */ + @Override + public Sheet querySheet(final Class clazz, final Flipper flipper, final FilterBean bean) { + return querySheet(clazz, null, flipper, bean); + } + + @Override + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + Sheet rs = querySheet(clazz, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public Sheet querySheet(final Class clazz, final Flipper flipper, final FilterNode node) { + return querySheet(clazz, null, flipper, node); + } + + @Override + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterNode node) { + Sheet rs = querySheet(clazz, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + /** + * 根据过滤对象FilterBean和翻页对象Flipper查询一页的数据, 对象只填充或排除SelectField指定的字段 + * + * @param Entity类的泛型 + * @param clazz Entity类 + * @param selects 收集的字段集合 + * @param flipper 翻页对象 + * @param bean 过滤Bean + * @return Entity对象的集合 + */ + @Override + public Sheet querySheet(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean) { + return querySheet(true, true, clazz, selects, flipper, FilterNodeBean.createFilterNode(bean)); + } + + @Override + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean) { + FilterNode node = FilterNodeBean.createFilterNode(bean); + Sheet rs = querySheet(clazz, selects, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + @Override + public Sheet querySheet(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) { + return querySheet(true, true, clazz, selects, flipper, node); + } + + @Override + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node) { + Sheet rs = querySheet(clazz, selects, flipper, node); + if (handler != null) handler.completed(rs, node); + } + + private Sheet querySheet(final boolean readcache, final boolean needtotal, final 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); + } + } + + @Override + public final void directQuery(String sql, Consumer consumer) { + final Connection conn = createReadSQLConnection(); + try { + if (debug.get()) logger.finest("direct query sql=" + sql); + final PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + final ResultSet set = ps.executeQuery(); + consumer.accept(set); + set.close(); + ps.close(); + } 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..0d5295a5a --- /dev/null +++ b/src/org/redkale/source/DataSQLListener.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; + +/** + * @Resource(name = "property.datasource.nodeid") + * + *

详情见: http://www.redkale.org + * @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..6dc600c90 --- /dev/null +++ b/src/org/redkale/source/DataSource.java @@ -0,0 +1,325 @@ +/* + * 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.nio.channels.*; +import java.sql.*; +import java.util.*; +import java.util.function.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public interface DataSource { + + //----------------------insert----------------------------- + /** + * 新增对象, 必须是Entity对象 + * + * @param 泛型 + * @param values Entity对象 + */ + public void insert(final T... values); + + //----------------------异步版--------------------------------- + public void insert(final CompletionHandler handler, final T... values); + + //-------------------------delete-------------------------- + /** + * 删除对象, 必须是Entity对象 + * + * @param 泛型 + * @param values Entity对象 + */ + public void delete(final T... values); + + /** + * 根据主键值删除数据 + * 等价SQL: DELETE FROM WHERE {primary} IN {ids} + * + * @param Entity类的泛型 + * @param clazz Entity类 + * @param ids 主键值 + */ + public void delete(final Class clazz, final Serializable... ids); + + public void delete(final Class clazz, final FilterNode node); + + //----------------------异步版--------------------------------- + public void delete(final CompletionHandler handler, final T... values); + + public void delete(final CompletionHandler handler, final Class clazz, final Serializable... ids); + + public void delete(final CompletionHandler handler, final Class clazz, final FilterNode node); + + //------------------------update--------------------------- + /** + * 更新对象, 必须是Entity对象 + * + * @param 泛型 + * @param values Entity对象 + */ + public void update(final T... values); + + public void updateColumn(final Class clazz, final Serializable id, final String column, final Serializable value); + + public void updateColumnIncrement(final Class clazz, final Serializable id, final String column, long incvalue); + + public void updateColumnAnd(final Class clazz, final Serializable id, final String column, long incvalue); + + public void updateColumnOr(final Class clazz, final Serializable id, final String column, long incvalue); + + public void updateColumns(final T value, final String... columns); + + //----------------------异步版--------------------------------- + public void update(final CompletionHandler handler, final T... values); + + public void updateColumn(final CompletionHandler handler, final Class clazz, final Serializable id, final String column, final Serializable value); + + public void updateColumnIncrement(final CompletionHandler handler, final Class clazz, final Serializable id, final String column, long incvalue); + + public void updateColumnAnd(final CompletionHandler handler, final Class clazz, final Serializable id, final String column, long incvalue); + + public void updateColumnOr(final CompletionHandler handler, final Class clazz, final Serializable id, final String column, long incvalue); + + public void updateColumns(final CompletionHandler handler, final T value, final String... columns); + + //-----------------------getXXXXResult----------------------------- + public Number getNumberResult(final Class entityClass, final FilterFunc func, final String column); + + public Number getNumberResult(final Class entityClass, final FilterFunc func, final String column, final FilterBean bean); + + public Number getNumberResult(final Class entityClass, final FilterFunc func, final String column, final FilterNode node); + + public Map queryColumnMap(final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn); + + public Map queryColumnMap(final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn, final FilterBean bean); + + public Map queryColumnMap(final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn, final FilterNode node); + + //----------------------异步版--------------------------------- + public void getNumberResult(final CompletionHandler handler, final Class entityClass, final FilterFunc func, final String column); + + public void getNumberResult(final CompletionHandler handler, final Class entityClass, final FilterFunc func, final String column, final FilterBean bean); + + public void getNumberResult(final CompletionHandler handler, final Class entityClass, final FilterFunc func, final String column, final FilterNode node); + + public void queryColumnMap(final CompletionHandler, String> handler, final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn); + + public void queryColumnMap(final CompletionHandler, FilterNode> handler, final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn, final FilterBean bean); + + public void queryColumnMap(final CompletionHandler, FilterNode> handler, final Class entityClass, final String keyColumn, final FilterFunc func, final String funcColumn, final FilterNode node); + + //-----------------------find---------------------------- + /** + * 根据主键获取对象 + * + * @param 泛型 + * @param clazz Entity类 + * @param pk 主键值 + * @return Entity对象 + */ + public T find(final Class clazz, final Serializable pk); + + public T find(final Class clazz, final SelectColumn selects, final Serializable pk); + + public T find(final Class clazz, final String column, final Serializable key); + + public T find(final Class clazz, final FilterBean bean); + + public T find(final Class clazz, final FilterNode node); + + public T find(final Class clazz, final SelectColumn selects, final FilterBean bean); + + public T find(final Class clazz, final SelectColumn selects, final FilterNode node); + + public boolean exists(final Class clazz, final Serializable pk); + + public boolean exists(final Class clazz, final FilterBean bean); + + public boolean exists(final Class clazz, final FilterNode node); + + //----------------------异步版--------------------------------- + public void find(final CompletionHandler handler, final Class clazz, final Serializable pk); + + public void find(final CompletionHandler handler, final Class clazz, final SelectColumn selects, final Serializable pk); + + public void find(final CompletionHandler handler, final Class clazz, final String column, final Serializable key); + + public void find(final CompletionHandler handler, final Class clazz, final FilterBean bean); + + public void find(final CompletionHandler handler, final Class clazz, final FilterNode node); + + public void find(final CompletionHandler handler, final Class clazz, final SelectColumn selects, final FilterBean bean); + + public void find(final CompletionHandler handler, final Class clazz, final SelectColumn selects, final FilterNode node); + + public void exists(final CompletionHandler handler, final Class clazz, final Serializable pk); + + public void exists(final CompletionHandler handler, final Class clazz, final FilterBean bean); + + public void exists(final CompletionHandler handler, final Class clazz, final FilterNode node); + + //-----------------------list set---------------------------- + /** + * 根据指定字段值查询对象某个字段的集合 + * + * @param Entity泛型 + * @param 字段类型 + * @param selectedColumn 字段名 + * @param clazz Entity类 + * @param column 过滤字段名 + * @param key 过滤字段值 + * @return 字段值的集合 + */ + public HashSet queryColumnSet(final String selectedColumn, final Class clazz, final String column, final Serializable key); + + public HashSet queryColumnSet(final String selectedColumn, final Class clazz, final FilterBean bean); + + public HashSet queryColumnSet(final String selectedColumn, final Class clazz, final FilterNode node); + + public List queryColumnList(final String selectedColumn, final Class clazz, final String column, final Serializable key); + + public List queryColumnList(final String selectedColumn, final Class clazz, final FilterBean bean); + + public List queryColumnList(final String selectedColumn, final Class clazz, final FilterNode node); + + //----------------------异步版--------------------------------- + public void queryColumnSet(final CompletionHandler, Serializable> handler, final String selectedColumn, final Class clazz, final String column, final Serializable key); + + public void queryColumnSet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final FilterBean bean); + + public void queryColumnSet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final FilterNode node); + + public void queryColumnList(final CompletionHandler, Serializable> handler, final String selectedColumn, final Class clazz, final String column, final Serializable key); + + public void queryColumnList(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final FilterBean bean); + + public void queryColumnList(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final FilterNode node); + + /** + * 根据指定参数查询对象某个字段的集合 + * + * @param Entity泛型 + * @param 字段类型 + * @param selectedColumn 字段名 + * @param clazz Entity类 + * @param flipper 翻页对象 + * @param bean 过滤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 void queryColumnSheet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final Flipper flipper, final FilterBean bean); + + public void queryColumnSheet(final CompletionHandler, FilterNode> handler, final String selectedColumn, final Class clazz, final Flipper flipper, final FilterNode node); + + /** + * 根据指定字段值查询对象集合 + * + * @param Entity泛型 + * @param clazz Entity类 + * @param column 过滤字段名 + * @param key 过滤字段值 + * @return Entity的List + */ + public List queryList(final Class clazz, final String column, final Serializable key); + + public List queryList(final Class clazz, final FilterBean bean); + + public List queryList(final Class clazz, final FilterNode node); + + public List queryList(final Class clazz, final SelectColumn selects, final FilterBean bean); + + public List queryList(final Class clazz, final SelectColumn selects, final FilterNode node); + + 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 FilterBean bean); + + public List queryList(final Class clazz, final Flipper flipper, final FilterNode node); + + public List queryList(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean); + + public List queryList(final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node); + + //----------------------异步版--------------------------------- + public void queryList(final CompletionHandler, Serializable> handler, final Class clazz, final String column, final Serializable key); + + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final FilterBean bean); + + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final FilterNode node); + + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final FilterBean bean); + + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final FilterNode node); + + public void queryList(final CompletionHandler, Serializable> handler, final Class clazz, final Flipper flipper, final String column, final Serializable key); + + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterBean bean); + + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterNode node); + + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean); + + public void queryList(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node); + + //-----------------------sheet---------------------------- + /** + * 根据指定参数查询对象某个对象的集合页 + *

+ * @param Entity泛型 + * @param clazz Entity类 + * @param flipper 翻页对象 + * @param bean 过滤Bean + * @return Entity的Sheet + */ + 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); + + //----------------------异步版--------------------------------- + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterBean bean); + + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final Flipper flipper, final FilterNode node); + + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterBean bean); + + public void querySheet(final CompletionHandler, FilterNode> handler, final Class clazz, final SelectColumn selects, final Flipper flipper, final FilterNode node); + + //-----------------------direct---------------------------- + /** + * 直接本地执行SQL语句进行查询,远程模式不可用 + * 通常用于复杂的关联查询 + * + * @param sql SQL语句 + * @param consumer 回调函数 + */ + public void directQuery(String sql, final Consumer consumer); + + /** + * 直接本地执行SQL语句进行增删改操作,远程模式不可用 + * 通常用于复杂的更新操作 + * + * @param sqls SQL语句 + * @return 结果数组 + */ + public int[] directExecute(String... sqls); +} diff --git a/src/org/redkale/source/DistributeGenerator.java b/src/org/redkale/source/DistributeGenerator.java new file mode 100644 index 000000000..849767df0 --- /dev/null +++ b/src/org/redkale/source/DistributeGenerator.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; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@Target({FIELD}) +@Retention(RUNTIME) +public @interface DistributeGenerator { + + long initialValue() default 1; + + /** + * 如果allocationSize的值小于或等于1,则主键不会加上nodeid + * + * @return allocationSize + */ + int allocationSize() default 1000; +} diff --git a/src/org/redkale/source/DistributeTables.java b/src/org/redkale/source/DistributeTables.java new file mode 100644 index 000000000..35e280f91 --- /dev/null +++ b/src/org/redkale/source/DistributeTables.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.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 当使用DistributeGenerator控制主键值时, 如果表A与表AHistory使用同一主键时, 就需要将表A的class标记: + *

+ *  @DistributeTables({AHistory.class})
+ *  public class A {
+ *  }
+ * 
+ * 这样DistributeGenerator将从A、B表中取最大值来初始化主键值。 常见场景就是表B是数据表A对应的历史表 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@Target({TYPE}) +@Retention(RUNTIME) +public @interface DistributeTables { + + Class[] value(); +} diff --git a/src/org/redkale/source/EntityCache.java b/src/org/redkale/source/EntityCache.java new file mode 100644 index 000000000..5d0c0a8c0 --- /dev/null +++ b/src/org/redkale/source/EntityCache.java @@ -0,0 +1,657 @@ +/* + * 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 static org.redkale.source.FilterFunc.*; +import java.util.stream.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Entity类的泛型 + */ +@SuppressWarnings("unchecked") +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 volatile 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 queryColumnMap(final String keyColumn, final FilterFunc func, final String funcColumn, FilterNode node) { + final Attribute keyAttr = info.getAttribute(keyColumn); + final Predicate filter = node == null ? null : node.createPredicate(this); + final Attribute funcAttr = funcColumn == null ? null : info.getAttribute(funcColumn); + Stream stream = this.list.stream(); + if (filter != null) stream = stream.filter(filter); + Collector collector = null; + final Class valtype = funcAttr == null ? null : funcAttr.type(); + switch (func) { + case AVG: + if (valtype == float.class || valtype == Float.class || valtype == double.class || valtype == Double.class) { + collector = (Collector) Collectors.averagingDouble((T t) -> ((Number) funcAttr.get(t)).doubleValue()); + } else { + collector = (Collector) Collectors.averagingLong((T t) -> ((Number) funcAttr.get(t)).longValue()); + } + break; + case COUNT: collector = (Collector) Collectors.counting(); + break; + case DISTINCTCOUNT: + collector = (Collector) Collectors.mapping((t) -> funcAttr.get(t), Collectors.toSet()); + break; + case MAX: + case MIN: + Comparator comp = (o1, o2) -> o1 == null ? (o2 == null ? 0 : -1) : ((Comparable) funcAttr.get(o1)).compareTo(funcAttr.get(o2)); + collector = (Collector) ((func == 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) funcAttr.get(t)).doubleValue()); + } else { + collector = (Collector) Collectors.summingLong((T t) -> ((Number) funcAttr.get(t)).longValue()); + } + break; + } + Map rs = stream.collect(Collectors.groupingBy(t -> keyAttr.get(t), LinkedHashMap::new, collector)); + if (func == MAX || func == MIN) { + Map rs2 = new LinkedHashMap(); + rs.forEach((x, y) -> { + if (((Optional) y).isPresent()) rs2.put(x, funcAttr.get((T) ((Optional) y).get())); + }); + rs = rs2; + } else if (func == DISTINCTCOUNT) { + Map rs2 = new LinkedHashMap(); + rs.forEach((x, y) -> rs2.put(x, ((Set) y).size())); + rs = rs2; + } + return rs; + } + + public Number getNumberResult(final FilterFunc func, 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 (func) { + 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..fd000655d --- /dev/null +++ b/src/org/redkale/source/EntityInfo.java @@ -0,0 +1,381 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Entity类的泛型 + */ +@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 containSQL; //用于反向LIKE使用 + + final String notcontainSQL; //用于反向LIKE使用 + + 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, final Properties conf, + 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, conf); + 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, Properties conf) { + this.type = type; + //--------------------------------------------- + this.nodeid = nodeid >= 0 ? nodeid : 0; + DistributeTables dt = type.getAnnotation(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.class.getSimpleName() + " 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; + } + if (conf == null) conf = new Properties(); + this.containSQL = conf.getProperty("contain-sql-template", "LOCATE(${keystr}, ${column}) > 0"); + this.notcontainSQL = conf.getProperty("notcontain-sql-template", "LOCATE(${keystr}, ${column}) = 0"); + } + + 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..de72030bd --- /dev/null +++ b/src/org/redkale/source/FilterBean.java @@ -0,0 +1,16 @@ +/* + * 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; + +/** + * + *

详情见: http://www.redkale.org + * @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..dd6de780b --- /dev/null +++ b/src/org/redkale/source/FilterColumn.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.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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..ceca71bcc --- /dev/null +++ b/src/org/redkale/source/FilterExpress.java @@ -0,0 +1,63 @@ +/* + * 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; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public enum FilterExpress { + + EQUAL("="), + NOTEQUAL("<>"), + GREATERTHAN(">"), + LESSTHAN("<"), + GREATERTHANOREQUALTO(">="), + LESSTHANOREQUALTO("<="), + + STARTSWITH("LIKE"), + NOTSTARTSWITH("NOT LIKE"), + ENDSWITH("LIKE"), + NOTENDSWITH("NOT LIKE"), + + LIKE("LIKE"), + NOTLIKE("NOT LIKE"), + IGNORECASELIKE("LIKE"), //不区分大小写的 LIKE + IGNORECASENOTLIKE("NOT LIKE"), //不区分大小写的 NOT LIKE + + CONTAIN("CONTAIN"), //包含, 相当于反向LIKE + NOTCONTAIN("NOT CONTAIN"), //不包含, 相当于反向LIKE + IGNORECASECONTAIN("CONTAIN"), //不区分大小写的 CONTAIN + IGNORECASENOTCONTAIN("NOT CONTAIN"), //不区分大小写的 NOT CONTAIN + + BETWEEN("BETWEEN"), + NOTBETWEEN("NOT BETWEEN"), + IN("IN"), + NOTIN("NOT IN"), + ISNULL("IS NULL"), + ISNOTNULL("IS NOT NULL"), + OPAND("&"), //与运算 > 0 + OPOR("|"), //或运算 > 0 + OPANDNO("&"), //与运算 == 0 + FV_MOD("%"), //取模运算,需要与FilterValue配合使用 + FV_DIV("DIV"), //整除运算,需要与FilterValue配合使用 + 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/FilterFunc.java b/src/org/redkale/source/FilterFunc.java new file mode 100644 index 000000000..0f1e689ca --- /dev/null +++ b/src/org/redkale/source/FilterFunc.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.source; + +/** + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +public enum FilterFunc { + AVG, + COUNT, + DISTINCTCOUNT, + MAX, + MIN, + SUM; + + public String getColumn(String col) { + if (this == DISTINCTCOUNT) return "COUNT(DISTINCT " + col + ")"; + return this.name() + "(" + col + ")"; + } +} diff --git a/src/org/redkale/source/FilterGroup.java b/src/org/redkale/source/FilterGroup.java new file mode 100644 index 000000000..315fc1d61 --- /dev/null +++ b/src/org/redkale/source/FilterGroup.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.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") + */ +/** + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface FilterGroup { + + String value() default "[AND]"; +} diff --git a/src/org/redkale/source/FilterJoinColumn.java b/src/org/redkale/source/FilterJoinColumn.java new file mode 100644 index 000000000..9cd57bc9e --- /dev/null +++ b/src/org/redkale/source/FilterJoinColumn.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.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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 AND a.usertype = b.usertype + * 那么注解为: @FilterJoinColumn(table = Record.class, columns = {"userid", "usertype"}) + * + * @return 关联字段 + */ + String[] columns(); +} diff --git a/src/org/redkale/source/FilterJoinNode.java b/src/org/redkale/source/FilterJoinNode.java new file mode 100644 index 000000000..80a374896 --- /dev/null +++ b/src/org/redkale/source/FilterJoinNode.java @@ -0,0 +1,316 @@ +/* + * 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.*; + +/** + * + *

详情见: http://www.redkale.org + * @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..fecb95135 --- /dev/null +++ b/src/org/redkale/source/FilterNode.java @@ -0,0 +1,1256 @@ +/* + * 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; +import java.lang.reflect.Array; +import java.util.*; +import java.util.function.*; +import static org.redkale.source.FilterExpress.*; +import org.redkale.util.Attribute; + +/** + * 注意: 在调用 createSQLExpress 之前必须先调用 createSQLJoin 在调用 createPredicate 之前必须先调用 isCacheUseable + *

+ *

+ * 详情见: http://www.redkale.org + * + * @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 Entity类的泛型 + * @param func EntityInfo的加载器 + * @param joinTabalis 关联表集合 + * @param info Entity类的EntityInfo + * @return SQL的join语句 不存在返回null + */ + 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 EntityInfo的加载器 + * @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 Entity类的泛型 + * @param joinTabalis 关联表的集合 + * @param info EntityInfo + * @return JOIN的SQL语句 + */ + 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()); + } + Object val0 = getValue(); + if (val0 == null) return null; + if (express == FV_MOD || express == FV_DIV) { + FilterValue fv = (FilterValue) val0; + return new StringBuilder().append(info.getSQLColumn(talis, column)).append(' ').append(express.value()).append(' ').append(fv.getOptvalue()) + .append(' ').append(fv.getExpress().value()).append(' ').append(fv.getDestvalue()); + } + CharSequence val = formatToString(express, val0); + if (val == null) return null; + StringBuilder sb = new StringBuilder(32); + if (express == CONTAIN) return info.containSQL.replace("${column}", info.getSQLColumn(talis, column)).replace("${keystr}", val); + if (express == IGNORECASECONTAIN) return info.containSQL.replace("${column}", "LOWER(" + info.getSQLColumn(talis, column) + ")").replace("${keystr}", val); + if (express == NOTCONTAIN) return info.notcontainSQL.replace("${column}", info.getSQLColumn(talis, column)).replace("${keystr}", val); + if (express == IGNORECASENOTCONTAIN) return info.notcontainSQL.replace("${column}", "LOWER(" + info.getSQLColumn(talis, column) + ")").replace("${keystr}", val); + + 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)); + } + + @SuppressWarnings("unchecked") + 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 FV_MOD: + FilterValue fv0 = (FilterValue) val; + switch (fv0.getExpress()) { + case EQUAL: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() % fv0.getOptvalue().longValue()) == fv0.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv0.getOptvalue() + " " + fv0.getExpress().value() + " " + fv0.getDestvalue(); + } + }; + case NOTEQUAL: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() % fv0.getOptvalue().longValue()) != fv0.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv0.getOptvalue() + " " + fv0.getExpress().value() + " " + fv0.getDestvalue(); + } + }; + case GREATERTHAN: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() % fv0.getOptvalue().longValue()) > fv0.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv0.getOptvalue() + " " + fv0.getExpress().value() + " " + fv0.getDestvalue(); + } + }; + case LESSTHAN: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() % fv0.getOptvalue().longValue()) < fv0.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv0.getOptvalue() + " " + fv0.getExpress().value() + " " + fv0.getDestvalue(); + } + }; + case GREATERTHANOREQUALTO: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() % fv0.getOptvalue().longValue()) >= fv0.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv0.getOptvalue() + " " + fv0.getExpress().value() + " " + fv0.getDestvalue(); + } + }; + case LESSTHANOREQUALTO: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() % fv0.getOptvalue().longValue()) <= fv0.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv0.getOptvalue() + " " + fv0.getExpress().value() + " " + fv0.getDestvalue(); + } + }; + default: + throw new RuntimeException("(" + fv0 + ")'s express illegal, must be =, !=, <, >, <=, >="); + } + case FV_DIV: + FilterValue fv1 = (FilterValue) val; + switch (fv1.getExpress()) { + case EQUAL: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() / fv1.getOptvalue().longValue()) == fv1.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv1.getOptvalue() + " " + fv1.getExpress().value() + " " + fv1.getDestvalue(); + } + }; + case NOTEQUAL: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() / fv1.getOptvalue().longValue()) != fv1.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv1.getOptvalue() + " " + fv1.getExpress().value() + " " + fv1.getDestvalue(); + } + }; + case GREATERTHAN: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() / fv1.getOptvalue().longValue()) > fv1.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv1.getOptvalue() + " " + fv1.getExpress().value() + " " + fv1.getDestvalue(); + } + }; + case LESSTHAN: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() / fv1.getOptvalue().longValue()) < fv1.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv1.getOptvalue() + " " + fv1.getExpress().value() + " " + fv1.getDestvalue(); + } + }; + case GREATERTHANOREQUALTO: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() / fv1.getOptvalue().longValue()) >= fv1.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv1.getOptvalue() + " " + fv1.getExpress().value() + " " + fv1.getDestvalue(); + } + }; + case LESSTHANOREQUALTO: + return new Predicate() { + + @Override + public boolean test(T t) { + return (((Number) attr.get(t)).longValue() / fv1.getOptvalue().longValue()) <= fv1.getDestvalue().longValue(); + } + + @Override + public String toString() { + return field + " " + express.value() + " " + fv1.getOptvalue() + " " + fv1.getExpress().value() + " " + fv1.getDestvalue(); + } + }; + default: + throw new RuntimeException("(" + fv1 + ")'s express illegal, must be =, !=, <, >, <=, >="); + } + 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 STARTSWITH: + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs != null && rs.toString().startsWith(val.toString()); + } + + @Override + public String toString() { + return field + " STARTSWITH " + formatToString(val); + } + }; + case ENDSWITH: + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs != null && rs.toString().endsWith(val.toString()); + } + + @Override + public String toString() { + return field + " ENDSWITH " + 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 NOTSTARTSWITH: + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs == null || !rs.toString().startsWith(val.toString()); + } + + @Override + public String toString() { + return field + " NOT STARTSWITH " + formatToString(val); + } + }; + case NOTENDSWITH: + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs == null || !rs.toString().endsWith(val.toString()); + } + + @Override + public String toString() { + return field + " NOT ENDSWITH " + 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 CONTAIN: + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs != null && val.toString().contains(rs.toString()); + } + + @Override + public String toString() { + return "" + formatToString(val) + ' ' + express.value() + ' ' + field; + } + }; + case IGNORECASECONTAIN: + final String valstr3 = val.toString().toLowerCase(); + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs != null && valstr3.contains(rs.toString().toLowerCase()); + } + + @Override + public String toString() { + return "" + formatToString(valstr3) + express.value() + ' ' + "LOWER(" + field + ") "; + } + }; + case NOTCONTAIN: + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs == null || !val.toString().contains(rs.toString()); + } + + @Override + public String toString() { + return "" + formatToString(val) + ' ' + express.value() + ' ' + field; + } + }; + case IGNORECASENOTCONTAIN: + final String valstr4 = val.toString().toLowerCase(); + return new Predicate() { + + @Override + public boolean test(T t) { + Object rs = attr.get(t); + return rs == null || !valstr4.contains(rs.toString().toLowerCase()); + } + + @Override + public String toString() { + return "" + formatToString(valstr4) + express.value() + ' ' + "LOWER(" + field + ") "; + } + }; + 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) { + boolean lower = (express == IGNORECASELIKE || express == IGNORECASENOTLIKE || express == IGNORECASECONTAIN || express == IGNORECASENOTCONTAIN); + sb.append(lower ? ("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 == STARTSWITH || express == NOTSTARTSWITH) { + value = value + "%"; + } else if (express == ENDSWITH || express == NOTENDSWITH) { + value = "%" + value; + } else if (express == IGNORECASELIKE || express == IGNORECASENOTLIKE) { + value = "%" + value.toString().toLowerCase() + '%'; + } else if (express == IGNORECASECONTAIN || express == IGNORECASENOTCONTAIN) { + 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..277c41741 --- /dev/null +++ b/src/org/redkale/source/FilterNodeBean.java @@ -0,0 +1,356 @@ +/* + * 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.*; +import java.util.concurrent.*; +import javax.persistence.*; +import static org.redkale.source.FilterExpress.*; +import org.redkale.util.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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 == null ? null : bean.beanAttr; + this.column = bean == null ? null : bean.column; + this.express = bean == null ? null : bean.express; + this.joinClass = bean == null ? null : bean.joinClass; + this.joinColumns = bean == null ? null : bean.joinColumns; + this.least = bean == null ? 1 : bean.least; + this.string = bean == null ? false : bean.string; + this.number = bean == null ? false : bean.number; + this.or = bean == null ? false : bean.or; + this.nodeBeans = bean == null ? null : 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(); + + 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 || beanAttr == 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(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 !=null && rs.nodeBeans != null) Arrays.sort(rs.nodeBeans); + return rs == null ? new FilterNodeBean(null) : 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 { + boolean lower = (express == IGNORECASELIKE || express == IGNORECASENOTLIKE || express == IGNORECASECONTAIN || express == IGNORECASENOTCONTAIN); + sb.append(lower ? ("LOWER(" + col + ')') : col).append(' ').append(express.value()).append(" ?"); + } + } + return sb; + } +} diff --git a/src/org/redkale/source/FilterValue.java b/src/org/redkale/source/FilterValue.java new file mode 100644 index 000000000..3c7bd642f --- /dev/null +++ b/src/org/redkale/source/FilterValue.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.source; + +/** + * FilterValue主要用于复杂的表达式, 例如: col / 10 = 3 、MOD(col, 8) > 0 这些都不是单独一个数值能表达的,因此需要FilterValue 才构建 8 、 > 、0 组合值. + * + * @author zhangjx + */ +public class FilterValue implements java.io.Serializable { + + private Number optvalue; + + private FilterExpress express; + + private Number destvalue; + + public FilterValue() { + } + + public FilterValue(Number optvalue, Number destvalue) { + this(optvalue, FilterExpress.EQUAL, destvalue); + } + + public FilterValue(Number optvalue, FilterExpress express) { + this(optvalue, express, 0); + } + + public FilterValue(Number optvalue, FilterExpress express, Number destvalue) { + this.optvalue = optvalue; + this.express = express; + this.destvalue = destvalue; + } + + public Number getOptvalue() { + return optvalue == null ? 0 : optvalue; + } + + public void setOptvalue(Number optvalue) { + this.optvalue = optvalue; + } + + public FilterExpress getExpress() { + return express == null ? FilterExpress.EQUAL : express; + } + + public void setExpress(FilterExpress express) { + this.express = express; + } + + public Number getDestvalue() { + return destvalue == null ? 0 : destvalue; + } + + public void setDestvalue(Number destvalue) { + this.destvalue = destvalue; + } + + @Override + public String toString() { + return FilterValue.class.getSimpleName() + "[optvalue=" + getOptvalue() + ", express=" + getExpress() + ", destvalue=" + getDestvalue() + "]"; + } +} diff --git a/src/org/redkale/source/Flipper.java b/src/org/redkale/source/Flipper.java new file mode 100644 index 000000000..f81b73755 --- /dev/null +++ b/src/org/redkale/source/Flipper.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.source; + +import java.io.Serializable; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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; + } + + @Override + @SuppressWarnings("CloneDoesntCallSuperClone") + public Flipper clone() { + return new Flipper(this.size, this.page, this.sort); + } + + 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..cabd2815a --- /dev/null +++ b/src/org/redkale/source/JDBCPoolSource.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.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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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; + + final Properties props; + + public JDBCPoolSource(DataDefaultSource source, String stype, Properties prop) { + this.dataSource = source; + this.stype = stype; + this.props = prop; + 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_CONNECTIONSMAX, "" + 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()); + } + }; + if (this.isOracle()) { + this.props.setProperty(JDBC_CONTAIN_SQLTEMPLATE, "INSTR(${keystr}, ${column}) > 0"); + this.props.setProperty(JDBC_NOTCONTAIN_SQLTEMPLATE, "INSTR(${keystr}, ${column}) = 0"); + } else if (this.isSqlserver()) { + this.props.setProperty(JDBC_CONTAIN_SQLTEMPLATE, "CHARINDEX(${column}, ${keystr}) > 0"); + this.props.setProperty(JDBC_NOTCONTAIN_SQLTEMPLATE, "CHARINDEX(${column}, ${keystr}) = 0"); + } + + try { + this.watch(); + } catch (Exception e) { + dataSource.logger.log(Level.WARNING, DataSource.class.getSimpleName() + " watch " + dataSource.conf + " error", e); + } + } + + final boolean isMysql() { + return source != null && source.getClass().getName().contains(".mysql."); + } + + final boolean isOracle() { + return source != null && source.getClass().getName().contains("oracle."); + } + + final boolean isSqlserver() { + return source != null && source.getClass().getName().contains(".sqlserver."); + } + + 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..21ebee142 --- /dev/null +++ b/src/org/redkale/source/Range.java @@ -0,0 +1,325 @@ +/* + * 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.util.function.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param Comparable的子类型 + */ +public interface Range extends java.io.Serializable, Predicate { + + 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 boolean test(Byte t) { + return t >= min && t <= 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 boolean test(Short t) { + return t >= min && t <= max; + } + + @Override + public String toString() { + return "{min:" + min + ", max:" + max + "}"; + } + } + + public static final class IntRange implements Range { + + private Integer min = Integer.MIN_VALUE; + + private Integer max = Integer.MAX_VALUE; + + public IntRange() { + } + + public IntRange(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 boolean test(Integer t) { + return t >= min && t <= 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 boolean test(Long t) { + return t >= min && t <= 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 boolean test(Float t) { + return t >= min && t <= 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 boolean test(Double t) { + return t >= min && t <= 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) { + if (min != null) this.min = min; + } + + public void setMax(String max) { + if (max != null) this.max = max; + } + + @Override + public boolean test(String t) { + return t.compareTo(min) >= 0 && t.compareTo(max) <= 0; + } + + @Override + public String toString() { + return "{min:'" + min + "', max:'" + max + "'}"; + } + } +} diff --git a/src/org/redkale/source/VirtualEntity.java b/src/org/redkale/source/VirtualEntity.java new file mode 100644 index 000000000..df6c09ace --- /dev/null +++ b/src/org/redkale/source/VirtualEntity.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.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@Documented +@Target(TYPE) +@Retention(RUNTIME) +public @interface VirtualEntity { + +} diff --git a/src/org/redkale/source/package-info.java b/src/org/redkale/source/package-info.java new file mode 100644 index 000000000..47fa2d01a --- /dev/null +++ b/src/org/redkale/source/package-info.java @@ -0,0 +1,4 @@ +/** + * 数据源(数据库、缓存)操作包 + */ +package org.redkale.source; diff --git a/src/org/redkale/util/AnyValue.java b/src/org/redkale/util/AnyValue.java new file mode 100644 index 000000000..275e49dee --- /dev/null +++ b/src/org/redkale/util/AnyValue.java @@ -0,0 +1,446 @@ +/* + * 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; + +/** + * 该类提供类似JSONObject的数据结构,主要用于读取xml配置文件和http-header存储 + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@SuppressWarnings("unchecked") +public abstract class AnyValue { + + /** + * 可读写的AnyValue默认实现类 + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ + @SuppressWarnings("unchecked") + public static final class DefaultAnyValue extends AnyValue { + + public static final BiPredicate EQUALS = new BiPredicate() { //为了兼容Android + @Override + public boolean test(String name1, String name2) { + return name1.equals(name2); + } + }; + + public static final BiPredicate EQUALSIGNORE = new BiPredicate() { //为了兼容Android + @Override + public boolean test(String name1, String name2) { + return 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 static 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(); + } + + protected 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 abstract Entry[] getStringEntrys(); + + public abstract Entry[] getAnyEntrys(); + + public abstract String[] getNames(); + + public abstract String[] getValues(String name); + + public abstract String[] getValues(String... names); + + public abstract AnyValue[] getAnyValues(String name); + + public abstract AnyValue[] getAnyValues(String... names); + + public abstract AnyValue getAnyValue(String name); + + public abstract String getValue(String name); + + public boolean getBoolValue(String name) { + return Boolean.parseBoolean(getValue(name)); + } + + public boolean getBoolValue(String name, boolean defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Boolean.parseBoolean(value); + } + + public byte getByteValue(String name) { + return Byte.parseByte(getValue(name)); + } + + public byte getByteValue(String name, byte defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Byte.decode(value); + } + + public char getCharValue(String name) { + return getValue(name).charAt(0); + } + + public char getCharValue(String name, char defaultValue) { + String value = getValue(name); + return value == null || value.length() == 0 ? defaultValue : value.charAt(0); + } + + public short getShortValue(String name) { + return Short.decode(getValue(name)); + } + + public short getShortValue(String name, short defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Short.decode(value); + } + + public int getIntValue(String name) { + return Integer.decode(getValue(name)); + } + + public int getIntValue(String name, int defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Integer.decode(value); + } + + public long getLongValue(String name) { + return Long.decode(getValue(name)); + } + + public long getLongValue(String name, long defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Long.decode(value); + } + + public float getFloatValue(String name) { + return Float.parseFloat(getValue(name)); + } + + public float getFloatValue(String name, float defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Float.parseFloat(value); + } + + public double getDoubleValue(String name) { + return Double.parseDouble(getValue(name)); + } + + public double getDoubleValue(String name, double defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : Double.parseDouble(value); + } + + public String getValue(String name, String defaultValue) { + String value = getValue(name); + return value == null ? defaultValue : value; + } + +} diff --git a/src/org/redkale/util/AsmMethodVisitor.java b/src/org/redkale/util/AsmMethodVisitor.java new file mode 100644 index 000000000..4ce456855 --- /dev/null +++ b/src/org/redkale/util/AsmMethodVisitor.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.util; + +import java.util.*; +import jdk.internal.org.objectweb.asm.*; + +/** + * MethodVisitor 的调试类 + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +public class AsmMethodVisitor { + + private final MethodVisitor visitor; + + private boolean debug = false; + + public AsmMethodVisitor 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) (Integer) field.get(null)] = name; + } + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + } + + public AsmMethodVisitor(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/Attribute.java b/src/org/redkale/util/Attribute.java new file mode 100644 index 000000000..49c252655 --- /dev/null +++ b/src/org/redkale/util/Attribute.java @@ -0,0 +1,555 @@ +/* + * 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.ArrayList; +import java.util.List; +import jdk.internal.org.objectweb.asm.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +/** + * 该类实现动态映射一个JavaBean类中成员对应的getter、setter方法; 代替低效的反射实现方式。 + *

+ *  public class Record {
+ *
+ *      private String name;
+ *
+ *      public String getName() {
+ *          return name;
+ *      }
+ *
+ *      public void setName(String name) {
+ *          this.name = name;
+ *      }
+ *  }
+ * 
+ * 获取name的 Attribute : + *
+ *  Attribute<Record, String> nameAction = Attribute.create(Record.class.getDeclaredField("name"));
+ * 
+ * 等价于: + *
+ *  Attribute<Record, String> nameAction = new Attribute<Record, String>() {
+ *
+ *      @Override
+ *      public String field() {
+ *          return "name";
+ *      }
+ *
+ *      @Override
+ *      public String get(Record obj) {
+ *          return obj.getName();
+ *      }
+ *
+ *      @Override
+ *      public void set(Record obj, String value) {
+ *          obj.setName(value);
+ *      }
+ *
+ *      @Override
+ *      public Class type() {
+ *          return String.class;
+ *      }
+ *
+ *      @Override
+ *      public Class declaringClass() {
+ *          return Record.class;
+ *      }
+ *  };
+ * 
+ *

+ * 映射Field时,field必须满足以下条件之一:
+ * 1、field属性是public且非final
+ * 2、至少存在对应的getter、setter方法中的一个
+ * 当不存在getter方法时,get操作固定返回null
+ * 当不存在setter方法时,set操作为空方法
+ * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param 字段依附的类 + * @param 字段的数据类型 + */ +public interface Attribute { + + /** + * 返回字段的数据类型 + * + * @return 字段的数据类型 + */ + public Class type(); + + /** + * 返回字段依附的类名 + * + * @return 依附的类名 + */ + public Class declaringClass(); + + /** + * 返回字段名 + * + * @return 字段名 + */ + public String field(); + + /** + * 获取指定对象的该字段的值 + * + * @param obj 指定对象 + * @return 字段的值 + */ + public F get(T obj); + + /** + * 给指定对象的该字段赋值 + * + * @param obj 指定对象 + * @param value 字段新值 + */ + public void set(T obj, F value); + + /** + * 根据一个Field生成 Attribute 对象。 + * + * @param 依附类的类型 + * @param 字段类型 + * @param field 字段,如果该字段不存在则抛异常 + * @return Attribute对象 + */ + @SuppressWarnings("unchecked") + public static Attribute create(final java.lang.reflect.Field field) { + return create((Class) field.getDeclaringClass(), field.getName(), field, null, null); + } + + /** + * 根据一个Field和field的别名生成 Attribute 对象。 + * + * @param 依附类的类型 + * @param 字段类型 + * @param fieldalias 别名 + * @param field 字段,如果该字段不存在则抛异常 + * @return Attribute对象 + */ + @SuppressWarnings("unchecked") + public static Attribute create(String fieldalias, final java.lang.reflect.Field field) { + return create((Class) field.getDeclaringClass(), fieldalias, field, null, null); + } + + /** + * 根据一个Class和field真实名称生成 Attribute 对象。 + * + * @param 依附类的类型 + * @param 字段类型 + * @param clazz 指定依附的类 + * @param fieldname 字段名,如果该字段不存在则抛异常 + * @return Attribute对象 + */ + 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); + } + } + + /** + * 根据一个Class和Field生成 Attribute 对象。 + * + * @param 依附类的类型 + * @param 字段类型 + * @param clazz 指定依附的类 + * @param field 字段,如果该字段不存在则抛异常 + * @return Attribute对象 + */ + public static Attribute create(Class clazz, final java.lang.reflect.Field field) { + return create(clazz, field.getName(), field); + } + + /** + * 根据一个Class、field别名和Field生成 Attribute 对象。 + * + * @param 依附类的类型 + * @param 字段类型 + * @param clazz 指定依附的类 + * @param fieldalias 字段别名 + * @param field 字段,如果该字段不存在则抛异常 + * @return Attribute对象 + */ + public static Attribute create(Class clazz, final String fieldalias, final java.lang.reflect.Field field) { + return create(clazz, fieldalias, field, null, null); + } + + /** + * 根据一个getter和setter方法生成 Attribute 对象。 + * tgetter、setter不能同时为null + * + * @param 依附类的类型 + * @param 字段类型 + * @param getter getter方法 + * @param setter setter方法 + * @return Attribute对象 + */ + @SuppressWarnings("unchecked") + public static Attribute create(final java.lang.reflect.Method getter, final java.lang.reflect.Method setter) { + return create((Class) (getter == null ? setter.getDeclaringClass() : getter.getDeclaringClass()), null, null, getter, setter); + } + + /** + * 根据Class、getter和setter方法生成 Attribute 对象。 + * tgetter、setter不能同时为null + * + * @param 依附类的类型 + * @param 字段类型 + * @param clazz 指定依附的类 + * @param getter getter方法 + * @param setter setter方法 + * @return Attribute对象 + */ + public static Attribute create(Class clazz, final java.lang.reflect.Method getter, final java.lang.reflect.Method setter) { + return create(clazz, null, null, getter, setter); + } + + /** + * 根据Class生成getter、setter方法都存在的字段对应的 Attribute 对象数组。 + * + * @param 依附类的类型 + * @param clazz 指定依附的类 + * @return Attribute对象数组 + */ + public static Attribute[] create(Class clazz) { + List> list = new ArrayList<>(); + for(java.lang.reflect.Field field : clazz.getFields()){ + if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) continue; + if(java.lang.reflect.Modifier.isFinal(field.getModifiers())) continue; + list.add(create(clazz, field)); + } + for(java.lang.reflect.Method setter : clazz.getDeclaredMethods()){ + if(java.lang.reflect.Modifier.isStatic(setter.getModifiers())) continue; + if(!setter.getName().startsWith("set")) continue; + if(setter.getReturnType() != void.class) continue; + if(setter.getParameterCount() != 1) continue; + Class t = setter.getParameterTypes()[0]; + String prefix = t == boolean.class || t == Boolean.class ? "is" : "get"; + java.lang.reflect.Method getter = null; + try { + clazz.getMethod(setter.getName().replaceFirst("set", prefix)); + } catch (Exception e){ + continue; + } + list.add(create(clazz, getter, setter)); + } + return list.toArray(new Attribute[list.size()]); + } + + /** + * 根据Class生成getter方法对应的 Attribute 对象数组。 + * + * @param 依附类的类型 + * @param clazz 指定依附的类 + * @return Attribute对象数组 + */ + public static Attribute[] createGetters(Class clazz) { + List> list = new ArrayList<>(); + for(java.lang.reflect.Field field : clazz.getFields()){ + if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) continue; + if(java.lang.reflect.Modifier.isFinal(field.getModifiers())) continue; + list.add(create(clazz, field)); + } + for(java.lang.reflect.Method method : clazz.getDeclaredMethods()){ + if(java.lang.reflect.Modifier.isStatic(method.getModifiers())) continue; + if(!method.getName().startsWith("get") && !method.getName().startsWith("is")) continue; + if(method.getReturnType() == void.class) continue; + if(method.getParameterCount() != 0) continue; + list.add(create(clazz, method, null)); + } + return list.toArray(new Attribute[list.size()]); + } + + /** + * 根据Class生成setter方法对应的 Attribute 对象数组。 + * + * @param 依附类的类型 + * @param clazz 指定依附的类 + * @return Attribute对象数组 + */ + public static Attribute[] createSetters(Class clazz) { + List> list = new ArrayList<>(); + for(java.lang.reflect.Field field : clazz.getFields()){ + if(java.lang.reflect.Modifier.isStatic(field.getModifiers())) continue; + if(java.lang.reflect.Modifier.isFinal(field.getModifiers())) continue; + list.add(create(clazz, field)); + } + for(java.lang.reflect.Method method : clazz.getDeclaredMethods()){ + if(java.lang.reflect.Modifier.isStatic(method.getModifiers())) continue; + if(!method.getName().startsWith("set")) continue; + if(method.getReturnType() != void.class) continue; + if(method.getParameterCount() != 1) continue; + list.add(create(clazz, null, method)); + } + return list.toArray(new Attribute[list.size()]); + } + + /** + * 根据Class、字段别名、getter和setter方法生成 Attribute 对象。 + * tgetter、setter不能同时为null + * + * @param 依附类的类型 + * @param 字段类型 + * @param clazz 指定依附的类 + * @param fieldalias 字段别名 + * @param getter getter方法 + * @param setter setter方法 + * @return Attribute对象 + */ + public static Attribute create(Class clazz, final String fieldalias, final java.lang.reflect.Method getter, final java.lang.reflect.Method setter) { + return create(clazz, fieldalias, null, getter, setter); + } + + /** + * 根据Class、字段别名、Field、getter和setter方法生成 Attribute 对象。 + * Field、tgetter、setter不能同时为null + * + * @param 依附类的类型 + * @param 字段类型 + * @param clazz 指定依附的类 + * @param fieldalias 字段别名 + * @param field 字段 + * @param getter getter方法 + * @param setter setter方法 + * @return Attribute对象 + */ + @SuppressWarnings("unchecked") + public static Attribute create(final Class clazz, String fieldalias, final java.lang.reflect.Field field, java.lang.reflect.Method getter, java.lang.reflect.Method setter) { + if (fieldalias != null && fieldalias.isEmpty()) fieldalias = null; + int mod = field == null ? java.lang.reflect.Modifier.STATIC : field.getModifiers(); + if (field != null && !java.lang.reflect.Modifier.isStatic(mod) && !java.lang.reflect.Modifier.isPublic(mod)) { + Class t = field.getType(); + char[] fs = field.getName().toCharArray(); + fs[0] = Character.toUpperCase(fs[0]); + String mn = new String(fs); + if (getter == null) { + String prefix = t == boolean.class || t == Boolean.class ? "is" : "get"; + try { + getter = clazz.getMethod(prefix + mn); + } catch (Exception ex) { + } + } + if (setter == null) { + try { + setter = clazz.getMethod("set" + mn, field.getType()); + } catch (Exception ex) { + } + } + } + final java.lang.reflect.Field tfield = field == null ? null : (!java.lang.reflect.Modifier.isPublic(mod) || java.lang.reflect.Modifier.isStatic(mod) ? null : field); + final java.lang.reflect.Method tgetter = getter; + final java.lang.reflect.Method tsetter = setter; + if (fieldalias == null) { + if (field != null) { + fieldalias = field.getName(); + } else { + String s; + if (getter != null) { + s = getter.getName().substring(getter.getName().startsWith("is") ? 2 : 3); + } else { + s = setter.getName().substring(3); + } + char[] d = s.toCharArray(); + if (d.length < 2 || Character.isLowerCase(d[1])) { + d[0] = Character.toLowerCase(d[0]); + } + fieldalias = new String(d); + } + } + if (tgetter == null && tsetter == null && tfield == null) { + throw new RuntimeException("[" + clazz + "]have no public field or setter or getter"); + } + final String fieldname = fieldalias; + Class column; + if (tfield != null) { // public tfield + column = tfield.getType(); + } else if (tgetter != null) { + column = tgetter.getReturnType(); + } else { // tsetter != null + column = tsetter.getParameterTypes()[0]; + } + final Class pcolumn = column; + if (column.isPrimitive()) column = java.lang.reflect.Array.get(java.lang.reflect.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 (Throwable ex) { + } + //--------------------------------------------------- + final ClassWriter cw = new ClassWriter(0); + 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 (tgetter == null) { + if (tfield == null) { + mv.visitInsn(ACONST_NULL); + } else { //public tfield + mv.visitVarInsn(ALOAD, 1); + mv.visitFieldInsn(GETFIELD, interName, tfield.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, tgetter.getName(), Type.getMethodDescriptor(tgetter), 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 (tsetter == null) { + if (tfield == null || java.lang.reflect.Modifier.isFinal(tfield.getModifiers())) { + m = 0; + } else { //public tfield + 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, tfield.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, tsetter.getName(), Type.getMethodDescriptor(tsetter), 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..7c399325b --- /dev/null +++ b/src/org/redkale/util/AutoLoad.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.util; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 自动加载。 使用场景: + * 1、被标记为@AutoLoad(false)的Service类不会被自动加载 + * 2、被标记为@AutoLoad(false)的Servlet类不会被自动加载 + * 3、被标记为@AutoLoad且同时被标记为@javax.persistence.Cacheable的Entity类在被DataSource初始化时需要将Entity类对应的表数据全量加载进缓存中。 + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@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..861faf3f1 --- /dev/null +++ b/src/org/redkale/util/ByteArray.java @@ -0,0 +1,194 @@ +/* + * 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.*; +import java.util.*; + +/** + * 简单的byte[]操作类。 + * + *

+ * 详情见: http://www.redkale.org + * + * @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 size() { + return count; + } + + public byte get(int index) { + return content[index]; + } + + public byte getLastByte() { + return content[count - 1]; + } + + public void copyTo(byte[] buf) { + System.arraycopy(this.content, 0, buf, 0, count); + } + + public byte[] directBytes() { + return content; + } + + public byte[] getBytes() { + return Arrays.copyOf(content, count); + } + + public byte[] getBytesAndClear() { + byte[] bs = Arrays.copyOf(content, count); + clear(); + return bs; + } + + 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 writeInt(int value) { + write((byte) (value >> 24 & 0xFF), (byte) (value >> 16 & 0xFF), (byte) (value >> 8 & 0xFF), (byte) (value & 0xFF)); + } + + public void write(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 write(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 write(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 toStringAndClear(final Charset charset) { + String str = toString(0, count, charset); + clear(); + return str; + } + + 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++) { + switch (content[i]) { + case '+': + content[index] = ' '; + break; + case '%': + content[index] = (byte) ((hexBit(content[++i]) * 16 + hexBit(content[++i]))); + break; + default: + content[index] = content[i]; + break; + } + 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..cd317cb62 --- /dev/null +++ b/src/org/redkale/util/Creator.java @@ -0,0 +1,358 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.util; + +import java.beans.*; +import java.io.*; +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import jdk.internal.org.objectweb.asm.*; +import jdk.internal.org.objectweb.asm.Type; +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +/** + *

+ * 实现一个类的构造方法。 代替低效的反射实现方式。 不支持数组类。 + * 常见的无参数的构造函数类都可以自动生成Creator, 对应自定义的类可以提供一个静态构建Creator方法。 + * 例如: + *

+ * public class Record {
+ *
+ *    private final int id;
+ *
+ *    private String name;
+ *
+ *    Record(int id, String name) {
+ *        this.id = id;
+ *        this.name = name;
+ *    }
+ *
+ *    private static Creator createCreator() {
+ *        return new Creator<Record>() {
+ *            @Override
+ *            @ConstructorParameters({"id", "name"})
+ *            public Record create(Object... params) {
+ *                if(params[0] == null) params[0] = 0;
+ *                return new Record((Integer) params[0], (String) params[1]);
+ *            }
+ *         };
+ *    }
+ * }
+ * 
+ * + * 或者: + *
+ * public class Record {
+ *
+ *    private final int id;
+ *
+ *    private String name;
+ *
+ *    @java.beans.ConstructorProperties({"id", "name"})
+ *    public Record(int id, String name) {
+ *        this.id = id;
+ *        this.name = name;
+ *    }
+ * }
+ * 
+ * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param 构建对象的数据类型 + */ +public interface Creator { + + /** + * 该注解只用于Creator.create方法上, 与 java.beans.ConstructorProperties 类似。 + * + */ + @Documented + @Target({METHOD}) + @Retention(RUNTIME) + public static @interface ConstructorParameters { + + String[] value(); + } + + /** + * 创建对象 + * + * @param params 构造函数的参数 + * @return 构建的对象 + */ + public T create(Object... params); + + /** + * 根据指定的class采用ASM技术生产Creator。 + * + * @param 构建类的数据类型 + * @param clazz 构建类 + * @return Creator对象 + */ + @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."); + } + for (final Method method : clazz.getDeclaredMethods()) { + if (!Modifier.isStatic(method.getModifiers())) continue; + if (method.getParameterTypes().length != 0) continue; + if (method.getReturnType() != Creator.class) continue; + try { + method.setAccessible(true); + return (Creator) method.invoke(null); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + 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 constructor0 = null; + for (Constructor c : clazz.getConstructors()) { //优先找public 的构造函数 + if (c.getParameterCount() == 0) { + constructor0 = c; + break; + } + } + if (constructor0 == null) {//其次找非private带ConstructorProperties的构造函数 + for (Constructor c : clazz.getDeclaredConstructors()) { + if (Modifier.isPrivate(c.getModifiers())) continue; + if (c.getAnnotation(ConstructorProperties.class) != null) { + constructor0 = c; + break; + } + } + } + if (constructor0 == null) {//再次找非private且带-parameters编译项的构造函数 java 8以上才支持 + for (Constructor c : clazz.getDeclaredConstructors()) { + if (Modifier.isPrivate(c.getModifiers())) continue; + Parameter[] params = c.getParameters(); + if (params.length == 0) continue; + boolean flag = true; + for (Parameter param : params) { + try { + clazz.getDeclaredField(param.getName()); + } catch (Exception e) { + flag = false; + break; + } + } + if (flag) { + constructor0 = c; + break; + } + } + } + if (constructor0 == null) {//最后找非private的空构造函数 + for (Constructor c : clazz.getDeclaredConstructors()) { + if (Modifier.isPrivate(c.getModifiers())) continue; + if (c.getParameterCount() == 0) { + constructor0 = c; + break; + } + } + } + final Constructor constructor = constructor0; + if (constructor == null) throw new RuntimeException("[" + clazz + "] have no public or java.beans.ConstructorProperties-Annotation constructor."); + //------------------------------------------------------------- + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + 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); + 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); + ConstructorProperties cps = constructor.getAnnotation(ConstructorProperties.class); + String[] cparams = cps == null ? null : cps.value(); + if (cparams == null && constructor.getParameterCount() > 0) { // java 8 以上版本才支持的 -parameters 编译项 + Parameter[] params = constructor.getParameters(); + String[] ss = new String[params.length]; + for (int i = 0; i < ss.length; i++) { + try { + clazz.getDeclaredField(params[i].getName()); + } catch (Exception e) { //没有该字段,直接退出 + ss = null; + break; + } + ss[i] = params[i].getName(); + } + cparams = ss; + } + if (cparams != null) { + av0 = mv.visitAnnotation(Type.getDescriptor(ConstructorParameters.class), true); + AnnotationVisitor av1 = av0.visitArray("value"); + for (String n : cparams) { + av1.visit(null, n); + } + av1.visitEnd(); + av0.visitEnd(); + } + final Class[] paramTypes = constructor.getParameterTypes(); + final int[] iconsts = {ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5}; + { //有Primitive数据类型且值为null的参数需要赋默认值 + for (int i = 0; i < paramTypes.length; i++) { + final Class pt = paramTypes[i]; + if (!pt.isPrimitive()) continue; + mv.visitVarInsn(ALOAD, 1); + if (i < 6) { + mv.visitInsn(iconsts[i]); + } else { + mv.visitIntInsn(BIPUSH, i); + } + mv.visitInsn(AALOAD); + Label lab = new Label(); + mv.visitJumpInsn(IFNONNULL, lab); + mv.visitVarInsn(ALOAD, 1); + if (i < 6) { + mv.visitInsn(iconsts[i]); + } else { + mv.visitIntInsn(BIPUSH, i); + } + if (pt == int.class) { + mv.visitInsn(ICONST_0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); + } else if (pt == long.class) { + mv.visitInsn(LCONST_0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + } else if (pt == boolean.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;"); + } else if (pt == short.class) { + mv.visitInsn(ICONST_0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false); + } else if (pt == float.class) { + mv.visitInsn(FCONST_0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); + } else if (pt == byte.class) { + mv.visitInsn(ICONST_0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false); + } else if (pt == double.class) { + mv.visitInsn(DCONST_0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); + } else if (pt == char.class) { + mv.visitInsn(ICONST_0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false); + } + mv.visitInsn(AASTORE); + mv.visitLabel(lab); + } + } + mv.visitTypeInsn(NEW, interName); + mv.visitInsn(DUP); + //--------------------------------------- + { + for (int i = 0; i < paramTypes.length; i++) { + mv.visitVarInsn(ALOAD, 1); + if (i < 6) { + mv.visitInsn(iconsts[i]); + } else { + mv.visitIntInsn(BIPUSH, i); + } + mv.visitInsn(AALOAD); + final Class ct = paramTypes[i]; + if (ct.isPrimitive()) { + final Class bigct = Array.get(Array.newInstance(ct, 1), 0).getClass(); + mv.visitTypeInsn(CHECKCAST, bigct.getName().replace('.', '/')); + try { + Method pm = bigct.getMethod(ct.getSimpleName() + "Value"); + mv.visitMethodInsn(INVOKEVIRTUAL, bigct.getName().replace('.', '/'), pm.getName(), Type.getMethodDescriptor(pm), false); + } catch (Exception ex) { + throw new RuntimeException(ex); //不可能会发生 + } + } else { + mv.visitTypeInsn(CHECKCAST, ct.getName().replace('.', '/')); + } + } + } + //--------------------------------------- + mv.visitMethodInsn(INVOKESPECIAL, interName, "", Type.getConstructorDescriptor(constructor), false); + mv.visitInsn(ARETURN); + mv.visitMaxs((paramTypes.length > 0 ? (paramTypes.length + 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(); + final byte[] bytes = cw.toByteArray(); + final boolean ispub = Modifier.isPublic(constructor.getModifiers()); + Class resultClazz = null; + if (loader instanceof URLClassLoader && !ispub) { + try { + final URLClassLoader urlLoader = (URLClassLoader) loader; + final URL url = new URL("memclass", "localhost", -1, "/" + newDynName.replace('/', '.') + "/", new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) throws IOException { + return new URLConnection(u) { + @Override + public void connect() throws IOException { + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(bytes); + } + }; + } + }); + Method addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + addURLMethod.setAccessible(true); + addURLMethod.invoke(urlLoader, url); + resultClazz = urlLoader.loadClass(newDynName.replace('/', '.')); + } catch (Throwable t) { //异常无需理会, 使用下一种loader方式 + t.printStackTrace(); + } + } + if (!ispub && resultClazz == null) throw new RuntimeException("[" + clazz + "] have no public or java.beans.ConstructorProperties-Annotation constructor."); + try { + if (resultClazz == null) resultClazz = new ClassLoader(loader) { + public final Class loadClass(String name, byte[] b) { + return defineClass(name, b, 0, b.length); + } + }.loadClass(newDynName.replace('/', '.'), bytes); + return (Creator) resultClazz.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..d3714f044 --- /dev/null +++ b/src/org/redkale/util/DLong.java @@ -0,0 +1,119 @@ +/* + * 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.util.*; + +/** + * 16bytes数据结构 + * 注意: 为了提高性能, DLong中的bytes是直接返回, 不得对bytes的内容进行修改。 + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public final class DLong extends Number implements Comparable { + + public static final DLong ZERO = new DLong(new byte[16]); + + protected final byte[] value; + + protected DLong(long v1, long v2) { //暂时不用 + this.value = new byte[]{(byte) (v1 >> 56), (byte) (v1 >> 48), (byte) (v1 >> 40), (byte) (v1 >> 32), + (byte) (v1 >> 24), (byte) (v1 >> 16), (byte) (v1 >> 8), (byte) v1, (byte) (v2 >> 56), (byte) (v2 >> 48), (byte) (v2 >> 40), (byte) (v2 >> 32), + (byte) (v2 >> 24), (byte) (v2 >> 16), (byte) (v2 >> 8), (byte) v2}; + } + + protected DLong(byte[] bytes) { + if (bytes == null || bytes.length != 16) throw new NumberFormatException("Not 16 length bytes"); + this.value = bytes; + } + + public byte[] getBytes() { + return Arrays.copyOf(value, value.length); + } + + public byte[] directBytes() { + return value; + } + + public static DLong create(byte[] bytes) { + return new DLong(bytes); + } + + public static DLong read(ByteBuffer buffer) { + byte[] bs = new byte[16]; + buffer.get(bs); + return new DLong(bs); + } + + public static ByteBuffer write(ByteBuffer buffer, DLong dlong) { + buffer.put(dlong.value); + return buffer; + } + + public boolean equals(byte[] bytes) { + return Arrays.equals(this.value, bytes); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final DLong other = (DLong) obj; + return Arrays.equals(this.value, other.value); + } + + @Override + public int hashCode() { + return Arrays.hashCode(value); + } + + @Override + public String toString() { + if (this == ZERO) return "0"; + return new String(Utility.binToHex(value)); + } + + @Override + public int intValue() { + return ((value[12] & 0xff) << 24) | ((value[113] & 0xff) << 16) | ((value[14] & 0xff) << 8) | (value[15] & 0xff); + } + + @Override + public long longValue() { + return ((((long) value[8] & 0xff) << 56) + | (((long) value[9] & 0xff) << 48) + | (((long) value[10] & 0xff) << 40) + | (((long) value[11] & 0xff) << 32) + | (((long) value[12] & 0xff) << 24) + | (((long) value[13] & 0xff) << 16) + | (((long) value[14] & 0xff) << 8) + | (((long) value[15] & 0xff))); + } + + @Override + public float floatValue() { + return (float) longValue(); + } + + @Override + public double doubleValue() { + return (double) longValue(); + } + + @Override + public int compareTo(DLong o) { + if (o == null) return 1; + for (int i = 0; i < value.length; i++) { + if (this.value[i] != o.value[i]) return this.value[i] - o.value[i]; + } + return 0; + } + +} diff --git a/src/org/redkale/util/LogLevel.java b/src/org/redkale/util/LogLevel.java new file mode 100644 index 000000000..de2ee27bf --- /dev/null +++ b/src/org/redkale/util/LogLevel.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.util; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 被标记的日志级别以上的才会被记录 + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +@Inherited +@Documented +@Target({TYPE}) +@Retention(RUNTIME) +public @interface LogLevel { + + String value(); +} diff --git a/src/org/redkale/util/ObjectPool.java b/src/org/redkale/util/ObjectPool.java new file mode 100644 index 000000000..a7636a4ae --- /dev/null +++ b/src/org/redkale/util/ObjectPool.java @@ -0,0 +1,102 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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..99199f8b8 --- /dev/null +++ b/src/org/redkale/util/Reproduce.java @@ -0,0 +1,141 @@ +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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + * @param 目标对象的数据类型 + * @param 源对象的数据类型 + */ +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.getParameterTypes().length > 0) continue; //为了兼容android 而不使用 getParameterCount() + 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..3aafaad14 --- /dev/null +++ b/src/org/redkale/util/ResourceFactory.java @@ -0,0 +1,451 @@ +/* + * 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.ref.*; +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 + * + *

+ * 详情见: http://www.redkale.org + * + * @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 loadermap = new ConcurrentHashMap(); + + private final ConcurrentHashMap> store = 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 A register(final Class clazz, final A rs) { + return register(true, clazz, rs); + } + + public A register(final boolean autoSync, final Class clazz, final A rs) { + return register(autoSync, "", clazz, rs); + } + + public A register(final A rs) { + return register(true, rs); + } + + public A register(final boolean autoSync, final A rs) { + if (rs == null) return null; + return (A) register(autoSync, "", rs.getClass(), rs); + } + + public void add(final Type clazz, final ResourceLoader rs) { + if (clazz == null || rs == null) return; + loadermap.put(clazz, rs); + } + + public void register(final String name, final boolean value) { + register(true, name, boolean.class, value); + } + + public void register(final boolean autoSync, final String name, final boolean value) { + register(autoSync, name, boolean.class, value); + } + + public void register(final String name, final byte value) { + register(true, name, byte.class, value); + } + + public void register(final boolean autoSync, final String name, final byte value) { + register(autoSync, name, byte.class, value); + } + + public void register(final String name, final short value) { + register(true, name, short.class, value); + } + + public void register(final boolean autoSync, final String name, final short value) { + register(autoSync, name, short.class, value); + } + + public void register(final String name, final int value) { + register(true, name, int.class, value); + } + + public void register(final boolean autoSync, final String name, final int value) { + register(autoSync, name, int.class, value); + } + + public void register(final String name, final float value) { + register(true, name, float.class, value); + } + + public void register(final boolean autoSync, final String name, final float value) { + register(autoSync, name, float.class, value); + } + + public void register(final String name, final long value) { + register(true, name, long.class, value); + } + + public void register(final boolean autoSync, final String name, final long value) { + register(autoSync, name, long.class, value); + } + + public void register(final String name, final double value) { + register(true, name, double.class, value); + } + + public void register(final boolean autoSync, final String name, final double value) { + register(autoSync, name, double.class, value); + } + + public A register(final String name, final A rs) { + return register(true, name, rs); + } + + public A register(final boolean autoSync, final String name, final A rs) { + final Class claz = rs.getClass(); + ResourceType rtype = claz.getAnnotation(ResourceType.class); + if (rtype == null) { + return (A) register(autoSync, name, claz, rs); + } else { + A old = null; + for (Class cl : rtype.value()) { + A t = (A) register(autoSync, name, cl, rs); + if (t != null) old = t; + } + return old; + } + } + + public A register(final String name, final Class clazz, final A rs) { + return register(true, name, clazz, rs); + } + + public A register(final String name, final Type clazz, final A rs) { + return register(true, name, clazz, rs); + } + + public A register(final boolean autoSync, final String name, final Type clazz, final A rs) { + ConcurrentHashMap map = this.store.get(clazz); + if (map == null) { + ConcurrentHashMap sub = new ConcurrentHashMap(); + sub.put(name, new ResourceEntry(rs)); + store.put(clazz, sub); + return null; + } else { + ResourceEntry re = map.get(name); + if (re == null) { + map.put(name, new ResourceEntry(rs)); + } else { + map.put(name, new ResourceEntry(rs, re.elements, autoSync)); + } + return re == null ? null : (A) re.value; + } + } + + public A find(Class clazz) { + return find("", clazz); + } + + public A find(String name, Type clazz) { + ResourceEntry re = findEntry(name, clazz); + return re == null ? null : (A) re.value; + } + + private ResourceEntry findEntry(String name, Type clazz) { + Map map = this.store.get(clazz); + if (map != null) { + ResourceEntry re = map.get(name); + if (re != null) return re; + } + if (parent != null) return parent.findEntry(name, clazz); + return null; + } + + public List query(Class clazz) { + return query(new ArrayList(), clazz); + } + + public List query(Type clazz) { + return query(new ArrayList(), clazz); + } + + private List query(final List list, Type clazz) { + Map map = this.store.get(clazz); + if (map != null) { + for (ResourceEntry re : map.values()) { + if (re.value != null) list.add((A) re.value); + } + } + if (parent != null) query(list, clazz); + return list; + } + + public A find(String name, Class clazz) { + ResourceEntry re = findEntry(name, clazz); + return re == null ? null : re.value; + } + + private ResourceEntry findEntry(String name, Class clazz) { + Map map = this.store.get(clazz); + if (map != null) { + ResourceEntry rs = map.get(name); + if (rs != null) return rs; + } + if (parent != null) return parent.findEntry(name, clazz); + return null; + } + + public A findChild(final String name, final Class clazz) { + A rs = find(name, clazz); + if (rs != null) return rs; + for (Map.Entry> en : this.store.entrySet()) { //不用forEach为兼容JDK 6 + if (!(en.getKey() instanceof Class)) continue; + if (!clazz.isAssignableFrom((Class) en.getKey())) continue; + ResourceEntry v = en.getValue().get(name); + if (v != null) return (A) v.value; + } + return null; + } + + //Map无法保证ResourceEntry的自动同步, 暂时不提供该功能 + @Deprecated + private 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) { + for (Map.Entry en : map.entrySet()) { // 不用forEach为兼容JDK 6 + String x = en.getKey(); + ResourceEntry re = en.getValue(); + if (re == null) continue; + Object y = re.value; + 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; + String tname = rc.name(); + if (tname.contains(RESOURCE_PARENT_NAME)) { + try { + Resource res = src.getClass().getAnnotation(Resource.class); + if (res == null) { + String srcname = (String) src.getClass().getMethod("name").invoke(src); + tname = tname.replace(RESOURCE_PARENT_NAME, srcname); + } else { + tname = res.name(); + } + } catch (Exception e) { // 获取src中的name()方法的值, 异常则忽略 + logger.log(Level.SEVERE, src.getClass().getName() + " not found @Resource on Class or [public String name()] method", e); + } + } + final String rcname = tname; + ResourceEntry re = findEntry(rcname, genctype); + if (re == null) { +// if (Map.class.isAssignableFrom(classtype)) { +// Map map = find(Pattern.compile(rcname.isEmpty() ? ".*" : rcname), (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[1], src); +// if (map != null) re = new ResourceEntry(map); +// } else + if (rcname.startsWith("property.")) { + re = findEntry(rcname, String.class); + } else { + re = findEntry(rcname, classtype); + } + } + if (re == null) { + ResourceLoader it = findLoader(field.getGenericType(), field); + if (it != null) { + it.load(this, src, rcname, field, attachment); + re = findEntry(rcname, genctype); + } + } + if (re == null) { + register(rcname, genctype, null); //自动注入null的值 + re = findEntry(rcname, genctype); + } + if (re == null) continue; + re.elements.add(new ResourceElement<>(src, field)); + + Object rs = re.value; + if (rs != null && !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()); + } + } + if (rs != null) field.set(src, rs); + } + } while ((clazz = clazz.getSuperclass()) != Object.class); + return true; + } catch (Exception ex) { + logger.log(Level.SEVERE, "inject " + src + " error", ex); + return false; + } + } + + private ResourceLoader findLoader(Type ft, Field field) { + ResourceLoader it = this.loadermap.get(ft); + if (it != null) return it; + Class c = field.getType(); + for (Map.Entry en : this.loadermap.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.findLoader(ft, field); + } + + private static class ResourceEntry { + + public final T value; + + public final List elements; + + public ResourceEntry(T value) { + this.value = value; + this.elements = new CopyOnWriteArrayList<>(); + } + + public ResourceEntry(T value, final List elements, boolean sync) { + this.value = value; + this.elements = elements == null ? new CopyOnWriteArrayList<>() : elements; + if (sync && elements != null && !elements.isEmpty()) { + + for (ResourceElement element : elements) { + Object dest = element.dest.get(); + if (dest == null) continue; + Object rs = value; + final Class classtype = element.fieldType; + if (rs != null && !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()); + } + } + if (rs == null && classtype.isPrimitive()) rs = Array.get(Array.newInstance(classtype, 1), 0); + try { + element.field.set(dest, rs); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + } + } + + private static class ResourceElement { + + public final WeakReference dest; + + public final Field field; + + public final Class fieldType; + + public ResourceElement(T dest, Field field) { + this.dest = new WeakReference(dest); + this.field = field; + this.fieldType = field.getType(); + } + } + + @FunctionalInterface + public static interface ResourceLoader { + + public void load(ResourceFactory factory, Object src, String resourceName, Field field, Object attachment); + } + +} diff --git a/src/org/redkale/util/ResourceType.java b/src/org/redkale/util/ResourceType.java new file mode 100644 index 000000000..3cb19031c --- /dev/null +++ b/src/org/redkale/util/ResourceType.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.lang.annotation.*; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * 显式的指明资源类型 + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({TYPE}) +@Retention(RUNTIME) +public @interface ResourceType { + + Class[] value(); +} diff --git a/src/org/redkale/util/SelectColumn.java b/src/org/redkale/util/SelectColumn.java new file mode 100644 index 000000000..a1bbfaa77 --- /dev/null +++ b/src/org/redkale/util/SelectColumn.java @@ -0,0 +1,139 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @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 SelectColumn + */ + public static SelectColumn createIncludes(String... columns) { + return new SelectColumn(columns, false); + } + + /** + * class中的字段名 + * + * @param columns 排除的字段名集合 + * @return SelectColumn + */ + 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..51319510e --- /dev/null +++ b/src/org/redkale/util/Sheet.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.util; + +import java.util.*; + +/** + * 页集合。 结构由一个total总数和一个List列表组合而成。 + * + *

+ * 详情见: http://www.redkale.org + * + * @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..1d5f5c806 --- /dev/null +++ b/src/org/redkale/util/TypeToken.java @@ -0,0 +1,192 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.util; + +import java.lang.reflect.Type; +import java.lang.reflect.*; +import java.util.*; +import jdk.internal.org.objectweb.asm.*; +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +/** + * + * 获取泛型的Type类 + * + *

+ * 详情见: http://www.redkale.org + * + * @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; + } + + /** + * 判断Type是否能确定最终的class, 是则返回true,存在通配符或者不确定类型则返回false。 + * 例如: Map< String, String > 返回 ture; Map< ? extends Serializable, String > 返回false; + * + * @param type Type对象 + * @return 是否可反解析 + */ + public final static boolean isClassType(final Type type) { + if (type instanceof Class) return true; + if (type instanceof WildcardType) return false; + if (type instanceof TypeVariable) return false; + if (type instanceof GenericArrayType) return isClassType(((GenericArrayType) type).getGenericComponentType()); + if (!(type instanceof ParameterizedType)) return false; //只能是null了 + final ParameterizedType ptype = (ParameterizedType) type; + if (ptype.getOwnerType() != null && !isClassType(ptype.getOwnerType())) return false; + if (!isClassType(ptype.getRawType())) return false; + for (Type t : ptype.getActualTypeArguments()) { + if (!isClassType(t)) return false; + } + return true; + } + + /** + * 动态创建 ParameterizedType + * + * @param ownerType0 ParameterizedType 的 ownerType + * @param rawType0 ParameterizedType 的 rawType + * @param actualTypeArguments0 ParameterizedType 的 actualTypeArguments + * @return Type + */ + public static Type createParameterizedType(final Type ownerType0, final Type rawType0, final Type... actualTypeArguments0) { + if (ownerType0 == null && rawType0 instanceof Class) { + int count = 0; + for (Type t : actualTypeArguments0) { + if (isClassType(t)) count++; + } + if (count == actualTypeArguments0.length) return createParameterizedType((Class) rawType0, actualTypeArguments0); + } + return new ParameterizedType() { + private final Class rawType = (Class) rawType0; + + private final Type ownerType = ownerType0; + + private final Type[] actualTypeArguments = actualTypeArguments0; + + @Override + public Type[] getActualTypeArguments() { + return actualTypeArguments.clone(); + } + + @Override + public Type getRawType() { + return rawType; + } + + @Override + public Type getOwnerType() { + return ownerType; + } + + @Override + public int hashCode() { + return Arrays.hashCode(actualTypeArguments) ^ Objects.hashCode(rawType) ^ Objects.hashCode(ownerType); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ParameterizedType)) return false; + final ParameterizedType that = (ParameterizedType) o; + if (this == that) return true; + return Objects.equals(ownerType, that.getOwnerType()) + && Objects.equals(rawType, that.getRawType()) + && Arrays.equals(actualTypeArguments, that.getActualTypeArguments()); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (ownerType != null) sb.append((ownerType instanceof Class) ? (((Class) ownerType).getName()) : ownerType.toString()).append("."); + sb.append(rawType.getName()); + + if (actualTypeArguments != null && actualTypeArguments.length > 0) { + sb.append("<"); + boolean first = true; + for (Type t : actualTypeArguments) { + if (!first) sb.append(", "); + sb.append(t); + first = false; + } + sb.append(">"); + } + return sb.toString(); + } + }; + } + + private static Type createParameterizedType(final Class rawType, final Type... actualTypeArguments) { + ClassLoader loader = TypeToken.class.getClassLoader(); + String newDynName = TypeToken.class.getName().replace('.', '/') + "_Dyn" + System.currentTimeMillis(); + for (;;) { + try { + Class.forName(newDynName.replace('/', '.')); + newDynName = TypeToken.class.getName().replace('.', '/') + "_Dyn" + Math.abs(System.nanoTime()); + } catch (Exception ex) { //异常说明类不存在 + break; + } + } + ClassWriter cw = new ClassWriter(0); + FieldVisitor fv; + MethodVisitor mv; + cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, "java/lang/Object", null); + String rawTypeDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(rawType); + StringBuilder sb = new StringBuilder(); + sb.append(rawTypeDesc.substring(0, rawTypeDesc.length() - 1)).append('<'); + for (Type c : actualTypeArguments) { + sb.append(getClassTypeDescriptor(c)); + } + sb.append(">;"); + { + fv = cw.visitField(ACC_PUBLIC, "field", rawTypeDesc, sb.toString(), null); + fv.visitEnd(); + } + {//构造方法 + 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(); + } + 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 { + return newClazz.getField("field").getGenericType(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private static CharSequence getClassTypeDescriptor(Type type) { + if (!isClassType(type)) throw new IllegalArgumentException(type + " not a class type"); + if (type instanceof Class) return jdk.internal.org.objectweb.asm.Type.getDescriptor((Class) type); + final ParameterizedType pt = (ParameterizedType) type; + CharSequence rawTypeDesc = getClassTypeDescriptor(pt.getRawType()); + StringBuilder sb = new StringBuilder(); + sb.append(rawTypeDesc.subSequence(0, rawTypeDesc.length() - 1)).append('<'); + for (Type c : pt.getActualTypeArguments()) { + sb.append(getClassTypeDescriptor(c)); + } + sb.append(">;"); + return sb; + } +} diff --git a/src/org/redkale/util/Utility.java b/src/org/redkale/util/Utility.java new file mode 100644 index 000000000..304c9a251 --- /dev/null +++ b/src/org/redkale/util/Utility.java @@ -0,0 +1,578 @@ +/* + * 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.*; + +/** + * + *

+ * 详情见: http://www.redkale.org + * + * @author zhangjx + */ +public final class Utility { + + private static final int zoneRawOffset = TimeZone.getDefault().getRawOffset(); + + 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 f = String.class.getDeclaredField("value"); + if (f.getType() == char[].class) { //JDK9及以上不再是char[] + Field safeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + safeField.setAccessible(true); + usafe = (sun.misc.Unsafe) safeField.get(null); + fd1 = usafe.objectFieldOffset(f); + 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 IPv4地址 + */ + 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; + } + + /** + * 获取当天凌晨零点的格林时间 + * + * @return 毫秒数 + */ + public static long midnight() { + return midnight(System.currentTimeMillis()); + } + + /** + * 获取指定时间当天凌晨零点的格林时间 + * + * @param time 指定时间 + * @return 毫秒数 + */ + public static long midnight(long time) { + return (time + zoneRawOffset) / 86400000 * 86400000 - zoneRawOffset; + } + + /** + * 获取当天20151231格式的int值 + * + * @return 20151231格式的int值 + */ + public static int today() { + java.time.LocalDate today = java.time.LocalDate.now(); + return today.getYear() * 10000 + today.getMonthValue() * 100 + today.getDayOfMonth(); + } + + /** + * 获取时间点所在星期的周一 + * + * @param time 指定时间 + * @return 毫秒数 + */ + 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(); + } + + /** + * 获取时间点所在星期的周日 + * + * @param time 指定时间 + * @return 毫秒数 + */ + 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号 + * + * @param time 指定时间 + * @return 毫秒数 + */ + 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(String str) { + return hexToBin(charArray(str)); + } + + 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]; + if (UNSAFE == null) return encodeUTF8(value.toCharArray()); + 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) { + if (value == null) return null; + if (UNSAFE == null) return value.toCharArray(); + return (char[]) UNSAFE.getObject(value, strvaloffset); + } + + public static char[] charArray(StringBuilder value) { + if (value == null) return null; + if (UNSAFE == null) return value.toString().toCharArray(); + return (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; + if (UNSAFE == null) return encodeUTF8Length(value.toCharArray()); + 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 long值 + */ + public static long merge(int high, int low) { + return (0L + 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) { + for (Map.Entry en : headers.entrySet()) { //不用forEach是为了兼容JDK 6 + conn.setRequestProperty(en.getKey(), en.getValue()); + } + } + 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/util/package-info.java b/src/org/redkale/util/package-info.java new file mode 100644 index 000000000..27728cf6c --- /dev/null +++ b/src/org/redkale/util/package-info.java @@ -0,0 +1,4 @@ +/** + * RedKale工具包 + */ +package org.redkale.util; diff --git a/src/org/redkale/watch/WatchFactory.java b/src/org/redkale/watch/WatchFactory.java new file mode 100644 index 000000000..6d491636e --- /dev/null +++ b/src/org/redkale/watch/WatchFactory.java @@ -0,0 +1,100 @@ +/* + * 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; + +/** + * + *

详情见: http://www.redkale.org + * @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..f7dffc461 --- /dev/null +++ b/src/org/redkale/watch/WatchNode.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.watch; + +/** + * + *

详情见: http://www.redkale.org + * @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..bd45ae97f --- /dev/null +++ b/src/org/redkale/watch/WatchNumber.java @@ -0,0 +1,52 @@ +/* + * 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.beans.*; +import java.util.concurrent.atomic.*; + +/** + * + *

详情见: http://www.redkale.org + * @author zhangjx + */ +public final class WatchNumber extends AtomicLong implements WatchNode { + + private final boolean interval; + + private final String name; + + private final String description; + + @ConstructorProperties({"name", "description", "interval", "value"}) + protected WatchNumber(String name, String description, boolean interval, long value) { + this.name = name; + this.description = description; + this.interval = interval; + this.set(value); + } + + @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..6cbf515c9 --- /dev/null +++ b/src/org/redkale/watch/WatchSupplier.java @@ -0,0 +1,48 @@ +/* + * 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; + +/** + * + *

详情见: http://www.redkale.org + * @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..fcf1e3bc6 --- /dev/null +++ b/src/org/redkale/watch/Watchable.java @@ -0,0 +1,37 @@ +/* + * 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类); + * + *

+ * 详情见: http://www.redkale.org + * + * @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; +} diff --git a/src/org/redkale/watch/package-info.java b/src/org/redkale/watch/package-info.java new file mode 100644 index 000000000..85f884f16 --- /dev/null +++ b/src/org/redkale/watch/package-info.java @@ -0,0 +1,4 @@ +/** + * 提供RedKale服务的监控、动态部署、数据收集功能 + */ +package org.redkale.watch;