diff --git a/DotXxlJob.sln b/DotXxlJob.sln index ac19b8f..dee34fe 100644 --- a/DotXxlJob.sln +++ b/DotXxlJob.sln @@ -13,6 +13,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{352EC932 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hessian.NET.Tests", "tests\Hessian.NET.Tests\Hessian.NET.Tests.csproj", "{187B28C7-C3D7-4E0A-A84B-98B7C1C758F9}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{E959F9B5-F3EB-48B1-B842-2CDDFDB01900}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASPNetCoreExecutor", "samples\ASPNetCoreExecutor\ASPNetCoreExecutor.csproj", "{DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HessianReader", "samples\HessianReader\HessianReader.csproj", "{F822B528-95FD-40B4-9EE0-3AE8878075AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hessian", "src\Hessian\Hessian.csproj", "{BD9B8108-6528-430F-AD28-6F8434A29F55}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,6 +37,9 @@ Global {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65} = {97756BA5-1E7C-4536-A49E-AE2190C0E6A5} {86456232-19D5-48DD-AC39-A0526517E0AD} = {97756BA5-1E7C-4536-A49E-AE2190C0E6A5} {187B28C7-C3D7-4E0A-A84B-98B7C1C758F9} = {352EC932-F112-4A2F-9DC3-F0761C85E068} + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5} = {E959F9B5-F3EB-48B1-B842-2CDDFDB01900} + {F822B528-95FD-40B4-9EE0-3AE8878075AC} = {E959F9B5-F3EB-48B1-B842-2CDDFDB01900} + {BD9B8108-6528-430F-AD28-6F8434A29F55} = {97756BA5-1E7C-4536-A49E-AE2190C0E6A5} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FFFEEA78-CB09-4BFB-89B7-E9A46EC3ED65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -67,5 +78,41 @@ Global {187B28C7-C3D7-4E0A-A84B-98B7C1C758F9}.Release|x64.Build.0 = Release|Any CPU {187B28C7-C3D7-4E0A-A84B-98B7C1C758F9}.Release|x86.ActiveCfg = Release|Any CPU {187B28C7-C3D7-4E0A-A84B-98B7C1C758F9}.Release|x86.Build.0 = Release|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|x64.ActiveCfg = Debug|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|x64.Build.0 = Debug|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|x86.ActiveCfg = Debug|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Debug|x86.Build.0 = Debug|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|Any CPU.Build.0 = Release|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|x64.ActiveCfg = Release|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|x64.Build.0 = Release|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|x86.ActiveCfg = Release|Any CPU + {DC9E5AF3-18FF-4713-BDB4-672E47ADA4E5}.Release|x86.Build.0 = Release|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Debug|x64.ActiveCfg = Debug|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Debug|x64.Build.0 = Debug|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Debug|x86.ActiveCfg = Debug|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Debug|x86.Build.0 = Debug|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Release|Any CPU.Build.0 = Release|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Release|x64.ActiveCfg = Release|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Release|x64.Build.0 = Release|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Release|x86.ActiveCfg = Release|Any CPU + {F822B528-95FD-40B4-9EE0-3AE8878075AC}.Release|x86.Build.0 = Release|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Debug|x64.ActiveCfg = Debug|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Debug|x64.Build.0 = Debug|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Debug|x86.ActiveCfg = Debug|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Debug|x86.Build.0 = Debug|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Release|Any CPU.Build.0 = Release|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Release|x64.ActiveCfg = Release|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Release|x64.Build.0 = Release|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Release|x86.ActiveCfg = Release|Any CPU + {BD9B8108-6528-430F-AD28-6F8434A29F55}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/samples/ASPNetCoreExecutor/1547620263.dat b/samples/ASPNetCoreExecutor/1547620263.dat new file mode 100644 index 0000000..3f69aab Binary files /dev/null and b/samples/ASPNetCoreExecutor/1547620263.dat differ diff --git a/samples/ASPNetCoreExecutor/1547621183.dat b/samples/ASPNetCoreExecutor/1547621183.dat new file mode 100644 index 0000000..d8c48ad Binary files /dev/null and b/samples/ASPNetCoreExecutor/1547621183.dat differ diff --git a/samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj b/samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj new file mode 100644 index 0000000..181e783 --- /dev/null +++ b/samples/ASPNetCoreExecutor/ASPNetCoreExecutor.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp2.2 + InProcess + + + + + + + + + + + diff --git a/samples/ASPNetCoreExecutor/ApplicationBuilderExtensions.cs b/samples/ASPNetCoreExecutor/ApplicationBuilderExtensions.cs new file mode 100644 index 0000000..5c2bbcf --- /dev/null +++ b/samples/ASPNetCoreExecutor/ApplicationBuilderExtensions.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Builder; + +namespace ASPNetCoreExecutor +{ + public static class ApplicationBuilderExtensions + { + public static IApplicationBuilder UseXxlJobExecutor(this IApplicationBuilder @this) + { + return @this.UseMiddleware(); + } + } +} \ No newline at end of file diff --git a/samples/ASPNetCoreExecutor/Program.cs b/samples/ASPNetCoreExecutor/Program.cs new file mode 100644 index 0000000..f446a2f --- /dev/null +++ b/samples/ASPNetCoreExecutor/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace ASPNetCoreExecutor +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} \ No newline at end of file diff --git a/samples/ASPNetCoreExecutor/Properties/launchSettings.json b/samples/ASPNetCoreExecutor/Properties/launchSettings.json new file mode 100644 index 0000000..663adfb --- /dev/null +++ b/samples/ASPNetCoreExecutor/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:22613", + "sslPort": 44333 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "ASPNetCoreExecutor": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/samples/ASPNetCoreExecutor/Startup.cs b/samples/ASPNetCoreExecutor/Startup.cs new file mode 100644 index 0000000..dd413a2 --- /dev/null +++ b/samples/ASPNetCoreExecutor/Startup.cs @@ -0,0 +1,29 @@ +using DotXxlJob.Core; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace ASPNetCoreExecutor +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddXxlJobExecutor(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + //启用XxlExecutor + app.UseXxlJobExecutor(); + } + } +} \ No newline at end of file diff --git a/samples/ASPNetCoreExecutor/XxlJobExecutorMiddleware.cs b/samples/ASPNetCoreExecutor/XxlJobExecutorMiddleware.cs new file mode 100644 index 0000000..882891f --- /dev/null +++ b/samples/ASPNetCoreExecutor/XxlJobExecutorMiddleware.cs @@ -0,0 +1,41 @@ +using System; +using System.Net; +using System.Threading.Tasks; +using DotXxlJob.Core; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace ASPNetCoreExecutor +{ + public class XxlJobExecutorMiddleware + { + private readonly IServiceProvider _provider; + private readonly RequestDelegate _next; + + private readonly XxlRpcServiceHandler _rpcService; + public XxlJobExecutorMiddleware(IServiceProvider provider, RequestDelegate next) + { + _provider = provider; + _next = next; + + _rpcService = _provider.GetRequiredService(); + } + + + public async Task Invoke(HttpContext context) + { + + if ("POST".Equals(context.Request.Method, StringComparison.OrdinalIgnoreCase) && + "application/octet-stream".Equals(context.Request.ContentType, StringComparison.OrdinalIgnoreCase)) + { + var rsp = await _rpcService.HandlerAsync(context.Request.Body); + + context.Response.StatusCode = (int) HttpStatusCode.OK; + context.Response.ContentType = "text/plain;utf-8"; + await context.Response.Body.WriteAsync(rsp,0,rsp.Length); + } + + await _next.Invoke(context); + } + } +} \ No newline at end of file diff --git a/samples/ASPNetCoreExecutor/appsettings.Development.json b/samples/ASPNetCoreExecutor/appsettings.Development.json new file mode 100644 index 0000000..e203e94 --- /dev/null +++ b/samples/ASPNetCoreExecutor/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/ASPNetCoreExecutor/appsettings.json b/samples/ASPNetCoreExecutor/appsettings.json new file mode 100644 index 0000000..def9159 --- /dev/null +++ b/samples/ASPNetCoreExecutor/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/HessianReader/1547620263.dat b/samples/HessianReader/1547620263.dat new file mode 100644 index 0000000..3f69aab Binary files /dev/null and b/samples/HessianReader/1547620263.dat differ diff --git a/samples/HessianReader/1547621183.dat b/samples/HessianReader/1547621183.dat new file mode 100644 index 0000000..d8c48ad Binary files /dev/null and b/samples/HessianReader/1547621183.dat differ diff --git a/samples/HessianReader/HessianReader.csproj b/samples/HessianReader/HessianReader.csproj new file mode 100644 index 0000000..1178b9f --- /dev/null +++ b/samples/HessianReader/HessianReader.csproj @@ -0,0 +1,19 @@ + + + + Exe + netcoreapp2.2 + + + + + + + + + + C:\Program Files\dotnet\sdk\NuGetFallbackFolder\newtonsoft.json\11.0.2\lib\netstandard2.0\Newtonsoft.Json.dll + + + + diff --git a/samples/HessianReader/Program.cs b/samples/HessianReader/Program.cs new file mode 100644 index 0000000..f7518e6 --- /dev/null +++ b/samples/HessianReader/Program.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections; +using System.IO; +using DotXxlJob.Core.Model; +using Hessian.Net; +using Newtonsoft.Json; + +namespace HessianReader +{ + class Program + { + static void Main(string[] args) + { + byte[] myBinary = File.ReadAllBytes("1547621183.dat"); + + foreach (var i in myBinary) + { + Console.Write("0x"); + Console.Write(i.ToString("x2")); + Console.Write(","); + } + + Console.WriteLine(Environment.NewLine); + Console.WriteLine("---------------------------------------------------------------"); + + + + + var serializer = new DataContractHessianSerializer(typeof (RpcRequest)); + + using (var stream1 = new MemoryStream(myBinary)) + { + var ds = new Hessian.Deserializer(stream1); + + Hessian.ClassDef def = ds.ReadClassDefinition(); + Console.WriteLine(JsonConvert.SerializeObject(def)); + Console.WriteLine(ds.ReadValue()); + //Console.WriteLine(ds.ReadLong()); + //Console.WriteLine(ds.ReadString()); + //Console.WriteLine(ds.ReadString()); + //Console.WriteLine(ds.ReadString()); + //Console.WriteLine(ds.ReadString()); + //Console.WriteLine(ds.ReadValue()); + //Console.WriteLine(ds.ReadValue()); + Console.WriteLine(JsonConvert.SerializeObject(def)); + } + + return; + + RpcRequest req = new RpcRequest { + RequestId = "71565f61-94e8-4dcf-9760-f2fb73a6886a", + CreateMillisTime = 1547621183585, + AccessToken = "", + ClassName = "com.xxl.job.core.biz.ExecutorBiz", + MethodName = "run", + ParameterTypes = new HessianArrayList {"class com.xxl.job.core.biz.model.TriggerParam"}, + Version = "null", + Parameters = new HessianArrayList() + }; + + var p =new TriggerParam { + JobId=1, + ExecutorHandler="demoJobHandler", + ExecutorParams="111", + ExecutorBlockStrategy="SERIAL_EXECUTION", + ExecutorTimeout=0, + LogId=5, + LogDateTime=1547621183414L, + GlueType="BEAN", + GlueSource="", + GlueUpdateTime=1541254891000, + BroadcastIndex=0, + BroadcastTotal=1 + }; + req.Parameters.Add(p); + + byte[] distArray; + + using (MemoryStream stream = new MemoryStream()) + { + + + serializer.WriteObject(stream,req); + //Console.WriteLine(Environment.NewLine); + //Console.WriteLine("---------------------------"+ stream.Length+"------------------------------------"); + stream.Flush(); + distArray =stream.ToArray(); + } + foreach (var j in distArray) + { + Console.Write("0x"); + Console.Write(j.ToString("x2")); + Console.Write(","); + } + + Console.WriteLine(Environment.NewLine); + Console.WriteLine("---------------------------------------------------------------"); + + using (var stream2 = new MemoryStream(distArray)) + { + + var instance = serializer.ReadObject(stream2) as RpcRequest; + + Console.WriteLine(JsonConvert.SerializeObject(instance)); + } + /** + * requestId='71565f61-94e8-4dcf-9760-f2fb73a6886a', + * createMillisTime=1547621183585, + * accessToken='', + * className='com.xxl.job.core.biz.ExecutorBiz', + * methodName='run', + * parameterTypes=[class com.xxl.job.core.biz.model.TriggerParam], + * parameters=[ + * TriggerParam{ + * jobId=1, + * executorHandler='demoJobHandler', + * executorParams='111', + * executorBlockStrategy='SERIAL_EXECUTION', + * executorTimeout=0, + * logId=5, + * logDateTim=1547621183414, + * glueType='BEAN', + * glueSource='', + * glueUpdatetime=1541254891000, + * broadcastIndex=0, + * broadcastTotal=1 + * } + * ], version='null' + * + */ + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/CallbackTaskQueue.cs b/src/DotXxlJob.Core/CallbackTaskQueue.cs new file mode 100644 index 0000000..4df750c --- /dev/null +++ b/src/DotXxlJob.Core/CallbackTaskQueue.cs @@ -0,0 +1,12 @@ +using DotXxlJob.Core.Model; + +namespace DotXxlJob.Core +{ + public class CallbackTaskQueue + { + public void Push(CallbackParam callbackParam) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/Config/XxlJobExecutorOptions.cs b/src/DotXxlJob.Core/Config/XxlJobExecutorOptions.cs new file mode 100644 index 0000000..84e76cd --- /dev/null +++ b/src/DotXxlJob.Core/Config/XxlJobExecutorOptions.cs @@ -0,0 +1,27 @@ +namespace DotXxlJob.Core.Config +{ + public class XxlJobExecutorOptions + { + + public string AdminAddresses { get; set; } + + + public string AppName { get; set; } + + + public string SpecialBindAddress { get; set; } + + + public int Port { get; set; } + + + public string AccessToken { get; set; } + + + public string LogPath { get; set; } + + + public int LogRetentionDays { get; set; } + + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/Constants.cs b/src/DotXxlJob.Core/Constants.cs new file mode 100644 index 0000000..ec5092a --- /dev/null +++ b/src/DotXxlJob.Core/Constants.cs @@ -0,0 +1,42 @@ +using System; + +namespace DotXxlJob.Core +{ + internal static class Constants + { + public const string XxlLogsDefaultRootDirectory = "xxl-job-logs"; + public const string HandleLogsDirectory = "HandlerLogs"; + public const string LogFileNameCallContextKey = "XxlJob.LogFileName"; + public const int DefaultLogRetentionDays = 30; + + public static TimeSpan RpcRequestExpireTimeSpan = TimeSpan.FromMinutes(3); + + public const int MaxCallbackRetryTimes = 10; + //每次回调最多发送几条记录 + public const int MaxCallbackRecordsPerRequest = 100; + public static TimeSpan CallbackRetryInterval = TimeSpan.FromSeconds(600); + + //Admin集群机器请求默认超时时间 + public static TimeSpan AdminServerDefaultTimeout = TimeSpan.FromSeconds(15); + //Admin集群中的某台机器熔断后间隔多长时间再重试 + public static TimeSpan AdminServerReconnectInterval = TimeSpan.FromMinutes(3); + //Admin集群中的某台机器请求失败多少次后熔断 + public const int AdminServerCircuitFaildTimes = 3; + + public static TimeSpan JobThreadWaitTime = TimeSpan.FromSeconds(90); + + public static class GlueType + { + public const string BEAN = "BEAN"; + } + + public static class ExecutorBlockStrategy + { + public const string SERIAL_EXECUTION = "SERIAL_EXECUTION"; + + public const string DISCARD_LATER = "DISCARD_LATER"; + + public const string COVER_EARLY = "COVER_EARLY"; + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/DateTimeExtensions.cs b/src/DotXxlJob.Core/DateTimeExtensions.cs new file mode 100644 index 0000000..5ba3014 --- /dev/null +++ b/src/DotXxlJob.Core/DateTimeExtensions.cs @@ -0,0 +1,35 @@ +using System; + +namespace DotXxlJob.Core +{ + public static class DateTimeExtensions + { + private const long UnixEpochTicks = 621355968000000000; + private const long UnixEpochSeconds = 62135596800; + private const long UnixEpochMilliseconds = 62135596800000; + + public static DateTimeOffset FromUnixTimeSeconds(this long seconds) + { + long ticks = seconds * TimeSpan.TicksPerSecond + UnixEpochTicks; + return new DateTime(ticks, DateTimeKind.Utc); + } + + public static DateTime FromUnixTimeMilliseconds(this long milliseconds) + { + long ticks = milliseconds * TimeSpan.TicksPerMillisecond + UnixEpochTicks; + return new DateTime(ticks, DateTimeKind.Utc); + } + + public static long ToUnixTimeSeconds(this DateTime dateTime) + { + long seconds = dateTime.ToUniversalTime().Ticks / TimeSpan.TicksPerSecond; + return seconds - UnixEpochSeconds; + } + + public static long ToUnixTimeMilliseconds(this DateTime dateTime) + { + long milliseconds = dateTime.ToUniversalTime().Ticks / TimeSpan.TicksPerMillisecond; + return milliseconds - UnixEpochMilliseconds; + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/DefaultJobHandlerFactory.cs b/src/DotXxlJob.Core/DefaultJobHandlerFactory.cs new file mode 100644 index 0000000..a3ebab6 --- /dev/null +++ b/src/DotXxlJob.Core/DefaultJobHandlerFactory.cs @@ -0,0 +1,10 @@ +namespace DotXxlJob.Core +{ + public class DefaultJobHandlerFactory:IJobHandlerFactory + { + public IJobHandler GetJobHandler(string handlerName) + { + return new HttpJobHandler(); + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/DotXxlJob.Core.csproj b/src/DotXxlJob.Core/DotXxlJob.Core.csproj index c5fbb8f..fc5fc47 100644 --- a/src/DotXxlJob.Core/DotXxlJob.Core.csproj +++ b/src/DotXxlJob.Core/DotXxlJob.Core.csproj @@ -8,4 +8,8 @@ + + + + diff --git a/src/DotXxlJob.Core/ExecutorRegistry.cs b/src/DotXxlJob.Core/ExecutorRegistry.cs index 919fd73..d32f530 100644 --- a/src/DotXxlJob.Core/ExecutorRegistry.cs +++ b/src/DotXxlJob.Core/ExecutorRegistry.cs @@ -3,8 +3,11 @@ namespace DotXxlJob.Core /// /// 执行器注册注册 /// - public class ExecutorRegistry + public class ExecutorRegistry:IExecutorRegistry { - + public void Start() + { + throw new System.NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/HttpJobHandler.cs b/src/DotXxlJob.Core/HttpJobHandler.cs new file mode 100644 index 0000000..9228d74 --- /dev/null +++ b/src/DotXxlJob.Core/HttpJobHandler.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using DotXxlJob.Core.Model; + +namespace DotXxlJob.Core +{ + public class HttpJobHandler:IJobHandler + { + public void Dispose() + { + + } + + public Task Execute(JobExecuteContext context) + { + return Task.FromResult(ReturnT.SUCCESS); + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/IJobHandler.cs b/src/DotXxlJob.Core/IJobHandler.cs index c458fe1..3734aa2 100644 --- a/src/DotXxlJob.Core/IJobHandler.cs +++ b/src/DotXxlJob.Core/IJobHandler.cs @@ -1,17 +1,17 @@ using System; using System.Threading.Tasks; +using DotXxlJob.Core.Model; namespace DotXxlJob.Core { public abstract class AbstractJobHandler:IJobHandler { - /// /// /// /// /// - public abstract Task> Execute(string param); + public abstract Task Execute(JobExecuteContext context); public virtual void Dispose() @@ -21,6 +21,6 @@ namespace DotXxlJob.Core public interface IJobHandler:IDisposable { - Task> Execute(string param); + Task Execute(JobExecuteContext context); } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/ITaskExecutor.cs b/src/DotXxlJob.Core/ITaskExecutor.cs new file mode 100644 index 0000000..679f513 --- /dev/null +++ b/src/DotXxlJob.Core/ITaskExecutor.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using DotXxlJob.Core.Model; + +namespace DotXxlJob.Core +{ + public interface ITaskExecutor + { + string GlueType { get; } + + Task Execute(TriggerParam triggerParam); + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/IXxlJobExecutor.cs b/src/DotXxlJob.Core/IXxlJobExecutor.cs index 3c65fe3..07254b5 100644 --- a/src/DotXxlJob.Core/IXxlJobExecutor.cs +++ b/src/DotXxlJob.Core/IXxlJobExecutor.cs @@ -1,7 +1,20 @@ +using DotXxlJob.Core.Model; + namespace DotXxlJob.Core { public interface IXxlJobExecutor { - + ReturnT Beat(); + + + ReturnT IdleBeat(int jobId); + + + ReturnT Kill(int jobId); + + ReturnT Log(long logDateTim, int logId, int fromLineNum); + + + ReturnT Run(TriggerParam triggerParam); } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/JobDispatcher.cs b/src/DotXxlJob.Core/JobDispatcher.cs new file mode 100644 index 0000000..de95df8 --- /dev/null +++ b/src/DotXxlJob.Core/JobDispatcher.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Concurrent; +using DotXxlJob.Core.Model; +using Microsoft.Extensions.Logging; + +namespace DotXxlJob.Core +{ + /// + /// 负责实际的JOB轮询 + /// + public class JobDispatcher + { + private readonly TaskExecutorFactory _executorFactory; + private readonly CallbackTaskQueue _callbackTaskQueue; + + private readonly ConcurrentDictionary RUNNING_QUEUE = new ConcurrentDictionary(); + + + private readonly ILogger _jobQueueLogger; + private readonly ILogger _logger; + public JobDispatcher( + TaskExecutorFactory executorFactory, + CallbackTaskQueue callbackTaskQueue, + ILoggerFactory loggerFactory + ) + { + this. _executorFactory = executorFactory; + this. _callbackTaskQueue = callbackTaskQueue; + + + this._jobQueueLogger = loggerFactory.CreateLogger(); + this._logger = loggerFactory.CreateLogger(); + } + + + /// + /// 尝试移除JobTask + /// + /// + /// + /// + public bool TryRemoveJobTask(int jobId) + { + if (RUNNING_QUEUE.TryGetValue(jobId, out var jobQueue)) + { + jobQueue.Stop(); + return true; + } + return false; + } + + /// + /// 执行队列,并快速返回结果 + /// + /// + /// + /// + public ReturnT Execute(TriggerParam triggerParam) + { + + var executor = this._executorFactory.GetTaskExecutor(triggerParam.GlueType); + if (executor == null) + { + return ReturnT.Failed($"glueType[{triggerParam.GlueType}] is not supported "); + } + + // 1. 根据JobId 获取 TaskQueue; 用于判断是否有正在执行的任务 + if (RUNNING_QUEUE.TryGetValue(triggerParam.JobId, out var taskQueue)) + { + if (taskQueue.Executor != executor) //任务执行器变更 + { + return ChangeJobQueue(triggerParam, executor); + } + } + + if (taskQueue != null) //旧任务还在执行,判断执行策略 + { + //丢弃后续的 + if (Constants.ExecutorBlockStrategy.DISCARD_LATER == triggerParam.ExecutorBlockStrategy) + { + return ReturnT.Failed($"block strategy effect:{triggerParam.ExecutorBlockStrategy}"); + } + //覆盖较早的 + if (Constants.ExecutorBlockStrategy.COVER_EARLY == triggerParam.ExecutorBlockStrategy) + { + return taskQueue.Replace(triggerParam); + } + } + + return PushJobQueue(triggerParam, executor); + + } + + + /// + /// 等待检查 + /// + /// + /// + public ReturnT IdleBeat(int jobId) + { + return RUNNING_QUEUE.ContainsKey(jobId) ? + new ReturnT(ReturnT.FAIL_CODE, "job thread is running or has trigger queue.") + : ReturnT.SUCCESS; + } + + + private ReturnT PushJobQueue(TriggerParam triggerParam, ITaskExecutor executor) + { + JobQueue jobQueue = new JobQueue ( executor, this._callbackTaskQueue,this._jobQueueLogger); + if (RUNNING_QUEUE.TryAdd(triggerParam.JobId, jobQueue)) + { + jobQueue.Push(triggerParam); + } + return ReturnT.Failed("add running queue executor error"); + } + + private ReturnT ChangeJobQueue(TriggerParam triggerParam, ITaskExecutor executor) + { + + JobQueue jobQueue = new JobQueue ( executor, this._callbackTaskQueue,this._jobQueueLogger); + if (RUNNING_QUEUE.TryUpdate(triggerParam.JobId, jobQueue, null)) + { + return jobQueue.Push(triggerParam); + } + return ReturnT.Failed(" replace running queue executor error"); + } + + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/TaskExcutor.cs b/src/DotXxlJob.Core/JobLogger.cs similarity index 61% rename from src/DotXxlJob.Core/TaskExcutor.cs rename to src/DotXxlJob.Core/JobLogger.cs index 67851a8..e5133da 100644 --- a/src/DotXxlJob.Core/TaskExcutor.cs +++ b/src/DotXxlJob.Core/JobLogger.cs @@ -1,6 +1,6 @@ namespace DotXxlJob.Core { - public class TaskExcutor + public class JobLogger { } diff --git a/src/DotXxlJob.Core/LogResult.cs b/src/DotXxlJob.Core/LogResult.cs new file mode 100644 index 0000000..42ba8c9 --- /dev/null +++ b/src/DotXxlJob.Core/LogResult.cs @@ -0,0 +1,7 @@ +namespace DotXxlJob.Core +{ + public class LogResult + { + + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/Model/CallbackParam.cs b/src/DotXxlJob.Core/Model/CallbackParam.cs new file mode 100644 index 0000000..24aa640 --- /dev/null +++ b/src/DotXxlJob.Core/Model/CallbackParam.cs @@ -0,0 +1,10 @@ +namespace DotXxlJob.Core.Model +{ + public class CallbackParam + { + public CallbackParam(TriggerParam triggerParam, ReturnT result) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/Model/HessianArrayList.cs b/src/DotXxlJob.Core/Model/HessianArrayList.cs new file mode 100644 index 0000000..a3c3ba7 --- /dev/null +++ b/src/DotXxlJob.Core/Model/HessianArrayList.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Runtime.Serialization; + +namespace DotXxlJob.Core.Model +{ + [DataContract(Name = "hessianArrayList")] + public class HessianArrayList:List + { + + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/Model/JobExecuteContext.cs b/src/DotXxlJob.Core/Model/JobExecuteContext.cs new file mode 100644 index 0000000..39ff717 --- /dev/null +++ b/src/DotXxlJob.Core/Model/JobExecuteContext.cs @@ -0,0 +1,7 @@ +namespace DotXxlJob.Core.Model +{ + public class JobExecuteContext + { + + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/Model/ReturnT.cs b/src/DotXxlJob.Core/Model/ReturnT.cs new file mode 100644 index 0000000..d28a69a --- /dev/null +++ b/src/DotXxlJob.Core/Model/ReturnT.cs @@ -0,0 +1,42 @@ +using System.Runtime.Serialization; + +namespace DotXxlJob.Core +{ + [DataContract] + public class ReturnT + { + public const int SUCCESS_CODE = 200; + public const int FAIL_CODE = 500; + + public static readonly ReturnT SUCCESS = new ReturnT(SUCCESS_CODE, null); + public static readonly ReturnT FAIL = new ReturnT(FAIL_CODE, null); + public static readonly ReturnT FAIL_TIMEOUT = new ReturnT(502, null); + + [DataMember(Name = "code",Order = 1)] + public int Code { get; set; } + [DataMember(Name = "msg",Order = 2)] + public string Msg { get; set; } + + public ReturnT() { } + + public ReturnT(int code, string msg) + { + this.Code = code; + this.Msg = msg; + } + + public static ReturnT Failed(string msg) + { + return new ReturnT(FAIL_CODE, msg); + } + public static ReturnT Success(string msg) + { + return new ReturnT(SUCCESS_CODE, msg); + } + [DataMember(Name = "content",Order = 3)] + public object Content { get; set; } + } + + + +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/Model/RpcRequest.cs b/src/DotXxlJob.Core/Model/RpcRequest.cs index 3002636..f862092 100644 --- a/src/DotXxlJob.Core/Model/RpcRequest.cs +++ b/src/DotXxlJob.Core/Model/RpcRequest.cs @@ -1,7 +1,51 @@ +using System.Collections; +using System.Collections.Specialized; +using System.Runtime.Serialization; + namespace DotXxlJob.Core.Model { + [DataContract(Name = "com.xxl.rpc.remoting.net.params.XxlRpcRequest")] public class RpcRequest { + /* + requestId + createMillisTime + accessToken + className + methodName + version + parameterTypes + parameters + */ + [DataMember(Name = "requestId",Order = 1)] + public string RequestId { get; set; } + //[DataMember(Name = "serverAddress")] + //public string ServerAddress{ get; set; } + + [DataMember(Name = "createMillisTime" ,Order = 2)] + public long CreateMillisTime{ get; set; } + + + [DataMember(Name = "accessToken" ,Order = 3)] + public string AccessToken{ get; set; } + + [DataMember(Name = "className" ,Order = 4)] + public string ClassName{ get; set; } + + [DataMember(Name = "methodName" ,Order = 5)] + public string MethodName{ get; set; } + + [DataMember(Name = "version" ,Order = 6)] + public string Version{ get; set; } + + [DataMember(Name = "parameterTypes",Order = 7)] + public HessianArrayList ParameterTypes{ get; set; } + + + [DataMember(Name = "parameters",Order = 8)] + public HessianArrayList Parameters{ get; set; } + + } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/Model/RpcResponse.cs b/src/DotXxlJob.Core/Model/RpcResponse.cs index 564c7c7..1519422 100644 --- a/src/DotXxlJob.Core/Model/RpcResponse.cs +++ b/src/DotXxlJob.Core/Model/RpcResponse.cs @@ -1,7 +1,18 @@ +using System.Runtime.Serialization; + namespace DotXxlJob.Core.Model { + [DataContract(Name = "com.xxl.rpc.remoting.net.params.XxlRpcResponse")] public class RpcResponse { + [DataMember(Name = "requestId",Order = 1)] + public string RequestId{ get; set; } + [DataMember(Name = "errorMsg",Order = 2)] + public string ErrorMsg; + [DataMember(Name = "result",Order = 3)] + public object Result{ get; set; } + + public bool IsError => this.ErrorMsg != null; } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/Model/TriggerParam.cs b/src/DotXxlJob.Core/Model/TriggerParam.cs index f8efb50..0e6425d 100644 --- a/src/DotXxlJob.Core/Model/TriggerParam.cs +++ b/src/DotXxlJob.Core/Model/TriggerParam.cs @@ -1,7 +1,45 @@ +using System.Runtime.Serialization; + namespace DotXxlJob.Core.Model { + + [DataContract(Name = "triggerParam")] public class TriggerParam { + static readonly long SerialVersionUID = 42L; + + [DataMember(Name = "jobId")] + public int JobId { get; set; } + + [DataMember(Name = "executorHandler")] + public string ExecutorHandler { get; set; } + [DataMember(Name = "executorParams")] + public string ExecutorParams{ get; set; } + [DataMember(Name = "executorBlockStrategy")] + public string ExecutorBlockStrategy{ get; set; } + + [DataMember(Name = "executorTimeout")] + public int ExecutorTimeout{ get; set; } + + [DataMember(Name = "logId")] + public int LogId{ get; set; } + [DataMember(Name = "logDateTim")] + public long LogDateTime{ get; set; } + + + [DataMember(Name = "glueType")] + public string GlueType{ get; set; } + + [DataMember(Name = "glueSource")] + public string GlueSource{ get; set; } + + [DataMember(Name = "glueUpdatetime")] + public long GlueUpdateTime{ get; set; } + + [DataMember(Name = "broadcastIndex")] + public int BroadcastIndex{ get; set; } + [DataMember(Name = "broadcastTotal")] + public int BroadcastTotal{ get; set; } } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/ReturnT.cs b/src/DotXxlJob.Core/ReturnT.cs deleted file mode 100644 index 9da2bfc..0000000 --- a/src/DotXxlJob.Core/ReturnT.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace DotXxlJob.Core -{ - public class ReturnT - { - public int Code { get; set; } - - public string Msg { get; set; } - - public T Content { get; set; } - } -} \ No newline at end of file diff --git a/src/DotXxlJob.Core/ServiceCollectionExtensions.cs b/src/DotXxlJob.Core/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..dcb8c4d --- /dev/null +++ b/src/DotXxlJob.Core/ServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace DotXxlJob.Core +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddXxlJobExecutor(this IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + return services; + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/TaskExecutorFactory.cs b/src/DotXxlJob.Core/TaskExecutorFactory.cs index 1e3cb13..ad9f6d3 100644 --- a/src/DotXxlJob.Core/TaskExecutorFactory.cs +++ b/src/DotXxlJob.Core/TaskExecutorFactory.cs @@ -1,7 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; + namespace DotXxlJob.Core { + /// + /// 负责响应RPC请求,调度任务执行器的工厂类 + /// public class TaskExecutorFactory { - + private readonly IServiceProvider _provider; + + private readonly Dictionary _cache = new Dictionary(); + public TaskExecutorFactory(IServiceProvider provider) + { + _provider = provider; + Initialize(); + } + + private void Initialize() + { + var executors = _provider.GetServices(); + + if (executors != null && executors.Any()) + { + foreach (var item in executors) + { + _cache.Add(item.GlueType,item); + } + } + } + + public ITaskExecutor GetTaskExecutor(string glueType) + { + return _cache.TryGetValue(glueType, out var executor) ? executor : null; + } } } \ No newline at end of file diff --git a/src/DotXxlJob.Core/TaskExecutors/BeanTaskExecutor.cs b/src/DotXxlJob.Core/TaskExecutors/BeanTaskExecutor.cs new file mode 100644 index 0000000..b41134c --- /dev/null +++ b/src/DotXxlJob.Core/TaskExecutors/BeanTaskExecutor.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using DotXxlJob.Core.Model; + +namespace DotXxlJob.Core.TaskExecutors +{ + /// + /// 实现 IJobHandler的执行器 + /// + public class BeanTaskExecutor:ITaskExecutor + { + private readonly IJobHandlerFactory _handlerFactory; + + public BeanTaskExecutor(IJobHandlerFactory handlerFactory) + { + _handlerFactory = handlerFactory; + } + + public string GlueType { get; } = Constants.GlueType.BEAN; + + public Task Execute(TriggerParam triggerParam) + { + var handler = _handlerFactory.GetJobHandler(triggerParam.ExecutorHandler); + + if (handler == null) + { + + return Task.FromResult(ReturnT.Failed($"job handler [{triggerParam.ExecutorHandler} not found.")); + } + + return Task.FromResult(ReturnT.Success("OK")); + //return handler.Execute(new JobExecuteContext()); + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/TaskQueue.cs b/src/DotXxlJob.Core/TaskQueue.cs new file mode 100644 index 0000000..87fc4d3 --- /dev/null +++ b/src/DotXxlJob.Core/TaskQueue.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; +using DotXxlJob.Core.Model; +using Microsoft.Extensions.Logging; + +namespace DotXxlJob.Core +{ + public class JobQueue + { + private readonly ITaskExecutor _executor; + private readonly CallbackTaskQueue _callbackTaskQueue; + private readonly ILogger _logger; + private readonly ConcurrentQueue TASK_QUEUE = new ConcurrentQueue(); + public JobQueue(ITaskExecutor executor,CallbackTaskQueue callbackTaskQueue,ILogger logger) + { + _executor = executor; + _callbackTaskQueue = callbackTaskQueue; + _logger = logger; + } + + public ITaskExecutor Executor => this._executor; + + + private CancellationTokenSource _cancellationTokenSource; + + /// + /// 覆盖之前的队列 + /// + /// + /// + public ReturnT Replace(TriggerParam triggerParam) + { + Stop(); + while (!TASK_QUEUE.IsEmpty) + { + TASK_QUEUE.TryDequeue(out _); + } + + return Push(triggerParam); + } + + public ReturnT Push(TriggerParam triggerParam) + { + this.TASK_QUEUE.Enqueue(triggerParam); + StartTask(); + return ReturnT.SUCCESS; + } + + public void Stop() + { + this._cancellationTokenSource?.Cancel(); + this._cancellationTokenSource?.Dispose(); + this._cancellationTokenSource = null; + + } + + + private void StartTask() + { + if (this._cancellationTokenSource != null ) + { + return; //running + } + this._cancellationTokenSource =new CancellationTokenSource(); + CancellationToken ct = _cancellationTokenSource.Token; + + Task.Factory.StartNew(async () => + { + + //ct.ThrowIfCancellationRequested(); + + while (!ct.IsCancellationRequested) + { + if (TASK_QUEUE.IsEmpty) + { + break; + } + + ReturnT result = null; + TriggerParam triggerParam = null; + try + { + + if (TASK_QUEUE.TryDequeue(out triggerParam)) + { + result = await this._executor.Execute(triggerParam); + } + else + { + this._logger.LogWarning("Dequeue Task Failed"); + } + } + catch (Exception ex) + { + result = ReturnT.Failed("Dequeue Task Failed:"+ex.Message); + } + + if(triggerParam !=null) + { + this._callbackTaskQueue.Push(new CallbackParam(triggerParam, result)); + } + + } + + + this._cancellationTokenSource.Dispose(); + this._cancellationTokenSource = null; + + }, this._cancellationTokenSource.Token); + + + } + } +} \ No newline at end of file diff --git a/src/DotXxlJob.Core/XxlRpcServiceHandler.cs b/src/DotXxlJob.Core/XxlRpcServiceHandler.cs new file mode 100644 index 0000000..53abdc5 --- /dev/null +++ b/src/DotXxlJob.Core/XxlRpcServiceHandler.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using DotXxlJob.Core.Config; +using DotXxlJob.Core.Model; +using Hessian.Net; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace DotXxlJob.Core +{ + + /// + /// 负责执行Http请求,序列化和反序列化并发送响应 + /// + public class XxlRpcServiceHandler + { + + private readonly JobDispatcher _jobDispatcher; + private readonly ILogger _logger; + private readonly DataContractHessianSerializer _reqSerializer; + private readonly DataContractHessianSerializer _resSerializer; + private readonly XxlJobExecutorOptions _options; + + private readonly ConcurrentDictionary METHOD_CACHE = + new ConcurrentDictionary(); + + public XxlRpcServiceHandler(IOptions optionsAccessor, + JobDispatcher jobDispatcher, + ILogger logger) + { + + _jobDispatcher = jobDispatcher; + this._logger = logger; + this._reqSerializer = new DataContractHessianSerializer(typeof (RpcRequest)); + this._resSerializer = new DataContractHessianSerializer(typeof (RpcResponse)); + this._options = optionsAccessor.Value; + if (this._options == null) + { + throw new ArgumentNullException(nameof(XxlJobExecutorOptions)); + } + + } + + /// + /// 处理XxlRpc请求流 + /// + /// + /// + /// + public async Task HandlerAsync(Stream reqStream) + { + using (Stream output = File.OpenWrite(DateTime.Now.ToUnixTimeSeconds()+".dat")) + { + reqStream.CopyTo(output); + } + + var req = _reqSerializer.ReadObject(reqStream) as RpcRequest; + var res = new RpcResponse(); + if (!ValidRequest(req, out var error)) + { + res.ErrorMsg = error; + } + else + { + await Invoke(req, res); + } + + using (var outputStream = new MemoryStream()) + { + _resSerializer.WriteObject(outputStream,res); + return outputStream.GetBuffer(); + } + + } + + /// + /// 校验请求信息 + /// + /// + /// + /// + private bool ValidRequest(RpcRequest req,out string error) + { + error = string.Empty; + if (req == null) + { + error = "unknown request stream data,codec fail"; + return false; + } + + if (!"com.xxl.job.core.biz.ExecutorBiz".Equals(req.ClassName)) // + { + error = "not supported request!"; + return false; + } + + if (DateTime.UtcNow.Subtract(req.CreateMillisTime.FromUnixTimeMilliseconds()) > Constants.RpcRequestExpireTimeSpan) + { + error = "request is timeout!"; + return false; + } + + if (!string.IsNullOrEmpty(this._options.AccessToken) && this._options.AccessToken != req.AccessToken) + { + error = "need authorize"; + return false; + } + + return true; + } + + /// + /// 执行请求,获取执行函数 + /// + /// + /// + /// + private Task Invoke(RpcRequest req, RpcResponse res) + { + try + { + var method = GetMethodInfo(req.MethodName); + if (method == null) + { + res.ErrorMsg = $"The method{req.MethodName} is not defined."; + } + else + { + var result = method.Invoke(this, req.Parameters.ToArray()); + + res.Result = result; + } + + } + catch (Exception ex) + { + res.ErrorMsg = ex.ToString(); + } + + return Task.CompletedTask; + + } + + private MethodInfo GetMethodInfo(string methodName) + { + if (METHOD_CACHE.TryGetValue(methodName, out var method)) + { + return method; + } + + var type = GetType(); + method = type.GetMethod( methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase); + if (method != null) + { + METHOD_CACHE.TryAdd(methodName, method); + } + + return method; + } + + + #region rpc service + + private ReturnT Beat() + { + return ReturnT.SUCCESS; + } + + private ReturnT IdleBeat(int jobId) + { + return this._jobDispatcher.IdleBeat(jobId); + } + + private ReturnT Kill(int jobId) + { + return this._jobDispatcher.TryRemoveJobTask(jobId) ? + ReturnT.SUCCESS + : + ReturnT.Success("job thread already killed."); + } + + /// + /// TODO:获取执行日志 + /// + /// + /// + /// + /// + private ReturnT Log(long logDateTime, int logId, int fromLineNum) + { + //var logResult = JobLogger.ReadLog(logDateTime, logId, fromLineNum); + return ReturnT.Success(null); + } + + /// + /// 执行 + /// + /// + /// + private ReturnT Run(TriggerParam triggerParam) + { + return this._jobDispatcher.Execute(triggerParam); + } + #endregion + + + + } +} \ No newline at end of file diff --git a/src/Hessian.NET/HessianInputReader.cs b/src/Hessian.NET/HessianInputReader.cs index 43167dc..0817baf 100644 --- a/src/Hessian.NET/HessianInputReader.cs +++ b/src/Hessian.NET/HessianInputReader.cs @@ -95,7 +95,7 @@ namespace Hessian.Net { return ReadPackedInt64(); } - + if (!LeadingByte.IsUnpackedInt64) { throw new HessianSerializerException(); @@ -286,6 +286,48 @@ namespace Hessian.Net { preamble = ObjectPreamble.None; } + + public ObjectPreamble BeginList() + { + ReadLeadingByte(); + + if (LeadingByte.IsVarList) + { + preamble = ObjectPreamble.VarList; + } + else if (LeadingByte.IsFixedList) + { + preamble = ObjectPreamble.FixList; + } + else if (LeadingByte.IsVarListUntyped) + { + preamble = ObjectPreamble.VarListUntyped; + } + else if (LeadingByte.IsFixListUntyped) + { + preamble = ObjectPreamble.FixListUntyped; + } + else if (LeadingByte.IsCompactFixList) + { + preamble = ObjectPreamble.CompactFixList; + } + else if (LeadingByte.IsCompactFixListUntyped) + { + preamble = ObjectPreamble.CompactFixListUntyped; + } + else + { + throw new HessianSerializerException(); + } + + return preamble; + + } + + public void EndList() + { + preamble = ObjectPreamble.None; + } public void EndClassDefinition() { @@ -313,10 +355,16 @@ namespace Hessian.Net return ReadInt32(); } + public byte? Peek() + { + return this.Stream. + } + protected void ReadLeadingByte() { var data = Stream.ReadByte(); + Console.WriteLine(data.ToString("x2")); if (-1 == data) { throw new HessianSerializerException(); diff --git a/src/Hessian.NET/HessianSerializationScheme.cs b/src/Hessian.NET/HessianSerializationScheme.cs index 81e7da1..663e13d 100644 --- a/src/Hessian.NET/HessianSerializationScheme.cs +++ b/src/Hessian.NET/HessianSerializationScheme.cs @@ -51,10 +51,15 @@ namespace Hessian.Net var serializer = factory.GetSerializer(type); return new ValueElement(type, serializer); } - + + if (IsListType(info)) + { + return new ListElement(info); + } return BuildSerializationObject(type, catalog, factory); } + private static ISerializationElement BuildSerializationObject(Type type, IDictionary catalog, IObjectSerializerFactory factory) { ISerializationElement existing; @@ -101,19 +106,24 @@ namespace Hessian.Net return element; } - private static bool IsSimpleType(TypeInfo typeinfo) + private static bool IsSimpleType(TypeInfo typeInfo) { - if (typeinfo.IsValueType || typeinfo.IsEnum || typeinfo.IsPrimitive) + if (typeInfo.IsValueType || typeInfo.IsEnum || typeInfo.IsPrimitive) { return true; } - if (typeof (String) == typeinfo.AsType()) + if (typeof (String) == typeInfo.AsType()) { return true; } return false; } + + public static bool IsListType(TypeInfo typeInfo) + { + return typeInfo.IsArray && typeInfo.HasElementType; + } } } \ No newline at end of file diff --git a/src/Hessian.NET/LeadingByte.cs b/src/Hessian.NET/LeadingByte.cs index 68659ca..fc9aa24 100644 --- a/src/Hessian.NET/LeadingByte.cs +++ b/src/Hessian.NET/LeadingByte.cs @@ -71,6 +71,16 @@ namespace Hessian.Net public bool IsLongObjectReference => Check(If.Marker.Equals(Marker.ClassReference)); public bool IsInstanceReference => Check(If.Marker.Equals(Marker.InstanceReference)); + + public bool IsVarList => Check(If.Marker.Equals(Marker.VarList)); + + public bool IsFixedList => Check(If.Marker.Equals(Marker.FixedList)); + public bool IsVarListUntyped => Check(If.Marker.Equals(Marker.VarListUntyped)); + public bool IsFixListUntyped => Check(If.Marker.Equals(Marker.FixListUntyped)); + public bool IsCompactFixList => Check(If.Marker.Between(Marker.CompactFixListStart).And(Marker.CompactFixListEnd)); + public bool IsCompactFixListUntyped => Check(If.Marker.Between(Marker.CompactFixListUntypedStart).And(Marker.CompactFixListUntypedEnd)); + + public void SetData(byte value) { diff --git a/src/Hessian.NET/ListElement.cs b/src/Hessian.NET/ListElement.cs new file mode 100644 index 0000000..a627392 --- /dev/null +++ b/src/Hessian.NET/ListElement.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Hessian.Net +{ + public class ListElement: ISerializationElement + { + public ListElement(Type listType) + { + this.ObjectType = listType.GetElementType(); + } + + public Type ObjectType { get; } + public void Serialize(HessianOutputWriter writer, object graph, HessianSerializationContext context) + { + throw new NotImplementedException(); + } + + public object Deserialize(HessianInputReader reader, HessianSerializationContext context) + { + var preamble = reader.BeginList(); + switch (preamble) + { + case ObjectPreamble.FixList: + break; + case ObjectPreamble.VarList: + break; + case ObjectPreamble.FixListUntyped: + break; + case ObjectPreamble.VarListUntyped: + break; + case ObjectPreamble.CompactFixList: + break; + case ObjectPreamble.CompactFixListUntyped: + break; + } + + + reader.EndList(); + } + + + private string ReadTypeName(HessianInputReader reader) + { + reader.re + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + // A type name is either a string, or an integer reference to a + // string already read and stored in the type-name ref map. + if ((tag >= 0x00 && tag < 0x20) + || (tag >= 0x30 && tag < 0x34) + || tag == 0x52 + || tag == 0x53) { + var typeName = ReadString(); + typeNameRefs.Add(typeName); + return typeName; + } + + return typeNameRefs.Get(ReadInteger()); + } + + #region List + + private IList ReadVarList() + { + reader.ReadByte(); + var type = ReadTypeName(); + return ReadListCore(type: type); + } + + private IList ReadFixList() + { + reader.ReadByte(); + var type = ReadTypeName(); + var length = ReadInteger(); + return ReadListCore(length, type); + } + + private IList ReadVarListUntyped() + { + reader.ReadByte(); + return ReadListCore(); + } + + private IList ReadFixListUntyped() + { + reader.ReadByte(); + var length = ReadInteger(); + return ReadListCore(length); + } + + private IList ReadCompactFixList() + { + var tag = reader.ReadByte(); + var length = tag - 0x70; + var type = ReadTypeName(); + return ReadListCore(length, type); + } + + private IList ReadCompactFixListUntyped() + { + var tag = reader.ReadByte(); + var length = tag - 0x70; + return ReadListCore(length); + } + + private IList ReadListCore(int? length = null, string type = null) + { + var list = GetListIntance(type, length); + + objectRefs.Add(list); + + if (length.HasValue) { + PopulateFixLengthList(list, length.Value); + } else { + PopulateVarList(list); + } + return list; + } + + private IList GetListIntance(string type, int? length = null) + { + IList list; + + if (length.HasValue) { + if (!listTypeResolver.Value.TryGetListInstance(type, length.Value, out list)) { + list = new List(length.Value); + } + } else { + if (!listTypeResolver.Value.TryGetListInstance(type, out list)) { + list = new List(); + } + } + + return list; + } + + private void PopulateFixLengthList(IList list, int length) + { + for (var i = 0; i < length; ++i) { + list.Add(ReadValue()); + } + } + + private void PopulateVarList(IList list) + { + while (true) { + var tag = reader.Peek(); + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + if (tag == 'Z') { + reader.ReadByte(); + break; + } + list.Add(ReadValue()); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Hessian.NET/ListPreamble.cs b/src/Hessian.NET/ListPreamble.cs new file mode 100644 index 0000000..05f2413 --- /dev/null +++ b/src/Hessian.NET/ListPreamble.cs @@ -0,0 +1,13 @@ +namespace Hessian.Net +{ + public enum ListPreamble + { + None, + VarList, + FixList, + VarListUntyped, + FixListUntyped, + CompactFixList, + CompactFixListUntyped + } +} \ No newline at end of file diff --git a/src/Hessian.NET/Marker.cs b/src/Hessian.NET/Marker.cs index 71ef53e..cd28a8f 100644 --- a/src/Hessian.NET/Marker.cs +++ b/src/Hessian.NET/Marker.cs @@ -24,8 +24,19 @@ public const byte UnpackedLong = (byte) 'L';// 0x4C; public const byte StringNonfinalChunk = 0x52; public const byte StringFinalChunk = 0x53; - public const byte FixedLengthList = 0x56; + public const byte VarList = 0x55; + public const byte FixedList = 0x56; + public const byte VarListUntyped = 0x57; + public const byte FixListUntyped = 0x58; + + public const byte CompactFixListStart = 0x70; + public const byte CompactFixListEnd = 0x77; + public const byte CompactFixListUntypedStart = 0x78; + public const byte CompactFixListUntypedEnd = 0x7F; + public const byte ClassReference = (byte) 'O';//0x4F public const byte InstanceReference = (byte) 'Q'; //0x51; + + } } \ No newline at end of file diff --git a/src/Hessian.NET/ObjectElement.cs b/src/Hessian.NET/ObjectElement.cs index ca1af28..0584805 100644 --- a/src/Hessian.NET/ObjectElement.cs +++ b/src/Hessian.NET/ObjectElement.cs @@ -108,6 +108,7 @@ namespace Hessian.Net for (var index = 0; index < propertiesCount; index++) { var propertyName = reader.ReadString(); + Console.WriteLine(propertyName); var exists = ObjectProperties.Any(property => String.Equals(property.PropertyName, propertyName)); if (!exists) @@ -130,10 +131,12 @@ namespace Hessian.Net var instance = Activator.CreateInstance(ObjectType); context.Instances.Add(instance); - + Console.WriteLine("==========================================="); foreach (var item in ObjectProperties) { + Console.WriteLine(item.PropertyName); var value = item.Deserialize(reader, context); + Console.WriteLine(value); item.Property.SetValue(instance, value); } diff --git a/src/Hessian.NET/ObjectPreamble.cs b/src/Hessian.NET/ObjectPreamble.cs index 7ce3fe8..7f19c15 100644 --- a/src/Hessian.NET/ObjectPreamble.cs +++ b/src/Hessian.NET/ObjectPreamble.cs @@ -5,6 +5,13 @@ None = -1, ClassDefinition, ObjectReference, - InstanceReference + InstanceReference, + + VarList, + FixList, + VarListUntyped, + FixListUntyped, + CompactFixList, + CompactFixListUntyped } } \ No newline at end of file diff --git a/src/Hessian/ClassDef.cs b/src/Hessian/ClassDef.cs new file mode 100644 index 0000000..1f00132 --- /dev/null +++ b/src/Hessian/ClassDef.cs @@ -0,0 +1,59 @@ +using System; + +namespace Hessian +{ + public class ClassDef : IEquatable + { + public string Name { get; private set; } + public string[] Fields { get; private set; } + + public ClassDef(string name, string[] fields) + { + Name = Conditions.CheckNotNull(name, "name"); + Fields = Conditions.CheckNotNull(fields, "fields"); + } + + public override int GetHashCode() + { + unchecked { + const uint prime = 16777619; + var hash = 2166136261; + + hash *= prime; + hash ^= (uint)Name.GetHashCode(); + + for (var i = 0; i < Fields.Length; ++i) { + hash *= prime; + hash ^= (uint)Fields[i].GetHashCode(); + } + + return (int)hash; + } + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { + return false; + } + if (ReferenceEquals(this, obj)) { + return true; + } + if (obj.GetType() != GetType()) { + return false; + } + return Equals((ClassDef) obj); + } + + public bool Equals(ClassDef other) + { + if (ReferenceEquals(null, other)) { + return false; + } + if (ReferenceEquals(this, other)) { + return true; + } + return string.Equals(Name, other.Name) && Fields.Equals(other.Fields); + } + } +} diff --git a/src/Hessian/Collections/ForwardingDictionary.cs b/src/Hessian/Collections/ForwardingDictionary.cs new file mode 100644 index 0000000..6b2d62e --- /dev/null +++ b/src/Hessian/Collections/ForwardingDictionary.cs @@ -0,0 +1,91 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Hessian.Collections +{ + public abstract class ForwardingDictionary : IDictionary + { + protected abstract IDictionary Delegate { get; } + + public virtual ICollection Keys + { + get { return Delegate.Keys; } + } + + public virtual ICollection Values + { + get { return Delegate.Values; } + } + + public virtual IEnumerator> GetEnumerator() + { + return Delegate.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public virtual void Add(KeyValuePair item) + { + Delegate.Add(item); + } + + public virtual void Clear() + { + Delegate.Clear(); + } + + public virtual bool Contains(KeyValuePair item) + { + return Delegate.Contains(item); + } + + public virtual void CopyTo(KeyValuePair[] array, int arrayIndex) + { + Delegate.CopyTo(array, arrayIndex); + } + + public virtual bool Remove(KeyValuePair item) + { + return Delegate.Remove(item); + } + + public virtual int Count + { + get { return Delegate.Count; } + } + + public virtual bool IsReadOnly + { + get { return Delegate.IsReadOnly; } + } + + public virtual bool ContainsKey(TKey key) + { + return Delegate.ContainsKey(key); + } + + public virtual void Add(TKey key, TValue value) + { + Delegate.Add(key, value); + } + + public virtual bool Remove(TKey key) + { + return Delegate.Remove(key); + } + + public virtual bool TryGetValue(TKey key, out TValue value) + { + return Delegate.TryGetValue(key, out value); + } + + public virtual TValue this[TKey key] + { + get { return Delegate[key]; } + set { Delegate[key] = value; } + } + } +} diff --git a/src/Hessian/Collections/IRefMap.cs b/src/Hessian/Collections/IRefMap.cs new file mode 100644 index 0000000..5bf3f5e --- /dev/null +++ b/src/Hessian/Collections/IRefMap.cs @@ -0,0 +1,32 @@ +namespace Hessian.Collections +{ + /// + /// Represents a map that associates objects with a zero-based integer + /// index. Specified in Hessian 2.0. + /// + /// + public interface IRefMap + { + /// + /// Adds an element to the ref map and returns its ID. + /// + /// + /// + int Add(T entry); + + /// + /// Retrieves the element identified by the given ID. + /// + /// + /// + T Get(int refId); + + /// + /// Looks up an element in the ref map and, if present, returns its ID. + /// + /// + /// Performance of this method is not guaranteed and is implementation-specific. + /// + int? Lookup(T entry); + } +} diff --git a/src/Hessian/Collections/ITwoWayDictionary.cs b/src/Hessian/Collections/ITwoWayDictionary.cs new file mode 100644 index 0000000..1da8ce5 --- /dev/null +++ b/src/Hessian/Collections/ITwoWayDictionary.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; + +namespace Hessian.Collections +{ + /// + /// Represents a dictionary which maintains uniqueness of both keys and values, + /// allowing for the lookup of a key by its value in additon to the normal + /// operations expected of a dictionary. + /// + public interface ITwoWayDictionary : IDictionary + { + /// + /// Exposes a view of the current dictionary that reverses keys and + /// values. Note that the same underlying data is shared. + /// + ITwoWayDictionary Inverse { get; } + + /// + /// Gets or sets a key by the given value. + /// + /// + /// The value with which to get or set a key. + /// + /// + /// Returns the key indexing the given . + /// + TKey this[TValue valueKey] { get; set; } + + /// + /// Gets a value indicating whether the given + /// is contained in this dictionary. + /// + /// + /// The value whose presence is to be determined. + /// + /// + /// Returns if is in + /// this dictionary, and otherwise. + /// + bool ContainsValue(TValue value); + + /// + /// Attempts to look up a key by the given value. A return value + /// indicates whether the lookip is successful. + /// + /// + /// The value whose corresponding key is to be retrieved. + /// + /// + /// When the method returns, contains the looked-up key if the lookup + /// succeeded. + /// + /// + /// Returns if the lookup succeeded, and + /// otherwise. + /// + bool TryGetKey(TValue value, out TKey key); + } +} diff --git a/src/Hessian/Collections/ListRefMap.cs b/src/Hessian/Collections/ListRefMap.cs new file mode 100644 index 0000000..37369d5 --- /dev/null +++ b/src/Hessian/Collections/ListRefMap.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace Hessian.Collections +{ + public class ListRefMap : IRefMap + { + private readonly List list = new List(); + + public int Add(T entry) + { + list.Add(entry); + return list.Count - 1; + } + + public T Get(int refId) + { + if (refId < 0 || refId >= list.Count) { + throw new InvalidRefException(refId); + } + return list[refId]; + } + + public int? Lookup(T entry) + { + for (var i = 0; i < list.Count; ++i) { + if (entry.Equals(list[i])) { + return i; + } + } + + return null; + } + } +} diff --git a/src/Hessian/Collections/TwoWayDictionary.cs b/src/Hessian/Collections/TwoWayDictionary.cs new file mode 100644 index 0000000..1b0500d --- /dev/null +++ b/src/Hessian/Collections/TwoWayDictionary.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; + +namespace Hessian.Collections +{ + public class TwoWayDictionary : ForwardingDictionary, ITwoWayDictionary + { + private readonly IDictionary dict; + private readonly TwoWayDictionary inverse; + + protected override IDictionary Delegate { + get { return dict; } + } + + public override bool IsReadOnly { + get { return false; } + } + + public ITwoWayDictionary Inverse { + get { return inverse; } + } + + public override TValue this[TKey key] { + set { UpdateDictAndInverse(key, value, false); } + } + + public TKey this[TValue valueKey] { + get { return inverse[valueKey]; } + set { inverse[valueKey] = value; } + } + + public override ICollection Values { + get { return inverse.dict.Keys; } + } + + public TwoWayDictionary() + : this(new Dictionary(), new Dictionary()) + { + } + + public TwoWayDictionary(IDictionary forwards, IDictionary backwards) + { + dict = Conditions.CheckNotNull(forwards, "forwards"); + inverse = new TwoWayDictionary(backwards, this); + } + + private TwoWayDictionary(IDictionary dict, TwoWayDictionary inverse) + { + this.dict = Conditions.CheckNotNull(dict, "dict"); + this.inverse = inverse; + } + + public bool ContainsValue(TValue value) + { + return inverse.ContainsKey(value); + } + + public bool TryGetKey(TValue value, out TKey key) + { + return inverse.TryGetValue(value, out key); + } + + public override void Add(TKey key, TValue value) + { + UpdateDictAndInverse(key, value, true); + } + + public override bool Remove(TKey key) + { + return RemoveFromDictAndInverse(key); + } + + private void UpdateDictAndInverse(TKey key, TValue value, bool throwIfContained) + { + if (!throwIfContained) { + dict.Remove(key); + inverse.dict.Remove(value); + } + + dict.Add(key, value); + inverse.dict.Add(value, key); + } + + private bool RemoveFromDictAndInverse(TKey key) + { + TValue value; + if (!TryGetValue(key, out value)) { + return false; + } + + return RemoveFromDictAndInverse(key, value); + } + + private bool RemoveFromDictAndInverse(TKey key, TValue value) + { + if (!ContainsKey(key) || !ContainsValue(value)) { + return false; + } + + return dict.Remove(key) && inverse.dict.Remove(value); + } + + #region ICollection> + + + public override bool Remove(KeyValuePair kvp) + { + return RemoveFromDictAndInverse(kvp.Key, kvp.Value); + } + + public override void Clear() + { + dict.Clear(); + inverse.dict.Clear(); + } + + public override bool Contains(KeyValuePair kvp) + { + return ContainsKey(kvp.Key) && ContainsValue(kvp.Value); + } + + #endregion + } +} diff --git a/src/Hessian/Collections/TwoWayDictionaryRefMap.cs b/src/Hessian/Collections/TwoWayDictionaryRefMap.cs new file mode 100644 index 0000000..f899219 --- /dev/null +++ b/src/Hessian/Collections/TwoWayDictionaryRefMap.cs @@ -0,0 +1,34 @@ +namespace Hessian.Collections +{ + public class TwoWayDictionaryRefMap : IRefMap + { + private readonly TwoWayDictionary map = new TwoWayDictionary(); + + public int Add(T value) + { + var refid = map.Count; + map.Add(value, refid); + return refid; + } + + public T Get(int refid) + { + T entry; + if (map.TryGetKey(refid, out entry)) { + return entry; + } + + throw new InvalidRefException(refid); + } + + public int? Lookup(T entry) + { + int refId; + if (map.TryGetValue(entry, out refId)) { + return refId; + } + + return null; + } + } +} diff --git a/src/Hessian/Conditions.cs b/src/Hessian/Conditions.cs new file mode 100644 index 0000000..22cffc7 --- /dev/null +++ b/src/Hessian/Conditions.cs @@ -0,0 +1,114 @@ +using System; + +namespace Hessian +{ + public class Conditions + { + protected Conditions() + { + } + + public static void CheckArgument(bool condition, string message, params object[] args) + { + if (condition) { + return; + } + + if (args.Length > 0) { + message = String.Format(message, args); + } + + throw new ArgumentException(message); + } + + public static T CheckNotNull(T value, string name) + where T : class + { + if (!ReferenceEquals(value, null)) { + return value; + } + + throw new ArgumentNullException(name); + } + + public static TComparable CheckGreater(TComparable value, TComparand bounds, + string name) + where TComparable : IComparable + { + if (value.CompareTo(bounds) > 0) { + return value; + } + + throw new ArgumentOutOfRangeException(name); + } + + public static TComparable CheckLess(TComparable value, TComparand bounds, + string name) + where TComparable : IComparable + { + if (value.CompareTo(bounds) < 0) { + return value; + } + + throw new ArgumentOutOfRangeException(name); + } + + public static TComparable CheckGreaterOrEqual(TComparable value, TComparand bounds, + string name) + where TComparable : IComparable + { + if (value.CompareTo(bounds) >= 0) { + return value; + } + + throw new ArgumentOutOfRangeException(name); + } + + public static TComparable CheckLessOrEqual(TComparable value, TComparand bounds, + string name) + where TComparable : IComparable + { + if (value.CompareTo(bounds) <= 0) { + return value; + } + + throw new ArgumentOutOfRangeException(name); + } + + public static int CheckGreater(int value, int bounds, string name) + { + if (value > bounds) { + return value; + } + + throw new ArgumentOutOfRangeException(name); + } + + public static int CheckLess(int value, int bounds, string name) + { + if (value < bounds) { + return value; + } + + throw new ArgumentOutOfRangeException(name); + } + + public static int CheckGreaterOrEqual(int value, int bounds, string name) + { + if (value >= bounds) { + return value; + } + + throw new ArgumentOutOfRangeException(name); + } + + public static int CheckLessOrEqual(int value, int bounds, string name) + { + if (value <= bounds) { + return value; + } + + throw new ArgumentOutOfRangeException(name); + } + } +} diff --git a/src/Hessian/Deserializer.cs b/src/Hessian/Deserializer.cs new file mode 100644 index 0000000..5de1161 --- /dev/null +++ b/src/Hessian/Deserializer.cs @@ -0,0 +1,892 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +using Hessian.Collections; +using Hessian.Platform; + +namespace Hessian +{ + public class Deserializer + { + private readonly ValueReader reader; + private readonly IRefMap classDefs; + private readonly IRefMap objectRefs; + private readonly IRefMap typeNameRefs; + private readonly Lazy listTypeResolver = new Lazy(); + private readonly Lazy dictTypeResolver = new Lazy(); + + private static readonly EndianBitConverter BitConverter = new BigEndianBitConverter(); + + public Deserializer (Stream stream) + { + if (stream == null) { + throw new ArgumentNullException("stream"); + } + + reader = new ValueReader(stream); + classDefs = new ListRefMap(); + objectRefs = new ListRefMap(); + typeNameRefs = new ListRefMap(); + } + + #region ReadValue + + public object ReadValue () + { + var tag = reader.Peek (); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + switch (tag.Value) { + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07: + case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0E: case 0x0F: + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F: + return ReadShortString(); + + case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27: + case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F: + return ReadShortBinary(); + + case 0x30: case 0x31: case 0x32: case 0x33: + return ReadMediumString(); + + case 0x34: case 0x35: case 0x36: case 0x37: + return ReadMediumBinary(); + + case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: + return ReadLongThreeBytes(); + + case 0x40: + return Reserved(); + + case 0x41: case 0x42: + return ReadChunkedBinary(); + + case 0x43: + return ReadClassDefinition(); + + case 0x44: + return ReadFullDouble(); + + case 0x45: + return Reserved(); + + case 0x46: + return ReadBoolean(); + + case 0x47: + return Reserved(); + + case 0x48: + return ReadUntypedMap(); + + case 0x49: + return ReadInteger(); + + case 0x4A: + return ReadDateInMillis(); + + case 0x4B: + return ReadDateInMinutes(); + + case 0x4C: + return ReadLongFull(); + + case 0x4D: + return ReadTypedMap(); + + case 0x4E: + return ReadNull(); + + case 0x4F: + return ReadObject(); + + case 0x50: + return Reserved(); + + case 0x51: + return ReadRef(); + + case 0x52: case 0x53: + return ReadChunkedString(); + + case 0x54: + return ReadBoolean(); + + case 0x55: + return ReadVarList(); + + case 0x56: + return ReadFixList(); + + case 0x57: + return ReadVarListUntyped(); + + case 0x58: + return ReadFixListUntyped(); + + case 0x59: + return ReadLongFourBytes(); + + case 0x5A: + // List terminator - solitary list terminators are most definitely not legit. + throw new UnexpectedTagException(0x5A, "value"); + + case 0x5B: case 0x5C: + return ReadDoubleOneByte(); + + case 0x5D: + return ReadDoubleOneByte(); + + case 0x5E: + return ReadDoubleTwoBytes(); + + case 0x5F: + return ReadDoubleFourBytes(); + + case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67: + case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F: + return ReadObjectCompact(); + + case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77: + return ReadCompactFixList(); + + case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F: + return ReadCompactFixListUntyped(); + + case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87: + case 0x88: case 0x89: case 0x8A: case 0x8B: case 0x8C: case 0x8D: case 0x8E: case 0x8F: + case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97: + case 0x98: case 0x99: case 0x9A: case 0x9B: case 0x9C: case 0x9D: case 0x9E: case 0x9F: + case 0xA0: case 0xA1: case 0xA2: case 0xA3: case 0xA4: case 0xA5: case 0xA6: case 0xA7: + case 0xA8: case 0xA9: case 0xAA: case 0xAB: case 0xAC: case 0xAD: case 0xAE: case 0xAF: + case 0xB0: case 0xB1: case 0xB2: case 0xB3: case 0xB4: case 0xB5: case 0xB6: case 0xB7: + case 0xB8: case 0xB9: case 0xBA: case 0xBB: case 0xBC: case 0xBD: case 0xBE: case 0xBF: + return ReadIntegerSingleByte(); + + case 0xC0: case 0xC1: case 0xC2: case 0xC3: case 0xC4: case 0xC5: case 0xC6: case 0xC7: + case 0xC8: case 0xC9: case 0xCA: case 0xCB: case 0xCC: case 0xCD: case 0xCE: case 0xCF: + return ReadIntegerTwoBytes(); + + case 0xD0: case 0xD1: case 0xD2: case 0xD3: case 0xD4: case 0xD5: case 0xD6: case 0xD7: + return ReadIntegerThreeBytes(); + + case 0xD8: case 0xD9: case 0xDA: case 0xDB: case 0xDC: case 0xDD: case 0xDE: case 0xDF: + case 0xE0: case 0xE1: case 0xE2: case 0xE3: case 0xE4: case 0xE5: case 0xE6: case 0xE7: + case 0xE8: case 0xE9: case 0xEA: case 0xEB: case 0xEC: case 0xED: case 0xEE: case 0xEF: + return ReadLongOneByte(); + + case 0xF0: case 0xF1: case 0xF2: case 0xF3: case 0xF4: case 0xF5: case 0xF6: case 0xF7: + case 0xF8: case 0xF9: case 0xFA: case 0xFB: case 0xFC: case 0xFD: case 0xFE: case 0xFF: + return ReadLongTwoBytes(); + } + + + throw new Exception("WTF: byte value " + tag.Value + " not accounted for!"); + } + + #endregion + + private string ReadTypeName() + { + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + // A type name is either a string, or an integer reference to a + // string already read and stored in the type-name ref map. + if ((tag >= 0x00 && tag < 0x20) + || (tag >= 0x30 && tag < 0x34) + || tag == 0x52 + || tag == 0x53) { + var typeName = ReadString(); + typeNameRefs.Add(typeName); + return typeName; + } + + return typeNameRefs.Get(ReadInteger()); + } + + #region List + + private IList ReadVarList() + { + reader.ReadByte(); + var type = ReadTypeName(); + return ReadListCore(type: type); + } + + private IList ReadFixList() + { + reader.ReadByte(); + var type = ReadTypeName(); + var length = ReadInteger(); + return ReadListCore(length, type); + } + + private IList ReadVarListUntyped() + { + reader.ReadByte(); + return ReadListCore(); + } + + private IList ReadFixListUntyped() + { + reader.ReadByte(); + var length = ReadInteger(); + return ReadListCore(length); + } + + private IList ReadCompactFixList() + { + var tag = reader.ReadByte(); + var length = tag - 0x70; + var type = ReadTypeName(); + return ReadListCore(length, type); + } + + private IList ReadCompactFixListUntyped() + { + var tag = reader.ReadByte(); + var length = tag - 0x70; + return ReadListCore(length); + } + + private IList ReadListCore(int? length = null, string type = null) + { + var list = GetListIntance(type, length); + + objectRefs.Add(list); + + if (length.HasValue) { + PopulateFixLengthList(list, length.Value); + } else { + PopulateVarList(list); + } + return list; + } + + private IList GetListIntance(string type, int? length = null) + { + IList list; + + if (length.HasValue) { + if (!listTypeResolver.Value.TryGetListInstance(type, length.Value, out list)) { + list = new List(length.Value); + } + } else { + if (!listTypeResolver.Value.TryGetListInstance(type, out list)) { + list = new List(); + } + } + + return list; + } + + private void PopulateFixLengthList(IList list, int length) + { + for (var i = 0; i < length; ++i) { + list.Add(ReadValue()); + } + } + + private void PopulateVarList(IList list) + { + while (true) { + var tag = reader.Peek(); + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + if (tag == 'Z') { + reader.ReadByte(); + break; + } + list.Add(ReadValue()); + } + } + + #endregion + + public object Reserved () + { + reader.ReadByte(); + return ReadValue(); + } + + #region String + + public string ReadString() + { + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + if (tag.Value < 0x20) { + return ReadShortString(); + } + + if (tag.Value >= 0x30 && tag.Value <= 0x33) { + return ReadMediumString(); + } + + if (tag.Value == 'R' || tag.Value == 'S') { + return ReadChunkedString(); + } + + throw new UnexpectedTagException(tag.Value, "string"); + } + + private string ReadShortString () + { + var length = reader.ReadByte(); + return ReadStringWithLength(length); + } + + private string ReadMediumString () + { + var b0 = reader.ReadByte (); + var b1 = reader.ReadByte (); + var length = ((b0 - 0x30) << 8) | b1; + return ReadStringWithLength(length); + } + + private string ReadStringWithLength (int length) + { + var sb = new StringBuilder (length); + while (length-- > 0) { + sb.AppendCodepoint(reader.ReadUtf8Codepoint()); + } + return sb.ToString(); + } + + private string ReadChunkedString() + { + var sb = new StringBuilder(); + var final = false; + + while (!final) { + var tag = reader.ReadByte(); + final = tag == 'S'; + var length = reader.ReadShort(); + while (length-- > 0) { + sb.AppendCodepoint(reader.ReadUtf8Codepoint()); + } + } + + return sb.ToString(); + } + + #endregion + + #region Binary + + public byte[] ReadBinary() + { + var tag = reader.Peek(); + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + if (tag.Value >= 0x20 && tag.Value <= 0x2F) { + return ReadShortBinary(); + } + + if (tag.Value >= 0x34 && tag.Value <= 0x37) { + return ReadMediumBinary(); + } + + if (tag.Value == 0x41 || tag.Value == 0x42) { + return ReadChunkedBinary(); + } + + throw new UnexpectedTagException(tag.Value, "binary"); + } + + private byte[] ReadShortBinary () + { + var length = reader.ReadByte(); + var data = new byte[length]; + reader.Read(data, length); + return data; + } + + private byte[] ReadMediumBinary() + { + var b0 = reader.ReadByte(); + var b1 = reader.ReadByte(); + var length = ((b0 - 0x34) << 8) | b1; + var data = new byte[length]; + reader.Read(data, length); + return data; + } + + public byte[] ReadChunkedBinary() + { + var data = new List(); + var final = false; + + while (!final) { + var tag = reader.ReadByte(); + final = tag == 'B'; + var length = reader.ReadShort(); + var buff = new byte[length]; + reader.Read(buff, length); + data.AddRange(buff); + } + + return data.ToArray(); + } + + #endregion Binary + + #region Integer + + public int ReadInteger() + { + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + // Full-length integer encoding is 'I' b0 b1 b2 b3 - i.e. a full 32-bit integer in big-endian order. + if (tag == 0x49) { + return ReadIntegerFull(); + } + + // Ints between -16 and 47 are encoded as value + 0x90. + if (tag >= 0x80 && tag <= 0xBF) { + return ReadIntegerSingleByte(); + } + + // Ints between -2048 and 2047 can be encoded as two octets with the leading byte from 0xC0 to 0xCF. + if (tag >= 0xC0 && tag <= 0xCF) { + return ReadIntegerTwoBytes(); + } + + // Ints between -262144 and 262143 can be three bytes with the first from 0xD0 to 0xD7. + if (tag >= 0xD0 && tag <= 0xD7) { + return ReadIntegerThreeBytes(); + } + + throw new UnexpectedTagException(tag.Value, "integer"); + } + + private int ReadIntegerFull() + { + reader.ReadByte(); // Discard tag. + byte b0 = reader.ReadByte(), + b1 = reader.ReadByte(), + b2 = reader.ReadByte(), + b3 = reader.ReadByte(); + + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; + } + + private int ReadIntegerSingleByte() + { + return reader.ReadByte() - 0x90; + } + + private int ReadIntegerTwoBytes() + { + byte b0 = reader.ReadByte(), + b1 = reader.ReadByte(); + + return ((b0 - 0xC8) << 8) | b1; + } + + private int ReadIntegerThreeBytes() + { + byte b0 = reader.ReadByte(), + b1 = reader.ReadByte(), + b2 = reader.ReadByte(); + + return ((b0 - 0xD4) << 16) | (b1 << 8) | b2; + } + + #endregion Integer + + #region Class Definition + + public ClassDef ReadClassDefinition() + { + var tag = reader.ReadByte(); + if (tag != 'C') { + throw new UnexpectedTagException(tag, "classdef"); + } + var name = ReadString(); + var fieldCount = ReadInteger(); + var fields = new string[fieldCount]; + for (var i = 0; i < fields.Length; ++i) { + fields[i] = ReadString(); + } + + var classDef = new ClassDef(name, fields); + + classDefs.Add(classDef); + + return classDef; + } + + #endregion Class Definition + + #region Double + + public double ReadDouble() + { + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + if (tag == 0x44) { + return ReadFullDouble(); + } + + if (tag == 0x5B || tag == 0x5C) { + return ReadDoubleOneByte(); + } + + if (tag == 0x5D) { + return ReadDoubleTwoBytes(); + } + + if (tag == 0x5E) { + return ReadDoubleThreeBytes(); + } + + if (tag == 0x5F) { + return ReadDoubleFourBytes(); + } + + throw new UnexpectedTagException(tag.Value, "double"); + } + + private double ReadFullDouble() + { + var data = new byte[9]; // 9 bytes: tag + IEEE 8-byte double + reader.Read(data, data.Length); + return BitConverter.ToDouble(data, 1); + } + + private double ReadDoubleOneByte() + { + // 0x5B encodes the double value 0.0, and 0x5C encodes 1.0. + return reader.ReadByte() - 0x5B; + } + + private double ReadDoubleTwoBytes() + { + // Doubles representing integral values between -128.0 and 127.0 are + // encoded as single bytes. Java bytes are signed, .NET bytes aren't, + // so we have to cast it first. + reader.ReadByte(); + return (sbyte) reader.ReadByte(); + } + + private double ReadDoubleThreeBytes() + { + // Doubles representing integral values between -32768.0 and 32767.0 are + // encoded as two-byte integers. + reader.ReadByte(); + return reader.ReadShort(); + } + + private double ReadDoubleFourBytes() + { + // Doubles that can be represented as singles are thusly encoded. + var data = new byte[5]; + reader.Read(data, data.Length); + return BitConverter.ToSingle(data, 0); + } + + #endregion Double + + public bool ReadBoolean() + { + var tag = reader.ReadByte(); + + switch (tag) { + case 0x46: return false; + case 0x54: return true; + } + + throw new UnexpectedTagException(tag, "boolean"); + } + + #region Date + + public DateTime ReadDate() + { + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + if (tag == 0x4A) { + return ReadDateInMillis(); + } + + if (tag == 0x4B) { + return ReadDateInMinutes(); + } + + throw new UnexpectedTagException(tag.Value, "date"); + } + + private DateTime ReadDateInMillis() + { + var data = new byte[9]; + reader.Read(data, data.Length); + var millis = LongFromBytes(data, 1); + return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(millis); + } + + private DateTime ReadDateInMinutes() + { + var data = new byte[5]; + reader.Read(data, data.Length); + var minutes = IntFromBytes(data, 1); + return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMinutes(minutes); + } + + #endregion Date + + #region Long + + public long ReadLong() + { + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + if (tag == 0x4C) { + return ReadLongFull(); + } + + if (tag >= 0xD8 && tag <= 0xEF) { + return ReadLongOneByte(); + } + + if (tag >= 0xF0 && tag <= 0xFF) { + return ReadLongTwoBytes(); + } + + if (tag >= 0x38 && tag <= 0x3F) { + return ReadLongThreeBytes(); + } + + if (tag == 0x59) { + return ReadLongFourBytes(); + } + + throw new UnexpectedTagException(tag.Value, "long"); + } + + private long ReadLongFull() + { + var data = new byte[9]; + reader.Read(data, data.Length); + return LongFromBytes(data, 1); + } + + private long ReadLongOneByte() + { + return reader.ReadByte() - 0xE0; + } + + private long ReadLongTwoBytes() + { + byte b0 = reader.ReadByte(), + b1 = reader.ReadByte(); + + return ((b0 - 0xF8) << 8) | b1; + } + + private long ReadLongThreeBytes() + { + byte b0 = reader.ReadByte(), + b1 = reader.ReadByte(), + b2 = reader.ReadByte(); + + return ((b0 - 0x3C) << 16) | (b1 << 8) | b2; + } + + private long ReadLongFourBytes() + { + var data = new byte[5]; + reader.Read(data, data.Length); + return IntFromBytes(data, 1); + } + + #endregion Long + + #region Dictionary/Map + + public IDictionary ReadMap() + { + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + if (tag == 'H') { + return ReadUntypedMap(); + } + + if (tag == 'M') { + return ReadTypedMap(); + } + + throw new UnexpectedTagException(tag.Value, "map"); + } + + private IDictionary ReadUntypedMap() + { + reader.ReadByte(); + return ReadMapCore(); + } + + private IDictionary ReadTypedMap() + { + reader.ReadByte(); + var typeName = ReadTypeName(); + return ReadMapCore(typeName); + } + + private IDictionary ReadMapCore(string type = null) + { + IDictionary dictionary; + if (type == null || !dictTypeResolver.Value.TryGetInstance("", out dictionary)) { + dictionary = new Dictionary(); + } + + objectRefs.Add(dictionary); + + while (true) { + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + if (tag == 'Z') { + break; + } + + var key = ReadValue(); + var value = ReadValue(); + dictionary.Add(key, value); + } + + return dictionary; + } + + #endregion + + #region Object + + public object ReadObject() + { + var tag = reader.Peek(); + + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + + if (tag == 'O') { + return ReadObjectFull(); + } + + if (tag >= 0x60 && tag < 0x70) { + return ReadObjectCompact(); + } + + throw new UnexpectedTagException(tag.Value, "object"); + } + + private object ReadObjectFull() + { + reader.ReadByte(); + var classDefId = ReadInteger(); + var classDef = classDefs.Get(classDefId); + return ReadObjectCore(classDef); + } + + private object ReadObjectCompact() + { + var classDefId = reader.ReadByte() - 0x60; + var classDef = classDefs.Get(classDefId); + return ReadObjectCore(classDef); + } + + private object ReadObjectCore(ClassDef classDef) + { + // XXX: This needs a better implementation - maybe, you know, constructing + // the requested type? + var builder = HessianObject.Builder.New(classDef.Name); + objectRefs.Add(builder.Object); + + foreach (var field in classDef.Fields) { + builder.Add(field, ReadValue()); + } + + return builder.Create(); + } + + #endregion + + public object ReadNull() + { + reader.ReadByte(); + return null; + } + + public object ReadRef() + { + var tag = reader.Peek(); + if (!tag.HasValue) { + throw new EndOfStreamException(); + } + if (tag != 0x51) { + throw new UnexpectedTagException(tag.Value, "ref"); + } + return objectRefs.Get(ReadInteger()); + } + + private static int IntFromBytes(byte[] buffer, int offset) + { + return (buffer[offset + 0] << 0x18) + | (buffer[offset + 1] << 0x10) + | (buffer[offset + 2] << 0x08) + | (buffer[offset + 3] << 0x00); + } + + private static long LongFromBytes(byte[] buffer, int offset) + { + return (buffer[offset + 0] << 0x38) + | (buffer[offset + 1] << 0x30) + | (buffer[offset + 2] << 0x28) + | (buffer[offset + 3] << 0x20) + | (buffer[offset + 4] << 0x18) + | (buffer[offset + 5] << 0x10) + | (buffer[offset + 6] << 0x08) + | (buffer[offset + 7] << 0x00); + } + } +} + diff --git a/src/Hessian/DictionaryTypeResolver.cs b/src/Hessian/DictionaryTypeResolver.cs new file mode 100644 index 0000000..06748b4 --- /dev/null +++ b/src/Hessian/DictionaryTypeResolver.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Hessian +{ + public class DictionaryTypeResolver + { + private readonly Dictionary>> constructors; + + public DictionaryTypeResolver() + { + constructors = new Dictionary>> { + {"System.Collections.Hashtable", DefaultCtor}, + {"System.Collections.Generic.IDictionary`2", DefaultCtor}, + {"System.Collections.Generic.Dictionary`2", DefaultCtor}, + {"System.Collections.IDictionary", DefaultCtor}, + {"java.lang.Map", DefaultCtor}, + {"java.util.HashMap", DefaultCtor}, + {"java.util.EnumMap", DefaultCtor}, + {"java.util.TreeMap", DefaultCtor}, + {"java.util.concurrent.ConcurrentHashMap", DefaultCtor} + }; + + } + + public bool TryGetInstance(string type, out IDictionary instance) + { + instance = null; + + if (!constructors.TryGetValue(type, out var ctor)) { + return false; + } + + instance = ctor(); + return true; + } + + private static IDictionary DefaultCtor() + { + return new Dictionary(); + } + } +} diff --git a/src/Hessian/Hessian.csproj b/src/Hessian/Hessian.csproj new file mode 100644 index 0000000..ec0b0b9 --- /dev/null +++ b/src/Hessian/Hessian.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp2.2 + + + diff --git a/src/Hessian/HessianException.cs b/src/Hessian/HessianException.cs new file mode 100644 index 0000000..7a3fdc3 --- /dev/null +++ b/src/Hessian/HessianException.cs @@ -0,0 +1,24 @@ +using System; + +namespace Hessian +{ + public class HessianException : ApplicationException + { + public HessianException() + { + + } + + public HessianException(string message) + : base(message) + { + + } + + public HessianException(string message, Exception innerException) + : base(message, innerException) + { + + } + } +} diff --git a/src/Hessian/HessianObject.cs b/src/Hessian/HessianObject.cs new file mode 100644 index 0000000..1e87a61 --- /dev/null +++ b/src/Hessian/HessianObject.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Hessian +{ + public class HessianObject : IReadOnlyCollection> + { + private readonly string typeName; + private readonly IDictionary fields; + + public string TypeName + { + get { return typeName; } + } + + public object this[string key] + { + get { return fields[key]; } + } + + public int Count + { + get { return fields.Count; } + } + + private HessianObject(string typeName) + { + this.typeName = Conditions.CheckNotNull(typeName, "typeName"); + fields = new Dictionary(); + } + + public IEnumerator> GetEnumerator() + { + return fields.Select(kvp => Tuple.Create(kvp.Key, kvp.Value)).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public class Builder + { + private readonly HessianObject obj; + + public HessianObject Object + { + get { return obj; } + } + + private Builder(string typeName) + { + obj = new HessianObject(typeName); + } + + public static Builder New(string typeName) + { + return new Builder(typeName); + } + + public Builder Add(string field, object value) + { + obj.fields.Add(field, value); + return this; + } + + public HessianObject Create() + { + return obj; + } + } + } +} diff --git a/src/Hessian/InvalidRefException.cs b/src/Hessian/InvalidRefException.cs new file mode 100644 index 0000000..2cc177a --- /dev/null +++ b/src/Hessian/InvalidRefException.cs @@ -0,0 +1,15 @@ +using System; + +namespace Hessian +{ + public class InvalidRefException : HessianException + { + public int RefId { get; private set; } + + public InvalidRefException(int refId) + : base(String.Format("Invalid ref ID: {0}", refId)) + { + RefId = refId; + } + } +} diff --git a/src/Hessian/ListTypeResolver.cs b/src/Hessian/ListTypeResolver.cs new file mode 100644 index 0000000..07c2c3b --- /dev/null +++ b/src/Hessian/ListTypeResolver.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Hessian +{ + public class ListTypeResolver + { + private readonly Dictionary>> constructors = new Dictionary>>(); + private readonly Dictionary>> length_constructors = new Dictionary>>(); + private readonly Func> empty_list_ctor = () => new List(); + private readonly Func> empty_list_ctor_with_length = length => new List(length); + + public ListTypeResolver() + { + constructors.Add("System.Collections.ArrayList", empty_list_ctor); + constructors.Add("System.Collections.List", empty_list_ctor); + constructors.Add("System.Collections.IList", empty_list_ctor); + constructors.Add("System.Collections.Generic.List`1", empty_list_ctor); + constructors.Add("System.Collections.Generic.IList`1", empty_list_ctor); + constructors.Add("System.Collections.ObjectModel.Collection`1", () => new Collection()); + constructors.Add("java.util.List", empty_list_ctor); + constructors.Add("java.util.Vector", empty_list_ctor); + constructors.Add("java.util.ArrayList", empty_list_ctor); + constructors.Add("java.util.LinkedList", empty_list_ctor); + + length_constructors.Add("System.Collections.List", empty_list_ctor_with_length); + length_constructors.Add("System.Collections.IList", empty_list_ctor_with_length); + length_constructors.Add("System.Collections.Generic.List`1", empty_list_ctor_with_length); + length_constructors.Add("System.Collections.Generic.IList`1", empty_list_ctor_with_length); + length_constructors.Add("java.util.List", empty_list_ctor_with_length); + length_constructors.Add("java.util.Vector", empty_list_ctor_with_length); + length_constructors.Add("java.util.ArrayList", empty_list_ctor_with_length); + length_constructors.Add("java.util.LinkedList", empty_list_ctor_with_length); + } + + public bool TryGetListInstance(string type, out IList list) + { + list = null; + + Func> ctor; + if (!constructors.TryGetValue(type, out ctor)) { + return false; + } + + list = ctor(); + return true; + } + + public bool TryGetListInstance(string type, int length, out IList list) + { + list = null; + + Func> ctor; + if (!length_constructors.TryGetValue(type, out ctor)) { + return false; + } + + list = ctor(length); + return true; + } + } +} diff --git a/src/Hessian/Main.cs b/src/Hessian/Main.cs new file mode 100644 index 0000000..59bfcfc --- /dev/null +++ b/src/Hessian/Main.cs @@ -0,0 +1,24 @@ +using System; +using System.IO; +using System.Text; + +namespace Hessian +{ + class MainClass + { + public static void Main (string[] args) + { + Console.WriteLine ("Hello World!"); + var bytes = Encoding.UTF8.GetBytes("Hessian"); + var ms = new MemoryStream(); + ms.WriteByte((byte)"Hessian".Length); + ms.Write (bytes, 0, bytes.Length); + ms.Position = 0; + + var ds = new Deserializer(ms); + + var actual = ds.ReadValue(); + Console.WriteLine(actual); + } + } +} diff --git a/src/Hessian/PeekStream.cs b/src/Hessian/PeekStream.cs new file mode 100644 index 0000000..f03f496 --- /dev/null +++ b/src/Hessian/PeekStream.cs @@ -0,0 +1,142 @@ +using System; +using System.IO; + +namespace Hessian +{ + public class PeekStream : Stream + { + private Stream inner; + private byte? peek; + + public PeekStream(Stream inner) + { + if (inner == null) { + throw new ArgumentNullException("inner"); + } + + this.inner = inner; + this.peek = null; + } + + public override bool CanRead { + get { + return inner.CanRead; + } + } + + public override bool CanSeek { + get { + return false; + } + } + + public override bool CanWrite { + get { + return false; + } + } + + public override long Length { + get { + return inner.Length; + } + } + + public override long Position { + get { + return inner.Position - (peek.HasValue ? 1 : 0); + } + set { + throw new NotSupportedException("Seeking not supported."); + } + } + + public byte? Peek () + { + if (!peek.HasValue) { + var b = inner.ReadByte(); + + if (b == -1) { + return null; + } + + peek = (byte) b; + } + + return peek; + } + + public override int ReadByte () + { + if (peek.HasValue) { + var val = peek.Value; + peek = null; + return val; + } + + return inner.ReadByte(); + } + + public override int Read (byte[] buffer, int offset, int count) + { + Conditions.CheckNotNull(buffer, "buffer"); + Conditions.CheckGreaterOrEqual(offset, 0, "offset"); + Conditions.CheckLess(offset, buffer.Length, "offset"); + Conditions.CheckGreaterOrEqual(count, 0, "count"); + Conditions.CheckArgument( + offset + count < buffer.Length, + "Buffer is not big enough to contain the requested amount of data at the given offset."); + + if (count == 0) { + return 0; + } + + var bytesToRead = count; + + if (peek.HasValue) { + buffer[offset++] = peek.Value; + peek = null; + --bytesToRead; + } + + int bytesRead; + while (bytesToRead > 0 && (bytesRead = inner.Read (buffer, offset, bytesToRead)) != 0) { + offset += bytesRead; + bytesToRead -= bytesRead; + } + + return count - bytesToRead; + } + + public override void Write (byte[] buffer, int offset, int count) + { + throw new NotSupportedException("Writes not supported."); + } + + public override void SetLength (long value) + { + throw new NotSupportedException("Seeking not supported."); + } + + public override long Seek (long offset, SeekOrigin origin) + { + throw new NotSupportedException("Seeking not supported."); + } + + public override void Flush () + { + throw new NotSupportedException("Writes not supported."); + } + + protected override void Dispose (bool disposing) + { + if (inner != null) { + inner.Dispose (); + inner = null; + } + + base.Dispose (disposing); + } + } +} + diff --git a/src/Hessian/Platform/BigEndianBitConverter.cs b/src/Hessian/Platform/BigEndianBitConverter.cs new file mode 100644 index 0000000..ea97841 --- /dev/null +++ b/src/Hessian/Platform/BigEndianBitConverter.cs @@ -0,0 +1,25 @@ +namespace Hessian.Platform +{ + public class BigEndianBitConverter : EndianBitConverter + { + protected override long FromBytes(byte[] bytes, int offset, int count) + { + var result = 0L; + for (var i = 0; i < count; ++i) + { + result = (result << 8) | bytes[offset + i]; + } + return result; + } + + protected override void CopyBytes(long source, byte[] buffer, int offset, int count) + { + var end = offset + count - 1; + for (var i = 0; i < count; ++i) + { + buffer[end - i] = (byte)(source & 0xFF); + source >>= 8; + } + } + } +} diff --git a/src/Hessian/Platform/EndianBitConverter.cs b/src/Hessian/Platform/EndianBitConverter.cs new file mode 100644 index 0000000..523f1b3 --- /dev/null +++ b/src/Hessian/Platform/EndianBitConverter.cs @@ -0,0 +1,215 @@ +using System; +using System.Runtime.InteropServices; + +namespace Hessian.Platform +{ + public abstract class EndianBitConverter + { + #region T -> byte[] + + public byte[] GetBytes(bool value) + { + // One byte, no endianness + return BitConverter.GetBytes(value); + } + + public byte[] GetBytes(char value) + { + return GetBytes(value, sizeof (char)); + } + + public byte[] GetBytes(short value) + { + return GetBytes(value, sizeof (short)); + } + + public byte[] GetBytes(ushort value) + { + return GetBytes(value, sizeof (ushort)); + } + + public byte[] GetBytes(int value) + { + return GetBytes(value, sizeof (int)); + } + + public byte[] GetBytes(uint value) + { + return GetBytes(value, sizeof (uint)); + } + + public byte[] GetBytes(long value) + { + return GetBytes(value, sizeof (long)); + } + + public byte[] GetBytes(ulong value) + { + return GetBytes((long)value, sizeof (ulong)); + } + + public byte[] GetBytes(float value) + { + return GetBytes(SingleToInt32(value), sizeof (int)); + } + + public byte[] GetBytes(double value) + { + return GetBytes(DoubleToInt64(value), sizeof (long)); + } + + private byte[] GetBytes(long value, int size) + { + var buffer = new byte[size]; + CopyBytes(value, buffer, 0, size); + return buffer; + } + + #endregion + + #region byte[] -> T + + public bool ToBoolean(byte[] value, int index) + { + // one byte, no endianness + return BitConverter.ToBoolean(value, index); + } + + public char ToChar(byte[] value, int index) + { + return (char) FromBytes(value, index, sizeof (char)); + } + + public short ToInt16(byte[] value, int index) + { + return (short) FromBytes(value, index, sizeof (short)); + } + + public ushort ToUInt16(byte[] value, int index) + { + return (ushort) FromBytes(value, index, sizeof (ushort)); + } + + public int ToInt32(byte[] value, int index) + { + return (int) FromBytes(value, index, sizeof (int)); + } + + public uint ToUInt32(byte[] value, int index) + { + return (uint) FromBytes(value, index, sizeof (uint)); + } + + public long ToInt64(byte[] value, int index) + { + return FromBytes(value, index, sizeof (long)); + } + + public ulong ToUInt64(byte[] value, int index) + { + return (ulong) FromBytes(value, index, sizeof (ulong)); + } + + public float ToSingle(byte[] value, int index) + { + var int32 = (int) FromBytes(value, index, sizeof (int)); + return Int32ToSingle(int32); + } + + public double ToDouble(byte[] value, int index) + { + var int64 = FromBytes(value, index, sizeof (long)); + return Int64ToDouble(int64); + } + + #endregion + + protected abstract long FromBytes(byte[] bytes, int offset, int count); + + protected abstract void CopyBytes(long source, byte[] buffer, int index, int count); + + private static int SingleToInt32(float value) + { + return new JonSkeetUnion32(value).AsInt; + } + + private static float Int32ToSingle(int value) + { + return new JonSkeetUnion32(value).AsFloat; + } + + private static long DoubleToInt64(double value) + { + return new JonSkeetUnion64(value).AsLong; + } + + private static double Int64ToDouble(long value) + { + return new JonSkeetUnion64(value).AsDouble; + } + + [StructLayout(LayoutKind.Explicit)] + private struct JonSkeetUnion32 + { + [FieldOffset(0)] + private readonly int i; + + [FieldOffset(0)] + private readonly float f; + + public int AsInt + { + get { return i; } + } + + public float AsFloat + { + get { return f; } + } + + public JonSkeetUnion32(int value) + { + f = 0; + i = value; + } + + public JonSkeetUnion32(float value) + { + i = 0; + f = value; + } + } + + [StructLayout(LayoutKind.Explicit)] + private struct JonSkeetUnion64 + { + [FieldOffset(0)] + private readonly long l; + + [FieldOffset(0)] + private readonly double d; + + public long AsLong + { + get { return l; } + } + + public double AsDouble + { + get { return d; } + } + + public JonSkeetUnion64(long value) + { + d = 0; + l = value; + } + + public JonSkeetUnion64(double value) + { + l = 0; + d = value; + } + } + } +} diff --git a/src/Hessian/Platform/LittleEndianBitConverter.cs b/src/Hessian/Platform/LittleEndianBitConverter.cs new file mode 100644 index 0000000..187a8cd --- /dev/null +++ b/src/Hessian/Platform/LittleEndianBitConverter.cs @@ -0,0 +1,25 @@ +namespace Hessian.Platform +{ + public class LittleEndianBitConverter : EndianBitConverter + { + protected override long FromBytes(byte[] bytes, int offset, int count) + { + var result = 0L; + var end = offset + count - 1; + for (var i = 0; i < count; ++i) + { + result = (result << 8) | bytes[end - i]; + } + return result; + } + + protected override void CopyBytes(long source, byte[] buffer, int offset, int count) + { + for (var i = 0; i < count; ++i) + { + buffer[offset + i] = (byte)(source & 0xFF); + source >>= 8; + } + } + } +} diff --git a/src/Hessian/StringBuilderExtensions.cs b/src/Hessian/StringBuilderExtensions.cs new file mode 100644 index 0000000..9bf4d20 --- /dev/null +++ b/src/Hessian/StringBuilderExtensions.cs @@ -0,0 +1,39 @@ +using System; +using System.IO; +using System.Text; + +namespace Hessian +{ + public static class StringBuilderExtensions + { + public static StringBuilder AppendCodepoint(this StringBuilder sb, uint codepoint) + { + if (codepoint < 0x10000) { + return sb.Append((char)codepoint); + } + + var n = codepoint - 0x10000; + var high = (char)((n >> 10) + 0xD800); + var low = (char)((n & 0x3FF) + 0xDC00); + + AssertValidSurrogates(high, low); + + return sb + .Append (high) + .Append (low); + } + + [System.Diagnostics.Conditional("DEBUG")] + private static void AssertValidSurrogates (char high, char low) + { + if (!Char.IsHighSurrogate (high)) { + throw new InvalidDataException ("Invalid high surrogate"); + } + + if (!Char.IsLowSurrogate (low)) { + throw new InvalidDataException ("Invalid low surrogate"); + } + } + } +} + diff --git a/src/Hessian/UnexpectedTagException.cs b/src/Hessian/UnexpectedTagException.cs new file mode 100644 index 0000000..4df5ea5 --- /dev/null +++ b/src/Hessian/UnexpectedTagException.cs @@ -0,0 +1,18 @@ +namespace Hessian +{ + public class UnexpectedTagException : HessianException + { + public byte Tag { get; private set; } + + public UnexpectedTagException(byte tag, string expectedType) + : base(FormatErrorMessage(tag, expectedType)) + { + Tag = tag; + } + + private static string FormatErrorMessage(byte tag, string expectedType) + { + return string.Format("{0:X} is not a valid {1} tag.", tag, expectedType); + } + } +} diff --git a/src/Hessian/ValueReader.cs b/src/Hessian/ValueReader.cs new file mode 100644 index 0000000..9b2c127 --- /dev/null +++ b/src/Hessian/ValueReader.cs @@ -0,0 +1,163 @@ +using System; +using System.IO; + +namespace Hessian +{ + public class ValueReader + { + private byte[] buffer = new byte[8]; + private PeekStream stream; + + public ValueReader (Stream stream) + { + this.stream = stream as PeekStream ?? new PeekStream(stream); + } + + public byte? Peek () + { + return stream.Peek (); + } + + public short ReadShort () + { + Read (buffer, 0, 2); + return BitConverter.ToInt16(buffer, 0); + } + + public int ReadInt() + { + Read (buffer, 0, 4); + return BitConverter.ToInt32(buffer, 0); + } + + public uint ReadUtf8Codepoint () + { + const uint replacementChar = 0xFFFD; + + byte b0, b1, b2, b3; + b0 = ReadByte (); + + if (b0 < 0x80) { + return b0; + } + + if (b0 < 0xC2) { + return replacementChar; + } + + if (b0 < 0xE0) { + b1 = ReadByte (); + + if ((b1 ^ 0x80) >= 0x40) { + return replacementChar; + } + + return (b1 & 0x3Fu) | ((b0 & 0x1Fu) << 6); + } + + if (b0 < 0xF0) { + b1 = ReadByte (); + b2 = ReadByte (); + + // Valid range: E0 A0..BF 80..BF + if (b0 == 0xE0 && (b1 ^ 0xA0) >= 0x20) { + return replacementChar; + } + + // Valid range: ED 80..9F 80..BF + if (b0 == 0xED && (b1 ^ 0x80) >= 0x20) { + return replacementChar; + } + + // Valid range: E1..EC 80..BF 80..BF + if ((b1 ^ 0x80) >= 0x40 || (b2 ^ 0x80) >= 0x40) { + return replacementChar; + } + + return (b2 & 0x3Fu) + | ((b1 & 0x3Fu) << 6) + | ((b0 & 0x0Fu) << 12); + } + + if (b0 < 0xF1) { + b1 = ReadByte(); + + if ((b1 ^ 0x90) < 0x30) { + return replacementChar; + } + + b2 = ReadByte(); + b3 = ReadByte(); + + if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) { + return replacementChar; + } + + return (b3 & 0x3Fu) + | ((b2 & 0x3Fu) << 6) + | ((b1 & 0x3Fu) << 12) + | ((b0 & 0x07u) << 18); + } + + if (b0 < 0xF4) { + b1 = ReadByte (); + b2 = ReadByte (); + b3 = ReadByte (); + + // Valid range: F1..F3 80..BF 80..BF 80..BF + if ((b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) + { + return replacementChar; + } + + return (b3 & 0x3Fu) + | ((b2 & 0x3Fu) << 6) + | ((b1 & 0x3Fu) << 12) + | ((b0 & 0x07u) << 18); + } + + if (b0 < 0xF5) { + b1 = ReadByte (); + + // Valid range: F4 80..8F 80..BF 80..BF + if ((b1 ^ 0x80) >= 0x10) { + return replacementChar; + } + + b2 = ReadByte(); + b3 = ReadByte(); + + if ((b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80) + { + return replacementChar; + } + + return (b3 & 0x3Fu) + | ((b2 & 0x3Fu) << 6) + | ((b1 & 0x3Fu) << 12) + | ((b0 & 0x07u) << 18); + } + + return replacementChar; + } + + public byte ReadByte() + { + var b = stream.ReadByte(); + if (b == -1) throw new EndOfStreamException(); + return (byte)b; + } + + public void Read(byte[] buffer, int count) + { + Read (buffer, 0, count); + } + + private void Read(byte[] buffer, int offset, int count) + { + var bytesRead = stream.Read (buffer, offset, count); + if (bytesRead != count) throw new EndOfStreamException(); + } + } +} +