├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── RELEASE.md ├── doc ├── httpie-install.txt ├── readme.txt ├── zzb-box User Guide.txt └── zzb-box 工程规范.txt ├── examples ├── demo-shell │ └── src │ │ └── main │ │ ├── resources │ │ ├── shellRemote.conf │ │ └── tasks.conf │ │ └── scala │ │ └── dshell │ │ ├── ShellApp.scala │ │ └── task │ │ ├── ExeOther.scala │ │ └── Hello.scala ├── project-struct │ ├── .history │ ├── conf │ │ ├── env │ │ │ ├── com │ │ │ │ └── service_a.conf │ │ │ ├── dev │ │ │ │ └── service_a.conf │ │ │ ├── net │ │ │ │ └── service_a.conf │ │ │ ├── org │ │ │ │ └── service_a.conf │ │ │ └── uat │ │ │ │ └── service_a.conf │ │ ├── service_a.conf │ │ ├── service_b.conf │ │ └── srvbox.conf │ ├── deplib │ │ └── h2-1.3.173.jar │ ├── project │ │ ├── Build.scala │ │ ├── BuildSettings.scala │ │ ├── Dependencies.scala │ │ ├── NightlyBuildSupport.scala │ │ ├── Utils.scala │ │ ├── build.properties │ │ └── plugins.sbt │ ├── service-a │ │ └── src │ │ │ ├── main │ │ │ └── scala │ │ │ │ └── demo │ │ │ │ └── sa │ │ │ │ ├── MainApp.scala │ │ │ │ └── ServiceA.scala │ │ │ └── test │ │ │ └── scala │ │ │ └── demo │ │ │ └── sa │ │ │ └── test │ │ │ └── TestServiceA.scala │ └── service-b │ │ └── src │ │ ├── main │ │ └── scala │ │ │ └── demo │ │ │ └── sb │ │ │ └── ServiceB.scala │ │ └── test │ │ └── scala │ │ └── demo │ │ └── sb │ │ └── test │ │ └── TestServiceB.scala └── srvbox-demoService │ └── src │ ├── main │ ├── resources │ │ ├── client.jks │ │ ├── demo.conf │ │ ├── logback.xml │ │ ├── server.jks │ │ └── srvbox.conf │ └── scala │ │ └── zzb │ │ └── srvdemo │ │ ├── DBOperate.scala │ │ ├── DemoProtocol.scala │ │ ├── DemoService.scala │ │ ├── HttpClientTest.scala │ │ ├── SomeActor.scala │ │ ├── akka_rest │ │ ├── AkkaRestHttpApi.scala │ │ ├── DemoRestActor.scala │ │ └── UserRestActor.scala │ │ ├── entites │ │ └── Entites.scala │ │ ├── http_rest │ │ └── UserHttpActor.scala │ │ └── schema │ │ └── Schemas.scala │ └── test │ └── scala │ └── zzb │ └── srvdemo │ └── test │ ├── AkkaRestTest.scala │ ├── DBOperatorTest.scala │ └── RestFulApiTest.scala ├── project ├── Build.scala ├── BuildSettings.scala ├── Dependencies.scala ├── NightlyBuildSupport.scala ├── Utils.scala ├── build.properties └── plugins.sbt ├── tools └── ez_setup.py ├── zzb-box └── src │ ├── main │ ├── resources │ │ ├── defaultBoxedService.conf │ │ ├── defaultRemoteManage.conf │ │ └── srv-task.conf │ └── scala │ │ └── zzb │ │ ├── service │ │ ├── BoxedService.scala │ │ ├── RestSupport.scala │ │ └── ServiceFilter.scala │ │ └── srvbox │ │ ├── BoxActor.scala │ │ ├── BoxApp.scala │ │ ├── BoxBuilder.scala │ │ ├── RestInterface.scala │ │ ├── ServiceActor.scala │ │ ├── ServiceManagerActor.scala │ │ ├── SrvManageProtocol.scala │ │ └── task │ │ └── ServiceManage.scala │ └── test │ ├── resources │ ├── S1.conf │ ├── S2.conf │ ├── remoteManage.conf │ ├── srvbox.conf │ └── testConfigs │ │ ├── namesNotList.conf │ │ ├── noExistSubServiceClassName.conf │ │ ├── noNames.conf │ │ ├── noServiceClass.conf │ │ ├── noServices.conf │ │ ├── noSubServiceConfigFile.conf │ │ └── spec.conf │ └── scala │ └── zzb │ ├── StopSystemAfterAll.scala │ └── srvbox │ ├── BoxActorTest.scala │ ├── BoxBuilderTest.scala │ ├── ConfigCheckTest.scala │ ├── FooService.scala │ ├── RemoteManageTest.scala │ └── ServiceActorTest.scala ├── zzb-config └── src │ ├── main │ └── scala │ │ └── zzb │ │ └── config │ │ └── EnvConfigLoader.scala │ └── test │ ├── resources │ ├── SomeConfig.conf │ ├── env │ │ ├── com │ │ │ └── SomeConfig.conf │ │ ├── dev │ │ │ └── SomeConfig.conf │ │ ├── net │ │ │ └── SomeConfig.conf │ │ ├── org │ │ │ └── SomeConfig.conf │ │ └── uat │ │ │ └── SomeConfig.conf │ └── noEnvConfig.conf │ └── scala │ └── zzb │ └── config │ └── EnvConfigTest.scala ├── zzb-datatype └── src │ ├── main │ └── scala │ │ └── zzb │ │ └── datatype │ │ ├── BasicJsonFormats.scala │ │ ├── BasicTypes.scala │ │ ├── DataType.scala │ │ ├── Extractor.scala │ │ ├── FuncType.scala │ │ ├── StructPath.scala │ │ ├── TFixKeysMap.scala │ │ ├── TList.scala │ │ ├── TMap.scala │ │ ├── TMono.scala │ │ ├── TProperty.scala │ │ ├── TStruct.scala │ │ ├── TVariant.scala │ │ ├── Versioned.scala │ │ ├── meta │ │ └── TypeInfo.scala │ │ └── package.scala │ └── test │ └── scala │ └── zzb │ └── datatype │ ├── AnyToPackTest.scala │ ├── BasicTypeTest.scala │ ├── DataCopyTest.scala │ ├── DateTimeParseTest.scala │ ├── EnsureTest.scala │ ├── FuncTypeTest.scala │ ├── JsonSprayTest.scala │ ├── JsonTest.scala │ ├── ListTypeTest.scala │ ├── MacroTypeTest.scala │ ├── MapTypeTest.scala │ ├── MetaInfoTest.scala │ ├── StructPathTest.scala │ ├── StructTransTest.scala │ ├── StructTypeTest.scala │ ├── StructXorTest.scala │ ├── TPropertyTest.scala │ ├── ValidTest.scala │ ├── VariantTest.scala │ ├── demo │ ├── CarIns.scala │ └── Ensure.scala │ └── testuse │ ├── UserInfo.scala │ ├── package.scala │ └── path │ └── User.scala ├── zzb-dbaccess └── src │ ├── main │ └── scala │ │ └── zzb │ │ └── db │ │ ├── DBAccess.scala │ │ ├── DBAccessSlick.scala │ │ ├── DBPools.scala │ │ ├── MongoAccess.scala │ │ └── Mongos.scala │ └── test │ ├── resources │ ├── DBSupport.conf │ └── DBSupport2.conf │ └── scala │ └── zzb │ └── db │ ├── DBSlickTest.scala │ ├── DBTest.scala │ ├── entites │ ├── BaseEntity.scala │ ├── Company.scala │ ├── Entites.scala │ └── User.scala │ └── schema │ ├── Schemas.scala │ └── SchemasSlick.scala ├── zzb-domain └── src │ ├── main │ ├── resources │ │ └── theme │ │ │ └── css │ │ │ ├── background.gif │ │ │ ├── stylesheet.css │ │ │ ├── tab.gif │ │ │ ├── titlebar.gif │ │ │ └── titlebar_end.gif │ ├── scala │ │ └── zzb │ │ │ └── domain │ │ │ ├── Action.scala │ │ │ ├── ActionError.scala │ │ │ ├── AlterSupport.scala │ │ │ ├── DomainActor.scala │ │ │ ├── DomainDirectives.scala │ │ │ ├── DomainFSM.scala │ │ │ ├── DomainLogging.scala │ │ │ ├── DomainSetActor.scala │ │ │ ├── WithDocCreatedDomainSetActor.scala │ │ │ ├── directive │ │ │ └── AuthorizeDirectives.scala │ │ │ ├── http │ │ │ ├── DomainHttpApi.scala │ │ │ ├── DomainHttpDirectives.scala │ │ │ └── TypeViewApi.scala │ │ │ └── package.scala │ └── twirl │ │ ├── typeList.scala.html │ │ ├── typz.scala.html │ │ └── typzenum.scala.html │ └── test │ ├── resources │ └── logback-test.xml │ └── scala │ └── zzb │ └── domain │ ├── ActionExtractorTest.scala │ ├── ActionPostEntityTest.scala │ ├── AlterMonitorTest.scala │ ├── DirectAlterMergeTest.scala │ ├── DirectAlterTest.scala │ ├── ErrorRequestTest.scala │ ├── MergeTest.scala │ ├── PlaneAlterTest.scala │ ├── PlaneHttpApiTest.scala │ ├── PlaneHttpTestBase.scala │ ├── PlaneListTest.scala │ ├── ResponderActorTest.scala │ ├── VersionsTest.scala │ ├── directive │ └── AuthorizeDirectivesTest.scala │ └── plane │ ├── Plane.scala │ ├── PlaneActor.scala │ ├── PlaneHttpApi.scala │ ├── PlaneSetActor.scala │ ├── PlaneSvcActor.scala │ └── package.scala ├── zzb-rest-testkit └── src │ └── main │ └── scala │ └── zzb │ └── rest │ └── testkit │ ├── RouteResultComponent.scala │ ├── RouteTest.scala │ ├── ScalatestInterface.scala │ ├── Specs2Interface.scala │ └── TestFrameworkInterface.scala ├── zzb-rest-tests └── src │ └── test │ └── scala │ └── zzb │ └── rest │ └── directives │ ├── BasicDirectivesSpec.scala │ ├── ExecutionDirectivesSpec.scala │ ├── FutureDirectivesSpec.scala │ ├── HeaderDirectivesSpec.scala │ ├── MethodDirectivesSpec.scala │ ├── MiscDirectivesSpec.scala │ ├── ParameterDirectivesSpec.scala │ ├── PathDirectivesSpec.scala │ ├── RespondWithDirectivesSpec.scala │ ├── RoutingSpec.scala │ └── SecurityDirectivesSpec.scala ├── zzb-rest └── src │ ├── main │ ├── resources │ │ └── reference.conf │ └── scala │ │ └── zzb │ │ └── rest │ │ ├── ApplyConverter.scala │ │ ├── ApplyConverterInstances.scala │ │ ├── Caller.scala │ │ ├── Directive.scala │ │ ├── Directives.scala │ │ ├── ExceptionHandler.scala │ │ ├── HListDeserializer.scala │ │ ├── HListDeserializerInstances.scala │ │ ├── HListable.scala │ │ ├── ObjectRegistry.scala │ │ ├── Prepender.scala │ │ ├── Rejection.scala │ │ ├── RejectionHandler.scala │ │ ├── RequestBuilding.scala │ │ ├── Requester.scala │ │ ├── ResponderActor.scala │ │ ├── RestEntity.scala │ │ ├── RestMessage.scala │ │ ├── RestReqContext.scala │ │ ├── RestService.scala │ │ ├── RestSettings.scala │ │ ├── Route.scala │ │ ├── RouteConcatenation.scala │ │ ├── StandardRoute.scala │ │ ├── TransformerPipelineSupport.scala │ │ ├── authentication │ │ ├── HttpAuthenticator.scala │ │ ├── LdapAuthConfig.scala │ │ ├── LdapAuthenticator.scala │ │ ├── UserPassAuthenticator.scala │ │ └── package.scala │ │ ├── directives │ │ ├── AnyParamDirectives.scala │ │ ├── BasicDirectives.scala │ │ ├── ExecutionDirectives.scala │ │ ├── FormFieldDirectives.scala │ │ ├── ForwardDirectives.scala │ │ ├── FutureDirectives.scala │ │ ├── HeaderDirectives.scala │ │ ├── MarshallingDirectives.scala │ │ ├── MethodDirectives.scala │ │ ├── MiscDirectives.scala │ │ ├── NameReceptacle.scala │ │ ├── ParameterDirectives.scala │ │ ├── PathDirectives.scala │ │ ├── RespondWithDirectives.scala │ │ ├── RouteDirectives.scala │ │ ├── SecurityDirectives.scala │ │ └── ZzbDatatypeDirectives.scala │ │ ├── http2akka │ │ ├── Http2AkkaDirectives.scala │ │ └── HttpApi.scala │ │ ├── marshalling │ │ ├── BasicMarshallers.scala │ │ ├── BasicToResponseMarshallers.scala │ │ ├── CollectingMarshallingContext.scala │ │ ├── Marshaller.scala │ │ ├── MarshallingContext.scala │ │ ├── MetaMarshallers.scala │ │ ├── MetaToResponseMarshallers.scala │ │ └── package.scala │ │ ├── package.scala │ │ ├── unmarshalling │ │ ├── BasicUnmarshallers.scala │ │ ├── DeserializationError.scala │ │ ├── Deserializer.scala │ │ ├── FromStringDeserializers.scala │ │ ├── SimpleUnmarshaller.scala │ │ ├── Unmarshaller.scala │ │ ├── UnmarshallerLifting.scala │ │ └── package.scala │ │ └── util │ │ ├── IdleReleasable.scala │ │ └── Statable.scala │ ├── multi-jvm │ ├── resources │ │ └── logback-test.xml │ └── scala │ │ └── zzb │ │ └── mvm │ │ └── rest │ │ ├── DataEntityMultiNodeSpec.scala │ │ ├── DataEntityMvmTest.scala │ │ └── STMultiNodeSpec.scala │ └── test │ ├── resources │ └── logback-test.xml │ └── scala │ └── zzb │ └── rest │ ├── DataEntityTest.scala │ ├── RestResponseTransTest.scala │ ├── RestRouteTest.scala │ ├── http2akka │ ├── Http2AkkaTest.scala │ └── HttpApiGenerateTest.scala │ ├── testkit │ └── QuietReporter.scala │ ├── testuse │ └── package.scala │ └── util │ ├── IdleReleasableTest.scala │ └── StatableTest.scala ├── zzb-shell ├── doc │ └── Wash-Shell Quick Guide.txt └── src │ ├── main │ ├── java │ │ └── wash │ │ │ └── shell │ │ │ └── ConsolePrintStream.java │ ├── resources │ │ └── defaultShellRemote.conf │ └── scala │ │ └── zzb │ │ └── shell │ │ ├── Action.scala │ │ ├── CLIException.scala │ │ ├── ConsoleIO.scala │ │ ├── Input.scala │ │ ├── Output.scala │ │ ├── OutputConversionEngine.scala │ │ ├── Shell.scala │ │ ├── ShellManageable.scala │ │ ├── Task.scala │ │ ├── Token.scala │ │ ├── TokenException.scala │ │ ├── remote │ │ ├── ActorOutputStream.scala │ │ ├── RemoteLogin.scala │ │ ├── ShellDaemon.scala │ │ └── ShellProtocol.scala │ │ ├── task │ │ ├── Tasks.scala │ │ ├── Verbose.scala │ │ └── Wildcard.scala │ │ └── util │ │ └── Page.scala │ └── test │ └── scala │ └── zzb │ └── shell │ ├── ActionErrorTest.scala │ ├── ActionStatExeTest.scala │ ├── ArgsParseTest.scala │ ├── AsynTaskTest.scala │ ├── DaemonTest.scala │ ├── PipeTaskTest.scala │ └── TaskTest.scala ├── zzb-storage └── src │ ├── main │ └── scala │ │ └── zzb │ │ └── storage │ │ ├── DBObjectHelper.scala │ │ ├── DocProcessor.scala │ │ ├── Storage.scala │ │ ├── TStorable.scala │ │ ├── dirvers │ │ ├── MemoryDriver.scala │ │ ├── MongoConverter.scala │ │ └── MongoDriver.scala │ │ └── package.scala │ └── test │ └── scala │ └── zzb │ └── storage │ ├── DBObjectHelperTest.scala │ ├── DocProcessorTest.scala │ ├── MongoStorageQueryTestSpec.scala │ ├── StorageTestSpec.scala │ └── data │ └── DataStruct.scala └── zzb-util └── src ├── main └── scala │ └── zzb │ └── util │ ├── IDGenerator.scala │ ├── MdcLoggingContext.scala │ ├── log │ ├── Clock.scala │ ├── HeritLog.scala │ └── MDCFilter.scala │ └── package.scala └── test └── scala └── zzb └── util └── IDGeneratorTest.scala /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea 3 | *\target -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 stepover 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | zzb 2 | === 3 | 4 | Restful framework base on Scala,Akka,Spray 5 | 6 | “zzb” 是一系列工具的集合,主要功能: 7 | 8 | * 服务配置模式的实现。一个服务容器(zzb-box),是一个独立的进程,多个独立的服务可以部署其中。也可以分别部署在不同的zzb-box中 9 | * 基于一套数据类型定义实现从存储到 restful api 的一站式支持(zzb-datatype,zzb-storage,zzb-rest,zzb-domain) 10 | * 为 zzb-box 提供远程shell管理的能力(zzb-shell) 11 | * 一个简单的xmpp包装器,用于支持xmpp机器人 12 | 13 | 14 | 项目当前版本是: 0.1.1 15 | 16 | 在项目中添加依赖 17 | 18 | resolvers += "zzb" at "http://dl.bintray.com/stepover/release" 19 | 20 | libraryDependencies ++= Seq( 21 | "me.stepover" %% "zzb-util" % "0.1.1" 22 | ) 23 | 24 | zzb-box 25 | ------- 26 | 这是一个服务容器模块,所有业务服务都按照zzb-box的标准写的话,就可以把多个服务放到一个容器进程中, 27 | 也可以部署到不同的进程中。详情请参阅 “zzb-box User Guide” 和 “zzb-box 工程规范”两个文档。 28 | 29 | zzb-config 30 | ----------- 31 | 这是对 typesafe config 的简单包装,主要是为了解决不同运行环境中(开发,业务测试,生产系统)配置 32 | 文件装载的问题。参阅“SrvBox 工程规范” 33 | 34 | zzb-dbaccess 35 | ------------ 36 | 这是一个对squeryl的一个很简单的扩展,主要是方便进行连接池的配置和管理,方便同时连接不同的数据库。 37 | 38 | zzb-datatype 39 | ------------ 40 | 这是一个数据类型定义的核心库,用于定义层次化业务数据结构,通过其中的类型元信息能够完成更多复杂的操作。 41 | 42 | zzb-storage 43 | ----------- 44 | 这是为 zzb-datatype 数据类型设计的存储框架,主要目的是提供nosql的存储包装,并支持文档的版本化。 45 | 46 | zzb-rest 47 | -------- 48 | 这是对 spray 的一个抄袭式改写,主要用来实现非http的Rest架构。基于“面向资源的设计”让每一个业务对象 49 | 都是一个有url的Rest资源,对应于一个Actor。其他服务可以直接使用Akka Remote访问。 50 | 51 | zzb-domain 52 | ---------- 53 | 为基于 zzb-datatype 定义的业务对象提供自动的 rest api 访问机制,既可以通过zzb-rest提供的非akka 54 | 方式访问,也可以很容易的映射成 http rest api, 同时提供了业务对象的状态机支持。 55 | 56 | zzb-shell 57 | --------- 58 | 这是一个命令行shell 工具库,可以实现自己的 shell 指令。也可以在应用服务中集成 shell Daemon(Zzb-Box中已集成), 59 | 然后可以远程连接到应用服务,通过shell窗口执行服务器预定义的指令。实现对服务的远程查看和管理。 60 | 61 | zzb-util 62 | -------- 63 | 一些工具,基本没啥东西。 64 | 65 | zzb-xmpp 66 | -------- 67 | XMPP 的Scala包装,基于 eventbus 实现了一个简单易用的xmpp库,支持基本的私聊,进房间,房间内私聊等功能。 68 | 69 | 在项目中添加依赖 70 | 71 | resolvers += "zzb" at "http://dl.bintray.com/stepover/release" 72 | 73 | libraryDependencies ++= Seq( 74 | "me.stepover" % "zzb-util_2.10" % "0.1.0", 75 | ) 76 | -------------------------------------------------------------------------------- /doc/httpie-install.txt: -------------------------------------------------------------------------------- 1 | httpie 是一个很方便的命令行工具,用于发起 Restful API的调用请求。 2 | 我们可以用他来执行 SrvBox 的管理命令。 3 | 4 | Windows 下 httpie 安装说明: 5 | 6 | 1. 首先安装 python ,下载地址 http://www.python.org/ftp/python/3.3.2/ 7 | 2. 执行 srv-utils/tools 目录下两个python脚本 8 | ez_setup.py 9 | 3. 确保 /Scripts 在你的 path 环境变量中 10 | 4. 执行 easy_install httpie 11 | 12 | 安装过程中可能会出现下载中断,再来一次或者本地装一个翻墙的代理。 13 | 14 | 15 | -------------------------------------------------------------------------------- /doc/readme.txt: -------------------------------------------------------------------------------- 1 | zzb 工程介绍 2 | 3 | zzb 是基于Scala-Akka-Spray 的Restful服务器架构,各模块功能简介如下。 4 | 5 | 6 | zzb-box 7 | 这是一个服务容器模块,所有业务服务都按照zzb-box的标准写的话,就可以把多个服务放到一个容器进程中, 8 | 也可以部署到不同的进程中。详情请参阅 “Srv-Box User Guide” 和 “SrvBox 工程规范”两个文档。 9 | 10 | zzb-config 11 | 这是对 typesafe config 的简单包装,主要是为了解决不同运行环境中(开发,业务测试,生产系统)配置 12 | 文件装载的问题。参阅“SrvBox 工程规范” 13 | 14 | zzb-dbaccess 15 | 这是一个对squeryl的一个很简单的扩展,主要是方便进行连接池的配置和管理,方便同时连接不同的数据库。 16 | 17 | zzb-datatype 18 | 这是一个数据类型定义的核心库,用于定义层次化业务数据结构,通过其中的类型元信息能够完成更多复杂的操作。 19 | 20 | zzb-storage 21 | 这是为 zzb-datatype 数据类型设计的存储框架,主要目的是提供nosql的存储包装,并支持文档的版本化。 22 | 23 | zzb-rest 24 | 这是对 spray 的一个抄袭式改写,主要用来实现非http的Rest架构。基于“面向资源的设计”让每一个业务对象 25 | 都是一个有url的Rest资源,对应于一个Actor。其他服务可以直接使用Akka Remote访问。 26 | 27 | zzb-domain 28 | 为基于 zzb-datatype 定义的业务对象提供自动的 rest api 访问机制,既可以通过zzb-rest提供的非akka 29 | 方式访问,也可以很容易的映射成 http rest api, 同时提供了业务对象的状态机支持。 30 | 31 | zzb-shell 32 | 这是一个命令行shell 工具库,可以实现自己的 shell 指令。也可以在应用服务中集成 shell Daemon(Zzb-Box中已集成), 33 | 然后可以远程连接到应用服务,通过shell窗口执行服务器预定义的指令。实现对服务的远程查看和管理。 34 | 35 | zzb-util 36 | 一些工具,基本没啥东西。 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /doc/zzb-box User Guide.txt: -------------------------------------------------------------------------------- 1 | 1. Service Config 设计模式简介 2 | 3 | 先看这篇文章吧 http://docs.huihoo.com/ace_tao/ACE-2002-12/Part-One/Chapter-10.htm 4 | 5 | SrvBox 实现了一个基于 Akka 服务的服务管理器。主要特性: 6 | 7 | ● 所有实现了zzb.service.BoxedService trait 的服务都可以部署在SrvBox进程中 8 | ● 同一个SrvBox进程中的多个 BoxedService 可以独立使用自己的 ActorSystem,也可以共享SrvBox的ActorSystem 9 | ● SrvBox 提供了Restful接口,可以查询当前部署的所有服务状态,可以单独启动或停止某一个服务。 10 | ● 部署在SrvBox中的服务可以支持Restful接口 11 | 12 | 2.参照 examples\srvbox-demoService 实现自己的 BoxedService 服务。 13 | 有以下关键点: 14 | a)依赖项,你的服务项目编译需要依赖srv-service, 15 | 16 | b) 新建服务需要实现 BoxedService trait 17 | c) 程序进程入口是 zzb.srvbox.BoxApp, 所有服务都通过它加载, 18 | 在sbt中要把 project 设置的 mainClass 设置为它(参见 project/BuildSettings.scala 中定义的 boxedServiceSettings) 19 | 在IDE 中要自己修改运行设置。 20 | d) 新建服务如果需要支持Restful接口,只需混入 RestSupport trail,实现其 route 函数(标准的spary route) 21 | 对 http://hostname//... 的访问会被转到你的route实现 22 | e) 提供一个 srvbox.conf 的配置文件 ,这是标准的akka风格文件,其中的 services {} 指定所有可以加载的服务 23 | 例如: 24 | 25 | services { 26 | names = ["demo","service2] 27 | 28 | demo { 29 | serviceClass = "zzb.srvdemo.DemoService" 30 | init-start = 1 31 | share-actorSystem = 1 32 | } 33 | service2{ 34 | serviceClass = "zzb.srvdemo.Service2" 35 | init-start = 1 36 | share-actorSystem = 0 37 | } 38 | path{ 39 | #路径设置 40 | demo=["/test/test.*"] 41 | } 42 | } 43 | 44 | 每个服务都有一个自己的配置文件,文件名称“服务名.conf",服务启动时配置会被加载,并传递到服务对象实例。 45 | init-start 表示服务是否会初始启动,默认值为1 46 | share-actorSystem 表示服务是使用独立的 ActorSystem 还是共享的,默认值为1 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /examples/demo-shell/src/main/resources/shellRemote.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | event-handlers = ["akka.event.slf4j.Slf4jLogger"] 5 | jvm-exit-on-fatal-error = on 6 | 7 | actor { 8 | provider = "akka.remote.RemoteActorRefProvider" 9 | } 10 | 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | netty.tcp { 14 | hostname = "127.0.0.1" 15 | port = 2552 16 | } 17 | } 18 | } 19 | 20 | daemon { 21 | username = "simon" 22 | password = "123456" 23 | session-timeout = 600 # seconds 24 | } -------------------------------------------------------------------------------- /examples/demo-shell/src/main/resources/tasks.conf: -------------------------------------------------------------------------------- 1 | task{ 2 | 3 | P1{ 4 | hello = dshell.task.Hello 5 | thinkhello = dshell.task.ThinkHello 6 | exeother = dshell.task.ExeOther 7 | } 8 | } -------------------------------------------------------------------------------- /examples/demo-shell/src/main/scala/dshell/ShellApp.scala: -------------------------------------------------------------------------------- 1 | package dshell 2 | 3 | 4 | import zzb.shell.{Task, Shell} 5 | import com.typesafe.config.ConfigFactory 6 | import akka.actor.ActorSystem 7 | import zzb.config.EnvConfigLoader 8 | 9 | /** 10 | * Created with IntelliJ IDEA. 11 | * User: Simon Xiao 12 | * Date: 13-9-24 13 | * Time: 下午8:01 14 | * Copyright baoxian.com 2012~2020 15 | */ 16 | object ShellApp extends App with EnvConfigLoader { 17 | 18 | Task.parseConfig(ConfigFactory.load("tasks").getConfig("task")) 19 | 20 | var config = loadConfig("shellRemote").getOrElse( 21 | throw new Exception("can't found shellRemote.conf") 22 | ) 23 | var system = ActorSystem("shell-remote", config) 24 | Shell.init(config,system) 25 | 26 | Shell("demo", "Shell-Demo") 27 | 28 | } 29 | -------------------------------------------------------------------------------- /examples/demo-shell/src/main/scala/dshell/task/ExeOther.scala: -------------------------------------------------------------------------------- 1 | package dshell.task 2 | 3 | import zzb.shell.Task 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-10-28 9 | * Time: 下午4:02 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | class ExeOther extends Task { 13 | 14 | override def usage = "exeother 'other-command-line'" 15 | override def checkArgs = args != Nil 16 | 17 | execOther(args.head) 18 | 19 | } 20 | -------------------------------------------------------------------------------- /examples/demo-shell/src/main/scala/dshell/task/Hello.scala: -------------------------------------------------------------------------------- 1 | package dshell.task 2 | 3 | import zzb.shell.Task 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-9-25 9 | * Time: 下午2:05 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | class Hello extends Task{ 13 | 14 | val title = if(args.size>0) args.head else "" 15 | println("hello " + title) 16 | 17 | } 18 | class ThinkHello extends Task{ 19 | 20 | val title = if(args.size>0) args.head else "" 21 | println("let me think a while") 22 | Thread.sleep(5000) 23 | println("ok,hello " + title+ ", after think 5 seconds") 24 | 25 | } 26 | -------------------------------------------------------------------------------- /examples/project-struct/.history: -------------------------------------------------------------------------------- 1 | update 2 | help 3 | reload 4 | exit 5 | reload plugins 6 | -------------------------------------------------------------------------------- /examples/project-struct/conf/env/com/service_a.conf: -------------------------------------------------------------------------------- 1 | someConfig { 2 | name = "AAA" 3 | } -------------------------------------------------------------------------------- /examples/project-struct/conf/env/dev/service_a.conf: -------------------------------------------------------------------------------- 1 | someConfig { 2 | name = "AAA" 3 | } -------------------------------------------------------------------------------- /examples/project-struct/conf/env/net/service_a.conf: -------------------------------------------------------------------------------- 1 | someConfig { 2 | name = "AAA" 3 | } -------------------------------------------------------------------------------- /examples/project-struct/conf/env/org/service_a.conf: -------------------------------------------------------------------------------- 1 | someConfig { 2 | name = "AAA" 3 | } -------------------------------------------------------------------------------- /examples/project-struct/conf/env/uat/service_a.conf: -------------------------------------------------------------------------------- 1 | someConfig { 2 | name = "AAA" 3 | } -------------------------------------------------------------------------------- /examples/project-struct/conf/service_a.conf: -------------------------------------------------------------------------------- 1 | someConfig { 2 | name = "AAA" 3 | } -------------------------------------------------------------------------------- /examples/project-struct/conf/service_b.conf: -------------------------------------------------------------------------------- 1 | 2 | someConfig { 3 | name = "BBB" 4 | } -------------------------------------------------------------------------------- /examples/project-struct/conf/srvbox.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | 6 | actor { 7 | provider = "akka.remote.RemoteActorRefProvider" 8 | } 9 | 10 | remote { 11 | enabled-transports = ["akka.remote.netty.tcp"] 12 | netty.tcp { 13 | hostname = "0.0.0.0" 14 | port = 2551 15 | } 16 | } 17 | } 18 | 19 | spray { 20 | can { 21 | server { 22 | server-header = "SrvShelf REST API" 23 | } 24 | } 25 | } 26 | 27 | services { 28 | names = ["service_a","service_b"] 29 | 30 | service_a { 31 | serviceClass = "demo.sa.ServiceA" 32 | init-start = 1 33 | share-actorSystem = 1 34 | } 35 | 36 | service_b { 37 | serviceClass = "demo.sb.ServiceB" 38 | init-start = 1 39 | share-actorSystem = 1 40 | } 41 | } 42 | 43 | http { 44 | host = "0.0.0.0" 45 | host = ${?HOST} 46 | port = 5000 47 | port = ${?PORT} 48 | } 49 | -------------------------------------------------------------------------------- /examples/project-struct/deplib/h2-1.3.173.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepover/zzb/0a7fc2518fd3f34b66e22f81c045b54360971657/examples/project-struct/deplib/h2-1.3.173.jar -------------------------------------------------------------------------------- /examples/project-struct/project/Build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | 5 | object Build extends Build { 6 | 7 | import BuildSettings._ 8 | import Dependencies._ 9 | 10 | 11 | // 让 sbt 命令行的提示符显示当前工程的名称 12 | override lazy val settings = super.settings :+ { 13 | shellPrompt := { 14 | s => Project.extract(s).currentProject.id + " > " 15 | } 16 | } 17 | 18 | // ------------------------------------------------------------------------------------------------------------------- 19 | // Root Project 你可以把工程名称“root_project”换成你希望的名字 20 | // ------------------------------------------------------------------------------------------------------------------- 21 | 22 | lazy val root = Project("root_project", file(".")) 23 | .aggregate(service_a, service_b) //定义包含的子模块 24 | .dependsOn(service_a, service_b) //加上对所有子模块的依赖 25 | .settings(rootSetting: _*) //这一行一定要加上,这是sbt-box插件为root project定义的设置,打包时会有用到 26 | 27 | // ------------------------------------------------------------------------------------------------------------------- 28 | // Modules 29 | // ------------------------------------------------------------------------------------------------------------------- 30 | 31 | lazy val service_a = Project("service-a", file("service-a")) //Project 的第一个参数是工程名称,第二个参数是文件相对目录 32 | .settings(moduleSettings: _*) 33 | .settings(boxedServiceSettings: _*) 34 | .settings(libraryDependencies ++= 35 | compile(scalaloggingSlf4j, srvBox) ++ 36 | test(scalatest) ++ 37 | runtime(logback) 38 | ) 39 | lazy val service_b = Project("service-b", file("service-b")) 40 | .dependsOn(service_a) //指定工程的依赖性 41 | .settings(moduleSettings: _*) 42 | .settings(boxedServiceSettings: _*) 43 | .settings(libraryDependencies ++= 44 | compile(scalaloggingSlf4j, srvBox) ++ 45 | test(scalatest) ++ 46 | runtime(logback) 47 | ) 48 | } -------------------------------------------------------------------------------- /examples/project-struct/project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | object Dependencies { 4 | 5 | val resolutionRepos = Seq( 6 | "BaoXian Repository" at "http://maven.baoxan.org:8083/nexus/content/groups/public/", 7 | "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/", 8 | "Sonatype snapshots" at "http://oss.sonatype.org/content/repositories/snapshots/", 9 | "Spray Repository" at "http://repo.spray.io/", 10 | "Spray Nightlies" at "http://nightlies.spray.io/" 11 | ) 12 | 13 | val akkaVersion = "2.2.0" 14 | val sprayVersion = "1.2-20130727" 15 | 16 | def compile(deps: ModuleID*): Seq[ModuleID] = deps map (_ % "compile") 17 | def provided(deps: ModuleID*): Seq[ModuleID] = deps map (_ % "provided") 18 | def test(deps: ModuleID*): Seq[ModuleID] = deps map (_ % "test") 19 | def runtime(deps: ModuleID*): Seq[ModuleID] = deps map (_ % "runtime") 20 | def container(deps: ModuleID*): Seq[ModuleID] = deps map (_ % "container") 21 | 22 | val scalaloggingSlf4j = "com.typesafe" %% "scalalogging-slf4j" % "1.0.1" 23 | val scalatest = "org.scalatest" %% "scalatest" % "1.9.1" 24 | val logback = "ch.qos.logback" % "logback-classic" % "1.0.10" 25 | val specs2 = "org.specs2" %% "specs2" % "1.14" 26 | val srvBox = "com.baoxian.zzb" % "srv-box" % "0.1-SNAPSHOT" 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /examples/project-struct/project/NightlyBuildSupport.scala: -------------------------------------------------------------------------------- 1 | import java.text.SimpleDateFormat 2 | import java.util.Date 3 | import sbt._ 4 | import Keys._ 5 | import Utils._ 6 | 7 | 8 | object NightlyBuildSupport { 9 | val isNightly = sys.props.get("nightly.build") == Some("yes") 10 | 11 | def buildVersion(version: String) = 12 | if (isNightly) version.takeWhile(_ != '-') + new SimpleDateFormat("-yyyyMMdd").format(new Date) 13 | else version 14 | 15 | lazy val settings = seq( 16 | packagedArtifacts <<= (packagedArtifacts, crossTarget) map { (artifacts, dir) => 17 | // if (isNightly) { 18 | // val file = dir / "commit" 19 | // val commit = git("rev-parse", "HEAD") 20 | // IO.write(file, """%1$s""" format commit) 21 | // artifacts + (Artifact(file.getName, "html", "html") -> file) 22 | // } else artifacts 23 | artifacts 24 | } 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /examples/project-struct/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.12.4 2 | -------------------------------------------------------------------------------- /examples/project-struct/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeResolver 2 | 3 | resolvers += "sbt-idea" at "http://mpeltonen.github.com/maven/" 4 | 5 | resolvers += "BaoXian Repository" at "http://maven.baoxan.org:8083/nexus/content/groups/public/" 6 | 7 | resolvers += "Typesafe Repository" at "http://repo.akka.io/releases/" 8 | 9 | resolvers += "Spray Repository" at "http://repo.spray.io/" 10 | 11 | libraryDependencies ++= Seq( 12 | "com.decodified" % "scala-ssh" % "0.6.2", 13 | "com.jcraft" % "jzlib" % "1.1.1", 14 | "org.kamranzafar" % "jtar" % "2.2", 15 | "org.slf4j" % "slf4j-nop" % "1.7.5" 16 | //"org.fusesource.scalate" % "scalate-core_2.9" % "1.6.1" 17 | ) 18 | 19 | 20 | addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.0.1") 21 | 22 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.7.0") 23 | 24 | addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.1") 25 | 26 | //addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.4.2") 27 | 28 | addSbtPlugin("com.baoxian.sbt" %% "sbt-box" % "0.1-RC4") -------------------------------------------------------------------------------- /examples/project-struct/service-a/src/main/scala/demo/sa/MainApp.scala: -------------------------------------------------------------------------------- 1 | package demo.sa 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import akka.actor.ActorSystem 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-8-31 10 | * Time: 下午8:47 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | object MainApp extends App { 14 | val config = ConfigFactory.load("service-a") 15 | 16 | val system = ActorSystem("demoApp", config) 17 | 18 | val service = new ServiceA(system, config) 19 | service.startup("service-a", false) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /examples/project-struct/service-a/src/main/scala/demo/sa/ServiceA.scala: -------------------------------------------------------------------------------- 1 | package demo.sa 2 | 3 | import zzb.service.{ RestSupport, BoxedService } 4 | import akka.actor.{ Props, Actor, ActorSystem } 5 | import com.typesafe.config.Config 6 | import spray.routing._ 7 | 8 | /** 9 | * Created with IntelliJ IDEA. 10 | * User: Simon Xiao 11 | * Date: 13-8-27 12 | * Time: 下午10:13 13 | * Copyright baoxian.com 2012~2020 14 | */ 15 | class ServiceA(system: ActorSystem, config: Config) 16 | extends BoxedService(system, config) with RestSupport { 17 | def fini() {} 18 | 19 | def init() { 20 | 21 | val someActor = system.actorOf(Props[SomeActor], "sa.someActor") 22 | 23 | //测试 deplib 目录是否进入了classpath 24 | Class.forName("org.h2.Driver") 25 | } 26 | 27 | def routes: Route = 28 | path("hello") { 29 | get { 30 | ctx ⇒ 31 | ctx.complete("greeting from service A! ") 32 | } 33 | } 34 | } 35 | 36 | class SomeActor extends Actor { 37 | def receive: Actor.Receive = { 38 | case "hello" ⇒ sender ! "greeting from service A!" 39 | } 40 | } -------------------------------------------------------------------------------- /examples/project-struct/service-a/src/test/scala/demo/sa/test/TestServiceA.scala: -------------------------------------------------------------------------------- 1 | package demo.sa.test 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-8-27 10 | * Time: 下午10:55 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | class TestServiceA extends WordSpec with MustMatchers { 14 | "Service A " must { 15 | "do work one" in { 16 | 17 | } 18 | "do work two" in { 19 | 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/project-struct/service-b/src/main/scala/demo/sb/ServiceB.scala: -------------------------------------------------------------------------------- 1 | package demo.sb 2 | 3 | import akka.actor.{ Props, Actor, ActorSystem } 4 | import com.typesafe.config.Config 5 | import zzb.service.{ RestSupport, BoxedService } 6 | import spray.routing._ 7 | 8 | /** 9 | * Created with IntelliJ IDEA. 10 | * User: Simon Xiao 11 | * Date: 13-8-27 12 | * Time: 下午10:16 13 | * Copyright baoxian.com 2012~2020 14 | */ 15 | class ServiceB(system: ActorSystem, config: Config) 16 | extends BoxedService(system, config) with RestSupport { 17 | def fini() {} 18 | 19 | def init() { 20 | val someActor = system.actorOf(Props[SomeActor], "sb.someActor") 21 | 22 | } 23 | 24 | def routes: Route = 25 | path("hello") { 26 | get { 27 | ctx ⇒ 28 | ctx.complete("greeting from service B!") 29 | } 30 | } 31 | } 32 | 33 | class SomeActor extends Actor { 34 | def receive: Actor.Receive = { 35 | case "hello" ⇒ sender ! "greeting from service B!" 36 | } 37 | } -------------------------------------------------------------------------------- /examples/project-struct/service-b/src/test/scala/demo/sb/test/TestServiceB.scala: -------------------------------------------------------------------------------- 1 | package demo.sb.test 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-8-27 10 | * Time: 下午10:58 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | class TestServiceB extends WordSpec with MustMatchers { 14 | "Service B " must { 15 | "do work one" in { 16 | 17 | } 18 | "do work two" in { 19 | 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/resources/client.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepover/zzb/0a7fc2518fd3f34b66e22f81c045b54360971657/examples/srvbox-demoService/src/main/resources/client.jks -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/resources/demo.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | 6 | actor { 7 | provider = "akka.remote.RemoteActorRefProvider" 8 | 9 | deployment{ 10 | /RestRequestPostman{ 11 | router = "round-robin" 12 | nr-of-instances = 20 13 | } 14 | } 15 | } 16 | 17 | remote { 18 | enabled-transports = ["akka.remote.netty.tcp"] 19 | netty.tcp { 20 | hostname = "0.0.0.0" 21 | port = 2559 22 | } 23 | } 24 | } 25 | 26 | db { 27 | usedb= ["userdb","infodb"] 28 | 29 | userdb { 30 | driver = "org.h2.Driver" 31 | url = "jdbc:h2:~/userdb" 32 | user = "sa" 33 | password = "" 34 | pool{ 35 | min = 2 36 | max = 10 37 | inc = 1 38 | testTable = ping 39 | } 40 | } 41 | 42 | infodb { 43 | driver = "org.h2.Driver" 44 | url = "jdbc:h2:~/infodb" 45 | user = "sa" 46 | password = "" 47 | pool{ 48 | min = 2 49 | max = 10 50 | inc = 1 51 | testTable = ping 52 | } 53 | } 54 | 55 | no_use { 56 | driver = "com.mysql.jdbc.Driver" 57 | url = "jdbc:mysql://localhost:3306/firstdb" 58 | } 59 | } 60 | 61 | mongo { 62 | usedb= ["userdb"] 63 | 64 | userdb { 65 | uri="mongodb://10.68.3.157:27017/" 66 | db = "userdb" 67 | } 68 | 69 | no_use { 70 | uri="mongodb://10.68.3.157:27017/" 71 | db = "test" 72 | } 73 | } 74 | 75 | filter{ 76 | boxActor="/user/boxActor" 77 | services=[] 78 | } 79 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.out 5 | 6 | 7 | %-6level[%logger{0}]: %msg%n 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/resources/server.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepover/zzb/0a7fc2518fd3f34b66e22f81c045b54360971657/examples/srvbox-demoService/src/main/resources/server.jks -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/resources/srvbox.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | loggers = ["akka.event.slf4j.Slf4jLogger"] 5 | jvm-exit-on-fatal-error = on 6 | 7 | actor { 8 | provider = "akka.remote.RemoteActorRefProvider" 9 | } 10 | 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | netty{ 14 | tcp { 15 | hostname = "127.0.0.1" 16 | port = 8559 17 | } 18 | } 19 | } 20 | } 21 | 22 | spray { 23 | can { 24 | server { 25 | server-header = "SrvShelf REST API" 26 | } 27 | } 28 | } 29 | 30 | services { 31 | names = ["demo"] 32 | 33 | demo { 34 | serviceClass = "zzb.srvdemo.DemoService" 35 | init-start = 1 36 | share-actorSystem = 1 37 | } 38 | 39 | path{ 40 | #路径设置 41 | demo=["/test/test.*"] 42 | } 43 | } 44 | 45 | http { 46 | isHttpOpen=true 47 | isHttpsOpen=true 48 | host = "0.0.0.0" 49 | host = ${?HOST} 50 | port = 5900 51 | port = ${?PORT} 52 | https-port=5901 53 | keystore{ 54 | path="server.jks" 55 | password="123456" 56 | } 57 | } 58 | 59 | remoteManage { 60 | enable= 1 61 | appname = "demo-service" 62 | daemon{ 63 | username = "simon" 64 | password = "123456" 65 | session-timeout = 600 # seconds 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/scala/zzb/srvdemo/DBOperate.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvdemo 2 | 3 | import zzb.db.DBAccess 4 | import zzb.srvdemo.schema._ 5 | import zzb.srvdemo.entites._ 6 | 7 | /** 8 | * Created with IntelliJ IDEA. 9 | * User: Simon Xiao 10 | * Date: 13-8-21 11 | * Time: 上午11:00 12 | * Copyright baoxian.com 2012~2020 13 | */ 14 | object DBOperate extends DBAccess { 15 | 16 | //这个只是让Demo服务每次运行都是从头开始,实际服务不要干这个事情哦。 17 | //实际生产服务不要做 schema create 的操作,表结构手动创建好。 18 | def reCreateDb() = { 19 | transaction("userdb") { 20 | UserDBSchema.drop //先清掉所有表 21 | UserDBSchema.create //创建所有表 22 | } 23 | transaction("infodb") { 24 | InfoDBSchema.drop //先清掉所有表 25 | InfoDBSchema.create //创建所有表 26 | } 27 | } 28 | 29 | def addUser(user: User) = { 30 | transaction("userdb") { 31 | UserDBSchema.users.insert(user) 32 | } 33 | user 34 | } 35 | 36 | def updateUser(user: User) = { 37 | transaction("userdb") { 38 | UserDBSchema.users.update(user) 39 | } 40 | user 41 | } 42 | 43 | def getUser(id: Long) = { 44 | var foundUser: Option[User] = None 45 | transaction("userdb") { 46 | foundUser = UserDBSchema.users.lookup(id) 47 | } 48 | foundUser 49 | } 50 | 51 | def listUsers(): List[User] = { 52 | 53 | var users: List[User] = Nil 54 | 55 | transaction("userdb") { 56 | users = from(UserDBSchema.users)(s => where(s.id gt 0) select (s)).toList 57 | } 58 | users 59 | } 60 | 61 | def delUser(id: Long) = { 62 | var deletedCount = 0 63 | transaction("userdb") { 64 | deletedCount = UserDBSchema.users.deleteWhere(user => user.id === id) 65 | } 66 | deletedCount 67 | } 68 | 69 | def addCompany(comp: Company) = { 70 | transaction("infodb") { 71 | InfoDBSchema.company.insert(comp) 72 | } 73 | } 74 | 75 | def listCompany() = { 76 | var comps: List[Company] = Nil 77 | transaction("infodb") { 78 | comps = from(InfoDBSchema.company)(s => where(s.id gt 0) select (s)).toList 79 | } 80 | comps 81 | } 82 | 83 | def delCompany(id: Long) = { 84 | transaction("infodb") { 85 | InfoDBSchema.company.deleteWhere(comp => comp.id === id) 86 | } 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/scala/zzb/srvdemo/DemoProtocol.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvdemo 2 | 3 | import zzb.srvdemo.entites._ 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-8-21 9 | * Time: 上午10:58 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | object DemoProtocol { 13 | case class AddUser(user:User) 14 | case class UpdateUser(user:User) 15 | case class DelUser(id:Long) 16 | case class GetUser(id:Long) 17 | case class ListUser() 18 | } 19 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/scala/zzb/srvdemo/DemoService.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvdemo 2 | 3 | import com.typesafe.config.Config 4 | import zzb.service.{RestSupport, ServiceFilter, BoxedService} 5 | 6 | import akka.actor._ 7 | import zzb.srvbox.SrvManageProtocol.{FilterOk, FilterError, RestRequest} 8 | import spray.routing.Route 9 | import zzb.srvdemo.http_rest.UserHttpActor 10 | import zzb.srvdemo.akka_rest.{DemoRestActor, AkkaRestHttpApi, UserRestActor} 11 | import zzb.db.DBPools 12 | 13 | 14 | /** 15 | * Created with IntelliJ IDEA. 16 | * User: Simon Xiao 17 | * Date: 13-7-29 18 | * Time: 下午1:17 19 | * Copyright goodsl.org 2012~2020 20 | */ 21 | class DemoService(system: ActorSystem, config: Config) 22 | extends ServiceFilter(system, config) with RestSupport { 23 | 24 | override def init() { 25 | 26 | 27 | //这个只是让Demo服务每次运行都清空数据,从无开始,实际服务不要干这个事情哦。 28 | DBOperate.reCreateDb() 29 | super.init() 30 | 31 | } 32 | 33 | val httpActor = system.actorOf(Props[UserHttpActor], "h") 34 | val restActor = system.actorOf(Props[DemoRestActor], "demo") 35 | val akkaRestHttpApi = system.actorOf(Props[AkkaRestHttpApi], "r_api") 36 | 37 | override def fini() { 38 | DBPools.closeAllDB() 39 | } 40 | 41 | def name: String = "demo" 42 | 43 | def filter(req: RestRequest): Either[FilterError, FilterOk.type ] = Left(FilterError("tete")) 44 | 45 | def routes: Route = 46 | pathPrefix("r"){ctx => 47 | akkaRestHttpApi ! ctx 48 | } ~ { 49 | ctx => httpActor ! ctx 50 | } 51 | 52 | 53 | } 54 | 55 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/scala/zzb/srvdemo/SomeActor.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvdemo 2 | 3 | import akka.actor.{Props, Actor, ActorLogging} 4 | import zzb.db.DBAccess 5 | import zzb.srvbox.SrvManageProtocol._ 6 | import akka.pattern._ 7 | import scala.util.Success 8 | import akka.util.Timeout 9 | import zzb.srvbox.SrvManageProtocol.FilterReg 10 | import zzb.srvbox.SrvManageProtocol.RestRequest 11 | import scala.util.Success 12 | 13 | /** 14 | * Created with IntelliJ IDEA. 15 | * User: Simon Xiao 16 | * Date: 13-7-30 17 | * Time: 上午8:42 18 | * Copyright goodsl.org 2012~2020 19 | */ 20 | class SomeActor extends Actor with DBAccess with ActorLogging{ 21 | 22 | import zzb.srvdemo.DemoProtocol._ 23 | import zzb.srvdemo.entites._ 24 | import context.dispatcher 25 | 26 | /* val boxActor = context.system.actorSelection("/user/boxActor") 27 | implicit val timeOut=Timeout(5000) 28 | boxActor ! FilterReg("demo",List("demo")) 29 | boxActor ? FilterList onComplete{ 30 | case Success(result)=> 31 | println(result) 32 | }*/ 33 | def receive = { 34 | case ListUser => sender ! DBOperate.listUsers() 35 | case AddUser(user) => sender ! DBOperate.addUser(user) 36 | case DelUser(id) => sender ! DBOperate.delUser(id) 37 | case RestRequest(name, ctx)=>sender ! FilterError("demo") 38 | case _ => () 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/scala/zzb/srvdemo/akka_rest/AkkaRestHttpApi.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvdemo.akka_rest 2 | 3 | import spray.routing.{HttpServiceActor, Route} 4 | import zzb.rest.http2akka.Http2AkkaDirectives 5 | import akka.util.Timeout 6 | import scala.concurrent.duration._ 7 | import zzb.srvdemo.entites._ 8 | import spray.httpx.SprayJsonSupport._ 9 | 10 | 11 | /** 12 | * Created by Simon on 2014/3/23. 13 | */ 14 | class AkkaRestHttpApi extends HttpServiceActor with Http2AkkaDirectives { 15 | override def receive: Receive = runRoute(routes) 16 | 17 | implicit val timeout = Timeout(10 seconds) 18 | 19 | implicit val system = context.system 20 | 21 | def routes: Route = 22 | path("hello") { 23 | get { 24 | akkaCompleteAs[String] 25 | } 26 | } ~ path("user") { 27 | post { 28 | //新增 29 | entity(as[User]) { 30 | user ⇒ 31 | akkaWithEntityCompleteAs[User](user) 32 | } 33 | } ~ 34 | get { 35 | akkaCompleteAs[List[User]] 36 | } 37 | } ~ 38 | path("user" / IntNumber) { 39 | userId => 40 | delete { 41 | akkaCompleteAs[ChangeCount] 42 | } ~ 43 | get { 44 | akkaCompleteAs[User] 45 | } ~ 46 | put { 47 | entity(as[User]) { 48 | user => 49 | akkaWithEntityCompleteAs[User](user) 50 | } 51 | } 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/scala/zzb/srvdemo/akka_rest/DemoRestActor.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvdemo.akka_rest 2 | 3 | import zzb.rest._ 4 | import akka.actor.{Props, ActorRef} 5 | 6 | /** 7 | * Created by Simon on 2014/3/23 8 | */ 9 | class DemoRestActor extends RestServiceActor{ 10 | override def receive: Receive = runRoute(route) 11 | 12 | implicit def childByName(name:String) = { 13 | if(name == "r") 14 | Right(context.actorOf(Props[UserRestActor],"r")) 15 | else 16 | Left((StatusCodes.NotFound ,"error")) 17 | } 18 | 19 | def route:Route = forwardChild 20 | } 21 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/scala/zzb/srvdemo/akka_rest/UserRestActor.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvdemo.akka_rest 2 | 3 | import zzb.rest._ 4 | import akka.actor._ 5 | import zzb.srvdemo.entites._ 6 | import zzb.srvdemo.DBOperate 7 | import akka.util.Timeout 8 | import scala.concurrent.duration._ 9 | 10 | /** 11 | * Created with IntelliJ IDEA. 12 | * User: Simon Xiao 13 | * Date: 14-3-6 14 | * Time: 上午10:49 15 | * Copyright baoxian.com 2012~2020 16 | */ 17 | class UserRestActor extends RestServiceActor { 18 | 19 | implicit val timeout = Timeout(10 seconds) 20 | 21 | 22 | def receive: Actor.Receive = runRoute(restRoute) 23 | 24 | 25 | def restRoute: zzb.rest.Route = { 26 | import zzb.rest._ 27 | 28 | path("hello") { 29 | get { 30 | ctx ⇒ 31 | ctx.complete("ok") 32 | } 33 | } ~ 34 | path("user") { 35 | post { 36 | //新增 37 | entity(as[User]) { 38 | user ⇒ 39 | complete(DBOperate.addUser(user)) 40 | } 41 | } ~ 42 | get { 43 | complete(DBOperate.listUsers()) 44 | } 45 | } ~ 46 | path("user" / IntNumber) { 47 | userId => 48 | delete { 49 | complete(ChangeCount(DBOperate.delUser(userId))) 50 | } ~ 51 | get { 52 | DBOperate.getUser(userId) match { 53 | case Some(u) => complete(u) 54 | case None => reject 55 | } 56 | } ~ 57 | put { 58 | entity(as[User]) { 59 | user => 60 | if (userId != user.id) 61 | reject(MalformedFormFieldRejection("id", "错误的user id")) 62 | else 63 | complete(DBOperate.updateUser(user)) 64 | 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/scala/zzb/srvdemo/entites/Entites.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvdemo.entites 2 | 3 | import org.squeryl.KeyedEntity 4 | import java.sql.Timestamp 5 | import spray.json._ 6 | 7 | /** 8 | * Created with IntelliJ IDEA. 9 | * User: Simon Xiao 10 | * Date: 13-8-20 11 | * Time: 下午4:58 12 | * Copyright baoxian.com 2012~2020 13 | */ 14 | 15 | class BaseEntity extends KeyedEntity[Long] { 16 | 17 | val id:Long = 0 18 | var lastModified = new Timestamp(System.currentTimeMillis) 19 | 20 | } 21 | 22 | case class User(var email: String, var password: String) extends BaseEntity { 23 | def this() = this("", "") 24 | } 25 | 26 | case class ChangeCount(count:Int) 27 | 28 | class Company (var name: String, var address: String) extends BaseEntity { 29 | def this() = this("", "") 30 | } 31 | 32 | object ChangeCount extends DefaultJsonProtocol { 33 | implicit val format = jsonFormat1(ChangeCount.apply) 34 | } 35 | 36 | /*{"email":"user1@demo.com","password":"p1","id":1,"lastModified":"2013-09-17 17:45:41.644"}*/ 37 | object User extends DefaultJsonProtocol { 38 | implicit val userJsonFormat =new RootJsonFormat[User] { 39 | def write(c: User) = 40 | JsObject(Map("email"->JsString(c.email),"password"->JsString(c.password), "id"->JsNumber(c.id), "lastModified"->JsString(c.lastModified.toString))) 41 | 42 | def read(value: JsValue) = value match { 43 | case JsObject(fields) => 44 | new User( 45 | fields("email") match { 46 | case JsString(email)=>email 47 | case _ => deserializationError("String expected") 48 | }, 49 | fields("password") match { 50 | case JsString(password)=>password 51 | case _ => deserializationError("String expected") 52 | } 53 | ) 54 | case _ => deserializationError("User expected") 55 | } 56 | } 57 | } 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /examples/srvbox-demoService/src/main/scala/zzb/srvdemo/schema/Schemas.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvdemo.schema 2 | 3 | 4 | import org.squeryl._ 5 | import zzb.srvdemo.entites.{Company, User} 6 | 7 | object Util extends PrimitiveTypeMode 8 | import Util._ 9 | /** 10 | * Created with IntelliJ IDEA. 11 | * User: Simon Xiao 12 | * Date: 13-8-20 13 | * Time: 下午5:00 14 | * Copyright baoxian.com 2012~2020 15 | */ 16 | object 17 | UserDBSchema extends Schema { 18 | 19 | val users = table[User] 20 | 21 | on(users)(user => declare( 22 | user.id is autoIncremented //, 23 | // user.email is (unique) 24 | )) 25 | 26 | } 27 | 28 | object 29 | InfoDBSchema extends Schema { 30 | 31 | val company = table[Company] 32 | 33 | on(company)(company => declare( 34 | company.id is autoIncremented 35 | )) 36 | 37 | } -------------------------------------------------------------------------------- /project/NightlyBuildSupport.scala: -------------------------------------------------------------------------------- 1 | import java.text.SimpleDateFormat 2 | import java.util.Date 3 | import sbt._ 4 | import Keys._ 5 | import Utils._ 6 | 7 | 8 | object NightlyBuildSupport { 9 | val isNightly = sys.props.get("nightly.build") == Some("yes") 10 | 11 | def buildVersion(version: String) = 12 | if (isNightly) version.takeWhile(_ != '-') + new SimpleDateFormat("-yyyyMMdd").format(new Date) 13 | else version 14 | 15 | lazy val settings = seq( 16 | packagedArtifacts <<= (packagedArtifacts, crossTarget) map { (artifacts, dir) => 17 | // if (isNightly) { 18 | // val file = dir / "commit" 19 | // val commit = git("rev-parse", "HEAD") 20 | // IO.write(file, """%1$s""" format commit) 21 | // artifacts + (Artifact(file.getName, "html", "html") -> file) 22 | // } else artifacts 23 | artifacts 24 | } 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers += Classpaths.typesafeReleases 2 | 3 | resolvers += "sbt-idea" at "http://mpeltonen.github.com/maven/" 4 | 5 | resolvers += "Typesafe Repository" at "http://repo.akka.io/releases/" 6 | 7 | resolvers += "Spray Repository" at "http://repo.spray.io/" 8 | 9 | libraryDependencies ++= Seq( 10 | "com.decodified" % "scala-ssh" % "0.6.2", 11 | "org.bouncycastle" % "bcprov-jdk16" % "1.46", 12 | "com.jcraft" % "jzlib" % "1.1.1" 13 | ) 14 | 15 | addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.3.0") 16 | 17 | addSbtPlugin("me.lessis" % "ls-sbt" % "0.1.3") 18 | 19 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.7.4") 20 | 21 | addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0") 22 | 23 | addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.8") 24 | 25 | addSbtPlugin("io.spray" % "sbt-twirl" % "0.7.0") 26 | 27 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.7") 28 | 29 | resolvers += Resolver.url( 30 | "bintray-sbt-plugin-releases", 31 | url("http://dl.bintray.com/content/sbt/sbt-plugin-releases"))( 32 | Resolver.ivyStylePatterns) 33 | 34 | addSbtPlugin("me.lessis" % "bintray-sbt" % "0.1.2") 35 | 36 | //支持aws s3 发布的插件 37 | addSbtPlugin("com.frugalmechanic" % "fm-sbt-s3-resolver" % "0.4.0") 38 | -------------------------------------------------------------------------------- /zzb-box/src/main/resources/defaultBoxedService.conf: -------------------------------------------------------------------------------- 1 | service { 2 | init-start = 1 3 | share-actorSystem = 1 4 | start-timeout = 60 #seconds 5 | } -------------------------------------------------------------------------------- /zzb-box/src/main/resources/defaultRemoteManage.conf: -------------------------------------------------------------------------------- 1 | remoteManage{ 2 | 3 | enable = 1 4 | appname = "srvbox" 5 | 6 | 7 | username = "admin" 8 | password = "baoxian.com!@#$%" 9 | session-timeout = "10" # minute 10 | 11 | } -------------------------------------------------------------------------------- /zzb-box/src/main/resources/srv-task.conf: -------------------------------------------------------------------------------- 1 | task{ 2 | srvbox{ 3 | srv-list = zzb.srvbox.task.List 4 | srv-start = zzb.srvbox.task.Start 5 | srv-stop = zzb.srvbox.task.Stop 6 | srv-halt = zzb.srvbox.task.Halt 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /zzb-box/src/main/scala/zzb/service/BoxedService.scala: -------------------------------------------------------------------------------- 1 | package zzb.service 2 | 3 | import com.typesafe.config.Config 4 | import akka.actor.ActorSystem 5 | 6 | import scala.collection.JavaConversions._ 7 | import zzb.db.{Mongos, DBPools} 8 | import zzb.srvbox.SrvManageProtocol.ServiceReady 9 | 10 | /** 11 | * Created with IntelliJ IDEA. 12 | * User: Simon Xiao 13 | * Date: 13-8-12 14 | * Time: 上午10:47 15 | * Copyright baoxian.com 2012~2020 16 | */ 17 | abstract class BoxedService(val system: ActorSystem, val config: Config) { 18 | 19 | var sharedSystem = false 20 | var dbNames: List[String] = Nil 21 | var mongoDBNames: List[String] = Nil 22 | 23 | val serviceName = config.getString("box.service-name") 24 | 25 | val starter = system.actorSelection(s"/user/boxActor/$serviceName") 26 | 27 | final def serviceReady() = starter ! ServiceReady(serviceName) 28 | 29 | def startup(serviceName: String, sharedSystem: Boolean): Unit = { 30 | 31 | this.sharedSystem = sharedSystem 32 | 33 | if (config.hasPath("db")) 34 | initDB(config.getConfig("db")) 35 | if (config.hasPath("mongo")) 36 | initMongoDB(config.getConfig("mongo")) 37 | 38 | init() 39 | } 40 | 41 | def init() = { 42 | serviceReady() 43 | } 44 | def fini(): Unit = {} 45 | 46 | private def initDB(dbconfig: Config) = { 47 | dbNames = dbconfig.getStringList("usedb").toList 48 | dbNames.map(name ⇒ DBPools.openDB(name, dbconfig.getConfig(name))) 49 | } 50 | 51 | private def initMongoDB(dbconfig: Config) = { 52 | mongoDBNames = dbconfig.getStringList("usedb").toList 53 | mongoDBNames.map(name ⇒ Mongos.openDB(name, dbconfig.getConfig(name))) 54 | } 55 | /** 56 | * Callback run on shutdown. 57 | * Shutdown actor systems here. 58 | */ 59 | def shutdown(): Unit = { 60 | fini() 61 | dbNames.map(DBPools.closeDB) 62 | mongoDBNames.map(Mongos.closeDB) 63 | if (!sharedSystem) 64 | system.shutdown() 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /zzb-box/src/main/scala/zzb/service/RestSupport.scala: -------------------------------------------------------------------------------- 1 | package zzb.service 2 | 3 | import spray.routing._ 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-8-17 9 | * Time: 下午2:40 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | trait RestSupport extends Directives { 13 | 14 | def RestRequest(ctx: RequestContext) = routes(ctx) 15 | 16 | def routes: Route 17 | } 18 | -------------------------------------------------------------------------------- /zzb-box/src/main/scala/zzb/service/ServiceFilter.scala: -------------------------------------------------------------------------------- 1 | package zzb.service 2 | 3 | import akka.actor._ 4 | import com.typesafe.config.Config 5 | import zzb.srvbox.SrvManageProtocol._ 6 | import scala.collection.JavaConversions._ 7 | import zzb.srvbox.SrvManageProtocol.FilterOk 8 | import zzb.srvbox.SrvManageProtocol.RestRequest 9 | import akka.actor.Identify 10 | import scala.concurrent.duration.Duration 11 | import java.util.concurrent.TimeUnit 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | /** 14 | * Created by blackangel on 14-1-14 15 | */ 16 | abstract class ServiceFilter(system: ActorSystem, config: Config) extends BoxedService(system, config) { 17 | def name:String 18 | 19 | require(config.hasPath("filter.boxActor")) 20 | require(config.hasPath("filter.services")) 21 | 22 | val boxActor = system.actorSelection(config.getString("filter.boxActor")) 23 | val services=config.getStringList("filter.services").toList 24 | val actor=system.actorOf(Props(new FilterHandler(name,services,boxActor,filter)),name+"-filter") 25 | 26 | def filter(req:RestRequest):Either[FilterError,FilterOk.type ] 27 | } 28 | 29 | class FilterHandler(val filterName:String,services:List[String],val boxActor:ActorSelection,val filter:RestRequest=>Either[FilterError,FilterOk.type ]) extends Actor with ActorLogging{ 30 | var scheduler=context.system.scheduler.schedule( 31 | Duration.create(10, TimeUnit.SECONDS), Duration.create(10, TimeUnit.SECONDS), self, "reg" 32 | ) 33 | def receive={ 34 | case "reg" =>{ 35 | boxActor ! Identify(filterName) 36 | } 37 | case ActorIdentity(`filterName`, Some(ref)) =>{ 38 | scheduler.cancel() 39 | ref ! FilterReg(filterName,services) 40 | } 41 | case req @ RestRequest(name,ctx) =>{ 42 | log.debug("service {} filter handler!",name) 43 | sender ! (filter(req) match { 44 | case Right(ok) =>ok 45 | case Left(failure)=>failure 46 | }) 47 | } 48 | case FilterUnReg(name) if name==filterName =>{ 49 | scheduler=context.system.scheduler.schedule( 50 | Duration.create(10, TimeUnit.SECONDS), Duration.create(10, TimeUnit.SECONDS), self, "reg" 51 | ) 52 | } 53 | case _ => 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /zzb-box/src/main/scala/zzb/srvbox/RestInterface.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvbox 2 | 3 | import akka.actor._ 4 | import spray.routing._ 5 | import scala.collection.JavaConverters._ 6 | import java.util 7 | 8 | /** 9 | * Created with IntelliJ IDEA. 10 | * User: Simon Xiao 11 | * Date: 13-7-29 12 | * Time: 下午3:32 13 | * Copyright goodsl.org 2012~2020 14 | */ 15 | class RestInterface extends HttpServiceActor 16 | with RestApi { 17 | 18 | def receive = runRoute(routes) 19 | } 20 | 21 | trait RestApi extends HttpService with ActorLogging { 22 | actor: Actor ⇒ 23 | 24 | import zzb.srvbox.SrvManageProtocol.RestRequest 25 | 26 | val serviceManagerActor = context.actorOf(Props[ServiceManagerActor], "serviceManagerActor") 27 | 28 | //url设置 29 | private val pathMatchs: collection.mutable.Map[String, List[String]] = collection.mutable.Map.empty 30 | 31 | override def preStart { 32 | if (context.system.settings.config.hasPath("services.path")) { 33 | context.system.settings.config.getConfig("services.path").entrySet().asScala.foreach { 34 | entry => 35 | pathMatchs.getOrElseUpdate(entry.getKey, entry.getValue.unwrapped.asInstanceOf[util.ArrayList[String]].asScala.toList) 36 | } 37 | } 38 | } 39 | 40 | def routes: Route = 41 | requestUri { 42 | uri => 43 | //查找是否有路径配置 44 | val founds = for { 45 | pathMatch <- pathMatchs 46 | key = pathMatch._1 47 | urlMap <- pathMatch._2 if uri.path.toString().matches(urlMap) 48 | } yield (key, urlMap.dropRight(2).drop(1)) 49 | val (serviceName, path) = 50 | if (founds.isEmpty) 51 | (uri.path.tail.head.toString, segmentStringToPathMatcher(uri.path.tail.head.toString)) 52 | else 53 | (founds.head._1, separateOnSlashes(founds.head._2)) 54 | //去除设置地路径或者服务名 55 | pathPrefix(path) { 56 | requestContext => { 57 | serviceManagerActor ! RestRequest(serviceName, requestContext) 58 | } 59 | } 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /zzb-box/src/main/scala/zzb/srvbox/SrvManageProtocol.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvbox 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: Simon Xiao 6 | * Date: 13-7-29 7 | * Time: 下午3:36 8 | * Copyright goodsl.org 2012~2020 9 | */ 10 | object SrvManageProtocol { 11 | import spray.json._ 12 | import spray.routing.RequestContext 13 | import DefaultJsonProtocol._ 14 | 15 | case class Register(name: String, serviceClass: String, sharedActorSystem: Boolean) 16 | 17 | case class ServiceStatus(name: String, running: Boolean) 18 | 19 | case class Services(services: List[ServiceStatus]) 20 | 21 | case class RequestStart(name: String) 22 | 23 | case class RequestStop(name: String) 24 | 25 | case object ServiceNotExist 26 | 27 | case object Halt 28 | 29 | case object RequestList 30 | 31 | case class RestRequest(name: String, ctx: RequestContext) 32 | 33 | /*过滤器注册,注销*/ 34 | case class FilterReg(name:String,serverNames:List[String]) 35 | case class FilterUnReg(name:String) 36 | case object FilterList 37 | /*过滤结果返回*/ 38 | case object FilterOk 39 | case class FilterError(msg:String) 40 | 41 | object FilterError extends DefaultJsonProtocol { 42 | implicit val format = jsonFormat1(FilterError.apply) 43 | } 44 | 45 | object RequestStart extends DefaultJsonProtocol { 46 | implicit val format = jsonFormat1(RequestStart.apply) 47 | } 48 | object RequestStop extends DefaultJsonProtocol { 49 | implicit val format = jsonFormat1(RequestStop.apply) 50 | } 51 | object ServiceStatus extends DefaultJsonProtocol { 52 | implicit val format = jsonFormat2(ServiceStatus.apply) 53 | } 54 | 55 | case class ServiceReady(serviceName:String) 56 | } 57 | -------------------------------------------------------------------------------- /zzb-box/src/test/resources/S1.conf: -------------------------------------------------------------------------------- 1 | 2 | tag = "in sub config file" 3 | 4 | -------------------------------------------------------------------------------- /zzb-box/src/test/resources/S2.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepover/zzb/0a7fc2518fd3f34b66e22f81c045b54360971657/zzb-box/src/test/resources/S2.conf -------------------------------------------------------------------------------- /zzb-box/src/test/resources/remoteManage.conf: -------------------------------------------------------------------------------- 1 | remoteManage{ 2 | 3 | enable = 1 4 | outfile = "shellout.txt" 5 | errfile = "shellerr.txt" 6 | 7 | daemon{ 8 | username = "admin" 9 | password = "baoxian" 10 | session-timeout = "10" # minute 11 | } 12 | 13 | 14 | } -------------------------------------------------------------------------------- /zzb-box/src/test/resources/srvbox.conf: -------------------------------------------------------------------------------- 1 | myapp { 2 | name = foo 3 | } 4 | 5 | services { 6 | names = ["S1", "S2"] 7 | 8 | 9 | S1 { 10 | serviceClass = zzb.srvbox.S1Service 11 | 12 | tag = "in main config file" 13 | } 14 | S2 { 15 | depend-on = ["S1"] 16 | serviceClass = zzb.srvbox.S2Service 17 | init-start = 1 18 | share-actorSystem = 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /zzb-box/src/test/resources/testConfigs/namesNotList.conf: -------------------------------------------------------------------------------- 1 | myapp { 2 | name = bar 3 | } 4 | 5 | services{ 6 | names=hello 7 | 8 | S1{ 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /zzb-box/src/test/resources/testConfigs/noExistSubServiceClassName.conf: -------------------------------------------------------------------------------- 1 | myapp { 2 | name = bar 3 | } 4 | 5 | services{ 6 | names = [ "S1","S2"] 7 | 8 | S1{ 9 | serviceClass = zzb.srvbox.S1Service 10 | } 11 | S2{ 12 | serviceClass = zzb.srvbox.S1ServiceNoExist 13 | init-start = 0 14 | share-actorSystem = 0 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /zzb-box/src/test/resources/testConfigs/noNames.conf: -------------------------------------------------------------------------------- 1 | myapp { 2 | name = bar 3 | } 4 | 5 | services{ 6 | 7 | 8 | S1{ 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /zzb-box/src/test/resources/testConfigs/noServiceClass.conf: -------------------------------------------------------------------------------- 1 | myapp { 2 | name = bar 3 | } 4 | 5 | services{ 6 | names = [ "S1" ] 7 | 8 | S1{ 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /zzb-box/src/test/resources/testConfigs/noServices.conf: -------------------------------------------------------------------------------- 1 | myapp { 2 | name = bar 3 | } 4 | 5 | # no set services , BoxApp throw exception -------------------------------------------------------------------------------- /zzb-box/src/test/resources/testConfigs/noSubServiceConfigFile.conf: -------------------------------------------------------------------------------- 1 | myapp { 2 | name = bar 3 | } 4 | 5 | services{ 6 | names = [ "S1","S2","S3" ] 7 | 8 | S1{ 9 | serviceClass = zzb.srvbox.S1Service 10 | } 11 | S2{ 12 | serviceClass = zzb.srvbox.S1Service 13 | init-start = 0 14 | share-actorSystem = 0 15 | } 16 | S3{ 17 | serviceClass = zzb.srvbox.S1Service 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /zzb-box/src/test/resources/testConfigs/spec.conf: -------------------------------------------------------------------------------- 1 | myapp { 2 | name = bar 3 | } 4 | 5 | services{ 6 | names = [ "S1","S2" ] 7 | 8 | S1{ 9 | serviceClass = zzb.srvbox.S1Service 10 | } 11 | S2{ 12 | depend-on = ["S1"] 13 | serviceClass = zzb.srvbox.S1Service 14 | init-start = 0 15 | share-actorSystem = 0 16 | 17 | start-timeout = 120 #secconds 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /zzb-box/src/test/scala/zzb/StopSystemAfterAll.scala: -------------------------------------------------------------------------------- 1 | package zzb 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: Simon Xiao 6 | * Date: 13-7-26 7 | * Time: 上午9:53 8 | * Copyright goodsl.org 2012~2020 9 | */ 10 | import org.scalatest.{ Suite, BeforeAndAfterAll } 11 | import akka.testkit.TestKit 12 | 13 | trait StopSystemAfterAll extends BeforeAndAfterAll { 14 | this: TestKit with Suite ⇒ 15 | override protected def afterAll() { 16 | super.afterAll() 17 | system.shutdown() 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /zzb-box/src/test/scala/zzb/srvbox/BoxBuilderTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvbox 2 | 3 | import akka.testkit.{ImplicitSender, TestKit} 4 | import akka.actor.ActorSystem 5 | import org.scalatest.WordSpecLike 6 | import org.scalatest.MustMatchers 7 | import zzb.StopSystemAfterAll 8 | import zzb.srvbox.SrvManageProtocol.ServiceStatus 9 | import akka.util.Timeout 10 | import scala.concurrent.duration._ 11 | import spray.util._ 12 | 13 | /** 14 | * Created by Simon on 2014/5/28 15 | */ 16 | class BoxBuilderTest extends TestKit(ActorSystem("testSystem")) 17 | with WordSpecLike 18 | with MustMatchers 19 | with ImplicitSender 20 | with StopSystemAfterAll { 21 | 22 | val (config, servicesOpts) = BoxBuilder.getSelectService(List("srvbox")) 23 | 24 | val boxBuilder = new BoxBuilder(system,config) 25 | 26 | 27 | implicit val timeout = Timeout(10 seconds) 28 | 29 | val startRes = boxBuilder.startBoxedServices(servicesOpts) 30 | 31 | Thread.sleep(1000) 32 | 33 | "BoxBuilder" must { 34 | "service started" in { 35 | 36 | val res = startRes.await 37 | res.length mustBe 2 38 | res(0).name mustBe "S1" 39 | res(0).running mustBe true 40 | 41 | res(1).name mustBe "S2" 42 | res(1).running mustBe true 43 | 44 | val s1 = system.actorSelection("/user/boxActor/S1") 45 | val s2 = system.actorSelection("/user/boxActor/S2") 46 | 47 | 48 | s1 ! "query" 49 | expectMsgPF() { 50 | case ServiceStatus(name, running) ⇒ 51 | name mustBe "S1" 52 | running mustBe true 53 | } 54 | 55 | s2 ! "query" 56 | expectMsgPF() { 57 | case ServiceStatus(name, running) ⇒ 58 | name mustBe "S2" 59 | running mustBe true 60 | } 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /zzb-box/src/test/scala/zzb/srvbox/FooService.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvbox 2 | 3 | import zzb.service.BoxedService 4 | import com.typesafe.config.Config 5 | import akka.actor.ActorSystem 6 | 7 | /** 8 | * Created with IntelliJ IDEA. 9 | * User: Simon Xiao 10 | * Date: 13-8-13 11 | * Time: 下午4:27 12 | * Copyright baoxian.com 2012~2020 13 | */ 14 | class S1Service(system: ActorSystem, config: Config) extends BoxedService(system, config) { 15 | 16 | override def init() {super.init()} 17 | } 18 | 19 | class S2Service(system: ActorSystem, config: Config) extends BoxedService(system, config) { 20 | 21 | override def init() {super.init()} 22 | 23 | } 24 | 25 | class S3Service(system: ActorSystem, config: Config) extends BoxedService(system, config) { 26 | 27 | } 28 | 29 | class S4Service(system: ActorSystem, config: Config) extends BoxedService(system, config) { 30 | 31 | } 32 | 33 | class S5Service(system: ActorSystem, config: Config) extends BoxedService(system, config) { 34 | 35 | override def init() {super.init()} 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /zzb-box/src/test/scala/zzb/srvbox/RemoteManageTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvbox 2 | 3 | import org.scalatest.WordSpecLike 4 | import org.scalatest.MustMatchers 5 | import zzb.shell.Task 6 | import akka.testkit.{ImplicitSender, TestKit} 7 | import akka.actor.ActorSystem 8 | import zzb.StopSystemAfterAll 9 | import com.typesafe.config.ConfigFactory 10 | import zzb.shell.remote.ShellProtocol._ 11 | 12 | /** 13 | * Created with IntelliJ IDEA. 14 | * User: Simon Xiao 15 | * Date: 13-10-16 16 | * Time: 上午11:11 17 | * Copyright baoxian.com 2012~2020 18 | */ 19 | class RemoteManageTest extends TestKit(ActorSystem("testSystem")) 20 | with WordSpecLike 21 | with MustMatchers 22 | with ImplicitSender 23 | with StopSystemAfterAll { 24 | 25 | "remote manage " must { 26 | val config = ConfigFactory.load("remoteManage") 27 | val shellActor = BoxApp.startRemoteManage(config,system) 28 | 29 | "can login from remote" in { 30 | 31 | 32 | shellActor ! Login("not_me", "wrongPass",self) 33 | 34 | expectMsgPF() { 35 | case LoginFailed ⇒ () 36 | } 37 | 38 | shellActor ! Login("admin", "wrongPass",self) 39 | expectMsgPF() { 40 | case LoginFailed ⇒ () 41 | } 42 | 43 | shellActor ! Login("admin", "baoxian",self) 44 | expectMsgPF() { 45 | case LoginSuccess(sid,peer) ⇒ 46 | sid.length must equal(36) 47 | } 48 | } 49 | "can exec task from remote" in { 50 | 51 | // shellActor ! Command("no_this_session", "tasks") 52 | // expectMsgPF() { 53 | // case Timeout(sid) => () 54 | // } 55 | // shellActor ! Login("admin", "baoxian") 56 | // val sessionid = expectMsgPF() { 57 | // case LoginSuccess(sid) ⇒ 58 | // sid.length must equal(36) 59 | // sid 60 | // } 61 | // shellActor ! Command(sessionid, "tasks") 62 | // expectMsgPF() { 63 | // case CommandResult(out,err,stat) ⇒ 64 | // assert(out.contains("tasks")) 65 | // } 66 | // Thread.sleep(20 * 1000) 67 | // shellActor ! Command("no_this_session", "tasks") 68 | // expectMsgPF() { 69 | // case Timeout(sid) => () 70 | // } 71 | } 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /zzb-box/src/test/scala/zzb/srvbox/ServiceActorTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.srvbox 2 | 3 | import akka.testkit.{ ImplicitSender, TestActorRef, TestKit } 4 | import akka.actor.{ Props, ActorSystem } 5 | import org.scalatest.{ Ignore, WordSpecLike } 6 | import org.scalatest.MustMatchers 7 | import zzb.StopSystemAfterAll 8 | 9 | /** 10 | * Created with IntelliJ IDEA. 11 | * User: Simon Xiao 12 | * Date: 13-8-13 13 | * Time: 下午2:52 14 | * Copyright baoxian.com 2012~2020 15 | */ 16 | class ServiceActorTest extends TestKit(ActorSystem("testSystem")) 17 | with WordSpecLike 18 | with MustMatchers 19 | with ImplicitSender 20 | with StopSystemAfterAll { 21 | 22 | import zzb.srvbox.SrvManageProtocol._ 23 | 24 | "A Service Actor" must { 25 | 26 | val boxActor = system.actorOf(Props[BoxActor], "boxActor") 27 | 28 | boxActor ! Register("S1", "zzb.srvbox.S1Service", sharedActorSystem = true) 29 | 30 | val serviceActor = system.actorSelection("/user/boxActor/S1") 31 | 32 | "keep service name and class name when got Register message" in { 33 | 34 | serviceActor ! Register("S1", "zzb.srvbox.S1Service", sharedActorSystem = true) 35 | serviceActor ! "query" 36 | expectMsgPF() { 37 | case ServiceStatus(name, running) ⇒ 38 | name must be("S1") 39 | running mustBe false 40 | } 41 | } 42 | "start service after got the 'start' command" in { 43 | serviceActor ! "start" 44 | 45 | expectMsgPF() { 46 | case ServiceStatus(name, running) ⇒ 47 | name must be("S1") 48 | running mustBe true 49 | } 50 | } 51 | "stop service after got the 'stop' command" in { 52 | serviceActor ! "stop" 53 | 54 | expectMsgPF() { 55 | case ServiceStatus(name, running) ⇒ 56 | name must be("S1") 57 | running mustBe false 58 | } 59 | } 60 | 61 | } 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /zzb-config/src/main/scala/zzb/config/EnvConfigLoader.scala: -------------------------------------------------------------------------------- 1 | package zzb.config 2 | 3 | import com.typesafe.config.{Config, ConfigResolveOptions, ConfigParseOptions, ConfigFactory} 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-9-1 9 | * Time: 上午3:44 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | trait EnvConfigLoader { 13 | 14 | def loadConfig(configName: String,withSysConfig :Boolean = true): Option[Config] = { 15 | 16 | //获取运行环境的名称 17 | //先从 JVM 启动参数中查找名为"BOX_ENV"的配置项目, 18 | //如果未找到就查找系统环境变量 "BOX_ENV" 19 | //如果 JVM 启动参数明确设置 ”BOX_ENV“为空串,就认为没有设置,不再查找系统参数 20 | val envName = scala.util.Properties.propOrElse("BOX_ENV", 21 | scala.util.Properties.envOrElse("BOX_ENV", "")) 22 | 23 | innerLoadConfig(configName, envName.trim,withSysConfig) 24 | } 25 | 26 | private def innerLoadConfig(configName: String, envName: String,withSysConfig :Boolean): Option[Config] = { 27 | val envConfigName = 28 | if (configName.endsWith(".conf")) 29 | s"env/$envName/$configName" 30 | else 31 | s"env/$envName/$configName.conf" 32 | 33 | //不允許配置文件不存在 34 | val parseOpt = ConfigParseOptions.defaults().setAllowMissing(false) 35 | 36 | try { 37 | //通用配置文件不允許不存在 38 | val generalConfig = 39 | if(withSysConfig) 40 | ConfigFactory.load(configName, parseOpt, ConfigResolveOptions.defaults) 41 | else ConfigFactory.parseResourcesAnySyntax(configName,parseOpt) 42 | 43 | if (envName.length > 0) { //环境专有配置可以不存在 44 | val envConfig = ConfigFactory.parseResourcesAnySyntax(envConfigName,ConfigParseOptions.defaults) 45 | Some(envConfig.withFallback(generalConfig)) 46 | } 47 | else 48 | Some(generalConfig) 49 | } catch { 50 | case ex: Throwable ⇒ 51 | println(ex);None 52 | } 53 | } 54 | } 55 | 56 | object EnvConfigLoader extends EnvConfigLoader 57 | -------------------------------------------------------------------------------- /zzb-config/src/test/resources/SomeConfig.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | stdout-loglevel = DEBUG 4 | event-handlers = ["akka.event.slf4j.Slf4jLogger"] 5 | jvm-exit-on-fatal-error = on 6 | 7 | actor { 8 | provider = "akka.remote.RemoteActorRefProvider" 9 | } 10 | 11 | remote { 12 | enabled-transports = ["akka.remote.netty.tcp"] 13 | netty.tcp { 14 | hostname = "127.0.0.1" 15 | port = 2590 16 | } 17 | } 18 | } 19 | 20 | db { 21 | 22 | usedb= ["firstdb"] 23 | 24 | firstdb { 25 | 26 | driver = "org.h2.Driver" 27 | url = "jdbc:h2:~/firstdb" 28 | user = "sa" 29 | password = "" 30 | pool{ 31 | min = 2 32 | max = 10 33 | inc = 1 34 | } 35 | } 36 | } 37 | 38 | envIsEmpty { 39 | hello = "simon" 40 | } -------------------------------------------------------------------------------- /zzb-config/src/test/resources/env/com/SomeConfig.conf: -------------------------------------------------------------------------------- 1 | db { 2 | 3 | usedb= ["firstdb"] 4 | 5 | firstdb { 6 | user = "dbuser" 7 | password = "dbpassword" 8 | } 9 | } -------------------------------------------------------------------------------- /zzb-config/src/test/resources/env/dev/SomeConfig.conf: -------------------------------------------------------------------------------- 1 | db { 2 | 3 | usedb= ["firstdb"] 4 | 5 | firstdb { 6 | user = "dbuser" 7 | password = "dbpassword" 8 | } 9 | } 10 | 11 | envIsEmpty { 12 | hello = "simon" 13 | } -------------------------------------------------------------------------------- /zzb-config/src/test/resources/env/net/SomeConfig.conf: -------------------------------------------------------------------------------- 1 | db { 2 | 3 | usedb= ["firstdb"] 4 | 5 | firstdb { 6 | user = "dbuser" 7 | password = "dbpassword" 8 | } 9 | } -------------------------------------------------------------------------------- /zzb-config/src/test/resources/env/org/SomeConfig.conf: -------------------------------------------------------------------------------- 1 | db { 2 | 3 | usedb= ["firstdb"] 4 | 5 | firstdb { 6 | user = "dbuser" 7 | password = "dbpassword" 8 | } 9 | } -------------------------------------------------------------------------------- /zzb-config/src/test/resources/env/uat/SomeConfig.conf: -------------------------------------------------------------------------------- 1 | db { 2 | 3 | usedb= ["firstdb"] 4 | 5 | firstdb { 6 | user = "dbuser" 7 | password = "dbpassword" 8 | } 9 | } -------------------------------------------------------------------------------- /zzb-config/src/test/resources/noEnvConfig.conf: -------------------------------------------------------------------------------- 1 | hello { 2 | 3 | name = simon 4 | } -------------------------------------------------------------------------------- /zzb-config/src/test/scala/zzb/config/EnvConfigTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.config 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-9-2 10 | * Time: 上午11:21 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | class EnvConfigTest extends WordSpec with MustMatchers with EnvConfigLoader { 14 | 15 | "env config" must { 16 | "override general config" in { 17 | System.setProperty("BOX_ENV","dev") 18 | val config = loadConfig("SomeConfig") 19 | config.get.getString("db.firstdb.user") must equal("dbuser") 20 | config.get.getInt("db.firstdb.pool.min") must equal(2) 21 | 22 | config.get.getInt("akka.remote.netty.tcp.port") must equal(2590) 23 | config.get.getString("akka.remote.netty.tcp.hostname") must equal("127.0.0.1") 24 | } 25 | 26 | "check config file no exist" in { 27 | val config = loadConfig("noThisFile") 28 | config must equal(None) 29 | } 30 | 31 | "work well when provided general config but no env config" in { 32 | val config = loadConfig("noEnvConfig") 33 | config.get.getString("hello.name") must equal("simon") 34 | } 35 | 36 | "general config has value,but env config empty block" in { 37 | 38 | val config = loadConfig("SomeConfig") 39 | 40 | config.get.getString("envIsEmpty.hello") must equal("simon") 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /zzb-datatype/src/main/scala/zzb/datatype/TFixKeysMap.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | import scala.language.implicitConversions 3 | 4 | /** 5 | * Created with IntelliJ IDEA. 6 | * User: Simon Xiao 7 | * Date: 13-11-25 8 | * Time: 下午1:12 9 | * Copyright baoxian.com 2012~2020 10 | */ 11 | 12 | trait TFixKeysMap[K, V] extends TMap[K, V] { 13 | 14 | val fixKeys: Set[K] 15 | 16 | override val itemFilter : ItemFilter = kv => fixKeys.contains(kv._1) 17 | 18 | implicit def packWrap(p:Pack) = new PackWrap(p) 19 | 20 | class PackWrap(p:Pack){ 21 | def availableKeys = fixKeys 22 | def allowKeys(keyFilter: K => Boolean) = fixKeys.filter(keyFilter) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /zzb-datatype/src/main/scala/zzb/datatype/Versioned.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import scala.reflect._ 4 | import spray.json.JsonFormat 5 | 6 | /** 7 | * Created by Simon on 2014/3/28 8 | */ 9 | 10 | trait Versioned { this : TStruct => 11 | 12 | /** 内置的 版本信息 字段 */ 13 | val verInfo = FieldStruct(VersionInfo, isRequired = true, VersionInfo()) 14 | } 15 | 16 | object VersionInfo extends TStruct{ 17 | val t_memo_ : String = "VersionInfo" 18 | override lazy val t_code_ = "verInfo" 19 | 20 | 21 | val ver = FieldInt(Ver, isRequired = true, default = 0) 22 | val time = Field(SaveTime) 23 | val opt = Field(Operator) 24 | val isOwn = Field(IsOwnerOperate) 25 | val tag = FieldString(Tag,isRequired = true, default = "") 26 | val eqtag = FieldString(EqTag,isRequired = true, default = "") 27 | } 28 | object VersionInfos extends TPackList[VersionInfo.Pack]{ 29 | override val t_memo_ : String = "VersionList" 30 | override implicit val elementFormat: JsonFormat[VersionInfo.Pack] = VersionInfo.Format 31 | override val lm = classTag[VersionInfo.Pack] 32 | 33 | override def itemDataType: DataType[Any] = VersionInfo 34 | } 35 | 36 | 37 | object Ver extends TInt { 38 | val t_memo_ : String = "version" 39 | override lazy val t_code_ = "ver" 40 | } 41 | 42 | object Tag extends TString { 43 | val t_memo_ : String = "tag" 44 | override lazy val t_code_ = "tag" 45 | } 46 | 47 | object EqTag extends TString { 48 | val t_memo_ : String = "eqtag" 49 | override lazy val t_code_ = "eqtag" 50 | } 51 | 52 | object SaveTime extends TDateTime{ 53 | val t_memo_ : String = "saveTime" 54 | override lazy val t_code_ = "time" 55 | } 56 | 57 | object Operator extends TString{ 58 | val t_memo_ : String = "operator" 59 | override lazy val t_code_ = "opt" 60 | } 61 | 62 | object IsOwnerOperate extends TBoolean{ 63 | val t_memo_ : String = "owner" 64 | override lazy val t_code_ = "isOwn" 65 | } 66 | 67 | 68 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/AnyToPackTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | 6 | /** 7 | * Created by Simon on 2014/4/10 8 | */ 9 | class AnyToPackTest extends WordSpec with MustMatchers { 10 | 11 | import testuse._ 12 | 13 | import UserInfo._ 14 | 15 | "AnyToPack " must { 16 | val np1 = UserName("Simon") 17 | val userInfo = UserInfo(userName := "Simon", memo := "hello") 18 | "支持TMonoType的值直接转换为对应的Pack" in { 19 | val np2 = UserName.AnyToPack("Simon") 20 | np2.get mustBe np1 21 | 22 | val np4 = UserName.AnyToPack(123) 23 | np4 mustBe None 24 | } 25 | 26 | "支持 Pack to Pack " in { 27 | val np3 = UserName.AnyToPack(np1) 28 | np1 mustBe np3.get 29 | 30 | userInfo.field(userName()) mustBe Some(np1) 31 | userName().AnyToPack(np1) mustBe Some(np1) 32 | 33 | val mm = userInfo.field(memo()) 34 | memo().AnyToPack(mm.get) mustBe mm 35 | } 36 | 37 | "如果两个TMonoType 的值类型是同样的,允许互相转换" in { 38 | memo().AnyToPack(TString("hello")) mustBe userInfo.field(memo()) 39 | } 40 | 41 | "支持枚举类型从数字或名称转换成Pack" in { 42 | BloodType.AnyToPack(1) mustBe Some(BloodType.int2EnumPack(1)) 43 | BloodType.AnyToPack(4) mustBe Some(BloodType.int2EnumPack(4)) 44 | BloodType.AnyToPack(5) mustBe None 45 | BloodType.AnyToPack("A") mustBe Some(BloodType.int2EnumPack(1)) 46 | BloodType.AnyToPack("G") mustBe None 47 | } 48 | "支持列表类型从列表数据转换成Pack" in { 49 | import UserInfo._ 50 | val users = List( 51 | UserInfo(userName := "Simon", userAge := 39), 52 | UserInfo(userName := "jack", userAge := 9) 53 | ) 54 | val usersPack = Users(users) 55 | 56 | Users.AnyToPack(usersPack) mustBe Some(usersPack) 57 | 58 | Users.AnyToPack(users) mustBe Some(usersPack) 59 | 60 | Users.AnyToPack(List(1,2,3)) mustBe None 61 | Users.AnyToPack(List()) mustBe Some(Users(List())) 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/DateTimeParseTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.scalatest.{MustMatchers, WordSpec} 4 | import spray.json.JsonParser 5 | 6 | /** 7 | * Created by Simon on 2015/4/14 8 | */ 9 | class DateTimeParseTest extends WordSpec with MustMatchers { 10 | 11 | "DateTimeType" must { 12 | " implicitly support multi-format " in { 13 | 14 | val json1 = "\"1999-11-22 20:22:13.001\"" 15 | 16 | val fromJson1 = TDateTime.fromJsValue(JsonParser(json1)) 17 | val d = 0 18 | 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/FuncTypeTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | import spray.json.JsonFormat 6 | 7 | /** 8 | * Created with IntelliJ IDEA. 9 | * User: Simon Xiao 10 | * Date: 13-11-23 11 | * Time: 下午4:05 12 | * Copyright baoxian.com 2012~2020 13 | */ 14 | class FuncTypeTest extends WordSpec with MustMatchers { 15 | 16 | 17 | "Func0Type" must { 18 | def hello: String = "hello world" 19 | 20 | object StrFunc extends TFunc0[String] { 21 | override def parse(str: String) = () => str 22 | 23 | val t_memo_ : String = "helloFunc" 24 | implicit val valueFormat: JsonFormat[() => String] = null 25 | } 26 | 27 | "init and exe ok" in { 28 | val fPack1 = StrFunc(hello _) 29 | fPack1.value() must equal("hello world") 30 | 31 | val fPack2 :StrFunc.Pack = hello _ 32 | fPack2.value() must equal("hello world") 33 | } 34 | } 35 | 36 | "Func1Type" must { 37 | 38 | def plusN(step:Int)(in :Int) = in + step 39 | 40 | object PlusNFunc extends TFunc1[Int,Int]{ 41 | val t_memo_ : String = "plusN" 42 | override def parse(str: String) = plusN(str.toInt) _ 43 | 44 | implicit val valueFormat: JsonFormat[(Int) => Int] = null 45 | } 46 | 47 | "init and exe ok" in { 48 | 49 | val fPack1 = PlusNFunc(plusN(3) _) 50 | fPack1.value(1) must equal(4) 51 | 52 | val fPack2 :PlusNFunc.Pack = plusN(3) _ 53 | fPack2.value(1) must equal(4) 54 | 55 | val fPack3 = PlusNFunc.parse("5") 56 | fPack3.value(5) must equal(10) 57 | } 58 | } 59 | 60 | "Func2Type" must { 61 | 62 | def plusN(ext:Int)(in1 :Int,in2:Int) = ext + in1 + in2 63 | 64 | object PlusNFunc extends TFunc2[Int,Int,Int]{ 65 | val t_memo_ : String = "plusN" 66 | override def parse(str: String) = plusN(str.toInt) _ 67 | 68 | implicit val valueFormat: JsonFormat[(Int, Int) => Int] = null 69 | } 70 | 71 | "init and exe ok" in { 72 | 73 | val fPack1 = PlusNFunc(plusN(3) _) 74 | fPack1.value(1,1) must equal(5) 75 | 76 | val fPack2 :PlusNFunc.Pack = plusN(3) _ 77 | fPack2.value(1,1) must equal(5) 78 | 79 | val fPack3 = PlusNFunc.parse("5") 80 | fPack3.value(5,1) must equal(11) 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/JsonSprayTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | import spray.routing._ 6 | import zzb.datatype.testuse.HomeInfo 7 | import spray.httpx.SprayJsonSupport._ 8 | 9 | /** 10 | * Created by Simon on 2014/4/4 11 | */ 12 | class JsonSprayTest extends WordSpec with MustMatchers { 13 | 14 | } 15 | 16 | 17 | trait HomeInfoHttpApi extends Directives { 18 | 19 | def route1 : Route = 20 | pathEndOrSingleSlash { 21 | entity(as[TString.Pack]) { 22 | owner => 23 | complete(owner) 24 | } 25 | } 26 | 27 | def route2 : Route = 28 | pathEndOrSingleSlash { 29 | entity(as[TStruct.Pack]) { 30 | owner => 31 | complete(owner) 32 | } 33 | } 34 | def route3 : Route = 35 | pathEndOrSingleSlash { 36 | entity(as[HomeInfo.Pack]) { 37 | owner => 38 | complete(owner) 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/MapTypeTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import java.io._ 4 | 5 | import org.scalatest.{MustMatchers, WordSpec} 6 | 7 | 8 | /** 9 | * Created by Simon on 2014/4/29 10 | */ 11 | class MapTypeTest extends WordSpec with MustMatchers { 12 | 13 | import zzb.datatype.BasicFormats._ 14 | 15 | val Colors = TMap[String, Int]("colors", "colors") 16 | 17 | object House extends TStruct { 18 | val colors = Field(Colors) 19 | override val t_memo_ : String = "多彩房屋" 20 | } 21 | 22 | def serializeTest(o: Serializable) = { 23 | val bs = new ByteArrayOutputStream() 24 | val out = new ObjectOutputStream(bs) 25 | out.writeObject(o) 26 | out.close() 27 | 28 | val oin = new ObjectInputStream(new ByteArrayInputStream(bs.toByteArray)) 29 | val t = oin.readObject() 30 | oin.close() 31 | 32 | println("序列化前的对象----" + o) 33 | println("反序列化的对象----" + t) 34 | 35 | t mustBe o 36 | } 37 | 38 | 39 | "TMap" must { 40 | 41 | "支持覆盖操作" in { 42 | val c1 = Colors(Map("white" -> 1, "red" -> 2, "black" -> 5)) 43 | val c2 = Colors(Map("white" -> 1, "red" -> 3, "blue" -> 4)) 44 | 45 | serializeTest(c1) 46 | 47 | val c1to2 = c1 ->> c2 48 | val c2to1 = c2 ->> c1 49 | 50 | c1to2.size mustBe 4 51 | c2to1.size mustBe 4 52 | 53 | c1to2("red").get mustBe 2 54 | c2to1("red").get mustBe 3 55 | c1to2("black").get mustBe c2to1("black").get 56 | } 57 | 58 | "集合字段赋值" in { 59 | import House._ 60 | val h0 = House(colors := Map("White" -> 1, "Red" -> 2)) 61 | 62 | val cc = h0(colors).get.value 63 | cc("White") mustBe 1 64 | 65 | intercept[IllegalArgumentException] { 66 | House(colors := Map("White" -> "1", "Red" -> "2")) 67 | 68 | } 69 | } 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/MetaInfoTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | import testuse._ 6 | import zzb.datatype.meta.{EnumTypeInfo, TypeInfo} 7 | 8 | /** 9 | * Created by Simon on 2014/4/17 10 | */ 11 | class MetaInfoTest extends WordSpec with MustMatchers { 12 | 13 | "基本类型元信息" in { 14 | 15 | val m0 = BloodType.typeInfo 16 | val m1 = UserInfo.birthDay().typeInfo 17 | val mm = UserInfo.typeInfo 18 | UserName.typeInfo.valueCode mustBe "String" 19 | UserAge.typeInfo.valueCode mustBe "int" 20 | UserInfo.birthDay().typeInfo.valueCode mustBe "DateTime" 21 | UserInfo.male().typeInfo.valueCode mustBe "boolean" 22 | 23 | val j0 = TypeInfo.format.write(m1) 24 | 25 | println(j0) 26 | 27 | val j1 = TypeInfo.format.read(j0) 28 | 29 | j1.valueCode mustBe m1.valueCode 30 | 31 | val bt = UserInfo.blood().typeInfo 32 | 33 | 34 | val n = UserInfo.getClass.getName 35 | mm.valueCode mustBe UserInfo.structName 36 | mm.simpleCode mustBe "UserInfo" 37 | 38 | val userInfoJson = TypeInfo.format.write(UserInfo.typeInfo) 39 | 40 | println("--->1" + userInfoJson) 41 | 42 | val tInfo = TypeInfo.format.read(userInfoJson) 43 | 44 | println("--->2" + TypeInfo.format.write(tInfo)) 45 | 46 | //TypeInfo.format.write(tInfo).toString() mustBe userInfoJson.toString() 47 | 48 | //TypeInfo.format.read(userInfoJson) mustBe UserInfo.typeInfo 49 | 50 | val jm0=EnumTypeInfo.format.write(m0) 51 | //EnumTypeInfo.format.read(jm0) mustBe m0 52 | 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/StructTransTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.scalatest.{MustMatchers, WordSpec} 4 | 5 | /** 6 | * Created by Simon on 2015/4/1 7 | */ 8 | class StructTransTest extends WordSpec with MustMatchers { 9 | 10 | "TStructType" must { 11 | "类型转换,两个类型共有字段同步复制" in { 12 | import zzb.datatype._ 13 | 14 | val Height = TFloat("height","高度") 15 | val Weight = TFloat("weight","重量") 16 | val Years = TInt("years","年龄") 17 | val Name = TString("name","名称") 18 | 19 | object Color extends Enumeration with TEnum { 20 | val t_memo_ = "颜色" 21 | val W = Value(1, "白色") 22 | val B = Value(2, "黑色") 23 | val Y = Value(3, "黄色") 24 | } 25 | 26 | val dd: Color.Value = Color.W 27 | 28 | object Man extends TStruct{ 29 | override val t_memo_ : String = "人类" 30 | 31 | val height = Field(Height) 32 | val weight = Field(Weight) 33 | val name = Field(Name) 34 | val years = Field(Years) 35 | val address = Field(TString("address","地址")) 36 | val color = Field(Color) 37 | 38 | val memo = Field(TString("memo","备注")) 39 | } 40 | 41 | object Car extends TStruct{ 42 | override val t_memo_ : String = "人类" 43 | 44 | val height = Field(Height) 45 | val weight = Field(Weight) 46 | val name = Field(Name) 47 | val brand = Field(TString("brand","品牌")) 48 | val color = Field(Color) 49 | val memo = Field(TString("memo","备注")) 50 | } 51 | 52 | val m1: Man.Pack = Man(Man.years := 40, Man.height := 1.75f,Man.name := "Simon",Man.memo := "欢天喜地" ) <~~ List(Some(Color.W)) 53 | 54 | val c1 = m1.to(Car) 55 | 56 | c1(Car.height()).get.value mustBe 1.75f 57 | c1(Car.name()).get.value mustBe "Simon" 58 | c1(Years) mustBe None 59 | c1(Car.brand()) mustBe None 60 | c1(Car.color()).get.value.idx mustBe 1 61 | 62 | //同名字段只要基础类型一样也会被复制 63 | c1(Car.memo()).get.value mustBe "欢天喜地" 64 | c1(Car.memo()).get.dataType mustBe Car.memo() 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/StructXorTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.scalatest.{MustMatchers, WordSpec} 4 | 5 | /** 6 | * Created by Simon on 2014/6/19 7 | */ 8 | class StructXorTest extends WordSpec with MustMatchers { 9 | 10 | import testuse.path._ 11 | import User._ 12 | 13 | val u1 = User( 14 | name := "Simon", 15 | age := 39, 16 | height := 1.75F, 17 | blood := BloodType.AB, 18 | male := true 19 | ) <~ Car( 20 | Car.license := "京GNR110", 21 | Car.vin := "123456789" 22 | ) 23 | 24 | val u2 = User( 25 | name := "Simon", 26 | age := 40, 27 | height := 1.75F, 28 | blood := BloodType.AB, 29 | male := true 30 | ) <~ Car( 31 | Car.license := "京GNR110", 32 | Car.vin := "abcefg" 33 | ) 34 | 35 | "异或操作" must { 36 | "保留差异字段,剔除相同字段,保留必填字段" in { 37 | 38 | val r1 = u1 xor u2 39 | r1(name).get.value mustBe "Simon" //必填项即使相等也保留 40 | r1(age).get.value mustBe 39 //不同项目保留左边的 41 | r1(height) mustBe None // 相同项目被剔除 42 | r1(car().vin()).get.value mustBe "123456789" //异或操作可以对 TStruct 类型的字段嵌套生效 43 | r1(car().license()) mustBe None 44 | 45 | val r2 = u2 xor u1 46 | r2(name).get.value mustBe "Simon" //必填项即使相等也保留 47 | r2(age).get.value mustBe 40 //不同项目保留左边的 48 | r2(height) mustBe None // 相同项目被剔除 49 | 50 | r2(car().vin()).get.value mustBe "abcefg" 51 | r2(car().license()) mustBe None 52 | 53 | } 54 | 55 | "一个Struct包含另一个Struct" in { 56 | val u3 = User( 57 | name := "Simon", 58 | age := 39, 59 | height := 1.75F 60 | ) 61 | 62 | val u4 = User( 63 | name := "Simon", 64 | age := 39, 65 | height := 1.75F, 66 | blood := BloodType.AB, 67 | male := true 68 | ) <~ Car( 69 | Car.license := "京GNR110", 70 | Car.vin := "123456789" 71 | ) 72 | 73 | val diff = u3.xor(u3).isOnlyRequireFields 74 | 75 | diff mustBe true 76 | 77 | val diff2 = u3.xor(u4).isOnlyRequireFields 78 | 79 | diff2 mustBe false 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/TPropertyTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.scalatest.{MustMatchers, WordSpec} 4 | import zzb.datatype.testuse.UserInfo 5 | 6 | 7 | /** 8 | * Created by Simon on 2014/6/27 9 | */ 10 | class TPropertyTest extends WordSpec with MustMatchers { 11 | 12 | "Property " must { 13 | 14 | "property access" in { 15 | import UserInfo._ 16 | val u0: UserInfo.Pack = UserInfo(userName := "simon", 17 | misc := Map("water" -> 100, "price" -> 23.1, "date" -> "1978-12-03 06:32:33")) 18 | 19 | u0.misc("water") mustBe Some(100) 20 | u0.misc("water") mustBe Some("100") 21 | 22 | 23 | u0.misc("water").get.isNumber mustBe true 24 | 25 | u0.misc("water").get.toInt mustBe 100 26 | 27 | u0.misc("water999").getOrElse("33") mustBe "33" 28 | 29 | val p0 = u0.misc 30 | val p1 = p0 + ("age" -> 40) 31 | 32 | val u1 = u0 <~ (u0.misc + ("age" -> 40)) 33 | u1.misc("age") mustBe Some(40) 34 | u1.misc("age") mustBe Some("40") 35 | 36 | val u2 = u1 <~ (u1.misc - "water") 37 | u2.misc("water") mustBe None 38 | u2.misc.getOrElse("water", "nothis") mustBe "nothis" 39 | u2.misc("age") mustBe Some(40) 40 | } 41 | "Property default " in { 42 | import UserInfo._ 43 | val u0: UserInfo.Pack = UserInfo(userName := "simon") 44 | u0.misc.size mustBe 0 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/ValidTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-11-24 10 | * Time: 下午7:39 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | class ValidTest extends WordSpec with MustMatchers { 14 | 15 | object UserName extends TString { 16 | val t_memo_ : String = "username" 17 | 18 | override val doPreValidate = true 19 | 20 | override def validators = List(minLength(3)) 21 | 22 | private def minLength(minLen :Int) = (s:UserName.Pack) => if(s.length >= minLen) None else Some(s"length must >= $minLen") 23 | } 24 | 25 | "Data valid" must { 26 | "failed on invalid init value" in { 27 | 28 | val n1 = UserName("simon") 29 | n1.value must equal("simon") 30 | 31 | intercept[DataInitValidException]{ 32 | val n2 = UserName("Hi") 33 | } 34 | 35 | 36 | 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/VariantTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | import org.joda.time.DateTime 4 | import org.scalatest.{MustMatchers, WordSpec} 5 | 6 | /** 7 | * Created by Simon on 2014/6/27 8 | */ 9 | class VariantTest extends WordSpec with MustMatchers { 10 | 11 | "Variant Type " must { 12 | "与数字类型之间进行转换" in { 13 | 14 | val b0 :Byte = 123 15 | val bv = TVariant(b0) 16 | val b1 :Byte = bv 17 | b0 mustBe b1 18 | bv mustBe b1 19 | bv.isBoolean mustBe false 20 | bv.isDateTime mustBe false 21 | bv.isNumber mustBe true 22 | val v : TVariant.Pack = b0 23 | v mustBe bv 24 | 25 | val bv2 = TVariant("234") 26 | 27 | val b2:Int = bv2 28 | b2 mustBe 234 29 | 30 | val s0 :Short = 1234 31 | val sv = TVariant(s0) 32 | val s1 :Short = sv 33 | s0 mustBe s1 34 | sv mustBe s1 35 | 36 | val i0 :Int = 12345 37 | val iv = TVariant(i0) 38 | val i1 :Int = iv 39 | i0 mustBe i1 40 | iv mustBe i1 41 | 42 | val d0 :Double = 12345.54321 43 | val dv = TVariant(d0) 44 | val d1 :Double = dv 45 | d0 mustBe d1 46 | dv mustBe d1 47 | } 48 | 49 | "与Bool类型之间进行转换" in { 50 | val bv = TVariant(true) 51 | val b1 :Boolean = bv 52 | b1 mustBe true 53 | bv mustBe true 54 | } 55 | 56 | "与 DateTime 之间的转换" in { 57 | val dv = TVariant("1978-12-03 06:32:33") 58 | dv.isBoolean mustBe false 59 | dv.isDateTime mustBe true 60 | 61 | val d1 :DateTime = dv 62 | TDateTime.date2String(d1) mustBe "1978-12-03 06:32:33" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/testuse/package.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype 2 | 3 | /** 4 | * Created by Simon on 2014/7/2 5 | */ 6 | package object testuse { 7 | 8 | import UserInfo.Format 9 | val Users = TList[UserInfo.Pack]("users","用户列表") 10 | val UsersP = TPackList(UserInfo,"users","用户列表") 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /zzb-datatype/src/test/scala/zzb/datatype/testuse/path/User.scala: -------------------------------------------------------------------------------- 1 | package zzb.datatype.testuse.path 2 | 3 | import zzb.datatype._ 4 | import zzb.datatype.testuse._ 5 | 6 | object BloodType extends Enumeration with TEnum { 7 | val t_memo_ = "血型" 8 | val A = Value(1, "A") 9 | val B = Value(2, "B") 10 | val O = Value(3, "O") 11 | val AB = Value(4, "AB") 12 | } 13 | 14 | object Car extends TStruct { 15 | val license = Field(TString("license", "车牌号")) 16 | val vin = Field(TString("vin", "车架号")) 17 | val t_memo_ : String = "车辆信息" 18 | } 19 | 20 | object User extends TStruct { 21 | 22 | val name = Field(TString("name", "姓名"), isRequired = true) 23 | val age = Field(TInt("age", "年龄")) 24 | val height = Field(TFloat("height", "身高")) 25 | val blood = FieldEnum(BloodType, default = () => BloodType.A) 26 | val birthDay = Field(TDateTime("birthday", "初生日期")) 27 | val male = Field(TBoolean("male", "性别")) 28 | 29 | import BasicFormats._ 30 | val Phones = TList[Int]("phones","电话号码") 31 | val phones = Field(Phones) 32 | 33 | val misc = FieldMap(TProperty("misc","杂项"),default = TProperty.empty) 34 | 35 | 36 | val car = Field(Car) 37 | 38 | val t_memo_ : String = "用户信息" 39 | 40 | implicit def packWrap(p: Pack): PackWrap = new PackWrap(p) 41 | 42 | class PackWrap(p: Pack) { 43 | def name = p(User.name).get.value 44 | } 45 | } 46 | 47 | object Student extends TStruct { 48 | 49 | val sname = Field(TString("sname", "姓名")) 50 | val sage = Field(TInt("sage", "年龄")) 51 | val height = Field(TFloat("height", "身高")) 52 | val blood = FieldEnum(BloodType, default = () => BloodType.A) 53 | val birthDay = Field(TDateTime("birthday", "初生日期")) 54 | val sex = Field(TBoolean("sex", "性别")) 55 | 56 | val car = Field(Car) 57 | 58 | val t_memo_ : String = "学生信息" 59 | 60 | implicit def packWrap(p: Pack) = new PackWrap(p) 61 | 62 | class PackWrap(p: Pack) { 63 | def name = p(User.name).get.value 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /zzb-dbaccess/src/main/scala/zzb/db/DBAccess.scala: -------------------------------------------------------------------------------- 1 | package zzb.db 2 | 3 | import org.squeryl.PrimitiveTypeMode 4 | import zzb.db.DBPools._ 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-8-19 10 | * Time: 下午8:25 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | trait DBAccess extends PrimitiveTypeMode { 14 | 15 | def transaction[A](name: String)(a: ⇒ A): Unit = 16 | transaction(sessionFactory(name))(a) 17 | 18 | def inTransaction[A](name: String)(a: ⇒ A): Unit = 19 | inTransaction(sessionFactory(name))(a) 20 | 21 | def trans[A](name: String)(a: ⇒ A): A = 22 | transaction(sessionFactory(name))(a) 23 | 24 | def inTrans[A](name: String)(a: ⇒ A): A = 25 | inTransaction(sessionFactory(name))(a) 26 | } 27 | -------------------------------------------------------------------------------- /zzb-dbaccess/src/main/scala/zzb/db/DBAccessSlick.scala: -------------------------------------------------------------------------------- 1 | package zzb.db 2 | 3 | import zzb.db.DBPools._ 4 | import scala.slick.jdbc.JdbcBackend 5 | /** 6 | * 7 | * Created by blackangel on 2014/8/21 8 | */ 9 | trait DBAccessSlick { 10 | def transaction[A](name: String)(a: JdbcBackend#Session ⇒ A): Unit ={ 11 | databaseSlick(name).withTransaction{ 12 | implicit session=> 13 | a(session) 14 | } 15 | } 16 | 17 | def trans[A](name: String)(a: JdbcBackend#Session ⇒ A): A = 18 | databaseSlick(name).withTransaction{ 19 | implicit session=> 20 | a(session) 21 | } 22 | 23 | def withSession[A](name: String)(a: JdbcBackend#Session ⇒ A): Unit ={ 24 | databaseSlick(name).withSession{ 25 | implicit session=> 26 | a(session) 27 | } 28 | } 29 | 30 | def withSessionR[A](name: String)(a: JdbcBackend#Session ⇒ A): A ={ 31 | databaseSlick(name).withSession{ 32 | implicit session=> 33 | a(session) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /zzb-dbaccess/src/main/scala/zzb/db/MongoAccess.scala: -------------------------------------------------------------------------------- 1 | package zzb.db 2 | 3 | import com.mongodb.casbah._ 4 | import Mongos._ 5 | /** 6 | * mongo操作 7 | * Created by blackangel on 2014/7/18. 8 | */ 9 | trait MongoAccess { 10 | 11 | def db[A](name:String)(f: MongoDB =>A)={ 12 | f(_db(name)) 13 | } 14 | } 15 | trait ExpressionNode{ 16 | def parent:Some[ExpressionNode] 17 | def children:List[ExpressionNode] 18 | } 19 | trait LogicalBoolean extends ExpressionNode{ 20 | 21 | } -------------------------------------------------------------------------------- /zzb-dbaccess/src/main/scala/zzb/db/Mongos.scala: -------------------------------------------------------------------------------- 1 | package zzb.db 2 | 3 | import com.typesafe.scalalogging.slf4j.Logging 4 | import com.mongodb.casbah._ 5 | import com.typesafe.config.Config 6 | 7 | /** 8 | * mongodb连接、关闭 9 | * Created by blackangel on 2014/7/18. 10 | */ 11 | object Mongos extends Logging { 12 | private val dbs: scala.collection.mutable.Map[String, (MongoClient,MongoDB)] = scala.collection.mutable.Map.empty 13 | 14 | /** 15 | * 连接一个mongodb数据库 16 | * uri: mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]] 17 | * 连接配置:http://docs.mongodb.org/manual/reference/connection-string/#connection-string-options 18 | * */ 19 | def openDB(name: String, dbConfig: Config){ 20 | require(dbConfig.hasPath("db")) 21 | val client: MongoClient = 22 | if (dbConfig.hasPath("uri")) 23 | MongoClient(MongoClientURI(dbConfig.getString("uri"))) 24 | else 25 | MongoClient(MongoClientURI("mongodb://localhost:27017/")) 26 | dbs(name.toLowerCase)=(client,client(dbConfig.getString("db"))) 27 | } 28 | 29 | def closeDB(name: String) = { 30 | 31 | if (dbs.contains(name.toLowerCase)) { 32 | dbs(name.toLowerCase)._1.close() 33 | dbs.remove(name.toLowerCase) 34 | } 35 | } 36 | def closeAllDB() { 37 | dbs.keySet.map(f=>closeDB(f)) 38 | } 39 | 40 | def _db(name:String)={ 41 | if(!dbs.contains(name.toLowerCase)) 42 | throw new Exception(s"mongo database $name not exist!") 43 | dbs(name.toLowerCase)._2 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /zzb-dbaccess/src/test/resources/DBSupport.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | db { 4 | 5 | usedb= ["firstdb","seconddb"] 6 | 7 | firstdb { 8 | 9 | driver = "org.h2.Driver" 10 | url = "jdbc:h2:./target/db/firstdb" 11 | user = "sa" 12 | password = "" 13 | pool{ 14 | min = 2 15 | max = 10 16 | inc = 1 17 | } 18 | } 19 | 20 | seconddb { 21 | driver = "org.h2.Driver" 22 | url = "jdbc:h2:./target/db/seconddb" 23 | user = "sa" 24 | password = "" 25 | pool{ 26 | min = 2 27 | max = 10 28 | inc = 1 29 | testTable = ping 30 | } 31 | } 32 | 33 | no_use { 34 | driver = "com.mysql.jdbc.Driver" 35 | url = "jdbc:mysql://localhost:3306/firstdb" 36 | } 37 | } -------------------------------------------------------------------------------- /zzb-dbaccess/src/test/resources/DBSupport2.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | db { 4 | 5 | usedb= ["firstdb","seconddb"] 6 | 7 | firstdb { 8 | 9 | driver = "org.h2.Driver" 10 | url = "jdbc:h2:./target/db/firstdb" 11 | user = "sa" 12 | password = "" 13 | pool{ 14 | min = 2 15 | max = 10 16 | inc = 1 17 | } 18 | } 19 | 20 | seconddb { 21 | driver = "org.h2.Driver" 22 | url = "jdbc:h2:./target/db/seconddb" 23 | user = "sa" 24 | password = "" 25 | pool{ 26 | min = 2 27 | max = 10 28 | inc = 1 29 | testTable = ping 30 | } 31 | } 32 | 33 | no_use { 34 | driver = "com.mysql.jdbc.Driver" 35 | url = "jdbc:mysql://localhost:3306/firstdb" 36 | } 37 | } -------------------------------------------------------------------------------- /zzb-dbaccess/src/test/scala/zzb/db/DBSlickTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.db 2 | 3 | import com.typesafe.config.ConfigFactory 4 | import org.scalatest.{BeforeAndAfterEach, MustMatchers, WordSpec} 5 | import zzb.db.entites.Entites._ 6 | import zzb.db.schema.SchemasSlick._ 7 | 8 | import scala.slick.driver.H2Driver.simple._ 9 | 10 | /** 11 | * Created with IntelliJ IDEA. 12 | * User: Simon Xiao 13 | * Date: 13-8-20 14 | * Time: 上午11:30 15 | * Copyright baoxian.com 2012~2020 16 | */ 17 | class DBSlickTest extends WordSpec with MustMatchers with BeforeAndAfterEach with DBAccessSlick { 18 | val config = ConfigFactory.load("DBSupport2") 19 | //DBPools.openDB("firstdb", config.getConfig("db.firstdb")) 20 | 21 | 22 | "db config file " must { 23 | 24 | "contans db block" in { 25 | 26 | assert(config.hasPath("db")) 27 | 28 | assert(config.hasPath("db.firstdb")) 29 | } 30 | 31 | "init db " must { 32 | "create connection pool" in { 33 | assert(DBPools.hasDB("firstdb")) 34 | } 35 | } 36 | } 37 | 38 | "data operator" must { 39 | "work" in { 40 | transaction("firstdb") { implicit session => 41 | val user: User = new User(None, "user1@domain.com", "oldPassword") 42 | users += user 43 | val result = users.filter(_.password === "oldPassword").map(_.email).list 44 | assert(result.size == 1) 45 | assert(result(0) == "user1@domain.com") 46 | } 47 | } 48 | } 49 | // 50 | override def beforeEach() = { 51 | DBPools.openDB("firstdb", config.getConfig("db.firstdb")) 52 | DBPools.openDB("seconddb", config.getConfig("db.seconddb")) 53 | transaction("firstdb"){implicit session => 54 | (users.ddl ++ companys.ddl).create 55 | } 56 | } 57 | 58 | override def afterEach() = { 59 | transaction("firstdb"){implicit session => 60 | (users.ddl ++ companys.ddl).drop 61 | } 62 | DBPools.closeAllDB() 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /zzb-dbaccess/src/test/scala/zzb/db/entites/BaseEntity.scala: -------------------------------------------------------------------------------- 1 | package zzb.db.entites 2 | 3 | import org.squeryl.KeyedEntity 4 | import java.sql.Timestamp 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-8-20 10 | * Time: 下午4:58 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | class BaseEntity extends KeyedEntity[Long] { 14 | 15 | val id: Long = 0 16 | var lastModified = new Timestamp(System.currentTimeMillis) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /zzb-dbaccess/src/test/scala/zzb/db/entites/Company.scala: -------------------------------------------------------------------------------- 1 | package zzb.db.entites 2 | 3 | import org.squeryl.KeyedEntity 4 | import java.sql.Timestamp 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-8-20 10 | * Time: 下午6:18 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | // 14 | //case class Company (var name: String, var address: String) extends KeyedEntity[Long] { 15 | // 16 | // val id:Long = 0 17 | // var lastModified = new Timestamp(System.currentTimeMillis) 18 | // 19 | // def this() = this("", "") 20 | // 21 | //} 22 | 23 | class Company(var name: String, var address: String) extends BaseEntity { 24 | 25 | def this() = this("", "") 26 | 27 | } 28 | -------------------------------------------------------------------------------- /zzb-dbaccess/src/test/scala/zzb/db/entites/Entites.scala: -------------------------------------------------------------------------------- 1 | package zzb.db.entites 2 | 3 | import scala.slick.direct._ 4 | import scala.slick.direct.AnnotationMapper._ 5 | /** 6 | * 7 | * Created by blackangel on 2014/8/21 8 | */ 9 | object Entites { 10 | case class Company(id:Option[Int],var name: String,var address: String) 11 | 12 | case class User(id:Option[Int],var email: String, var password: String) 13 | } 14 | -------------------------------------------------------------------------------- /zzb-dbaccess/src/test/scala/zzb/db/entites/User.scala: -------------------------------------------------------------------------------- 1 | package zzb.db.entites 2 | 3 | import java.sql.Timestamp 4 | import org.squeryl.KeyedEntity 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-8-20 10 | * Time: 下午4:58 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | 14 | ////case class 版本 15 | //case class User(var email: String, var password: String) extends KeyedEntity[Long] { 16 | // 17 | // val id:Long = 0 18 | // var lastModified = new Timestamp(System.currentTimeMillis) 19 | // // Zero argument constructor required 20 | // // Squeryl Roadmap says 0.9.5 will not need them :-) 21 | // def this() = this("", "") 22 | // 23 | //} 24 | 25 | // 非 case class 版本 26 | 27 | class User(var email: String, var password: String) extends BaseEntity { 28 | 29 | // Zero argument constructor required 30 | // Squeryl Roadmap says 0.9.5 will not need them :-) 31 | def this() = this("", "") 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /zzb-dbaccess/src/test/scala/zzb/db/schema/Schemas.scala: -------------------------------------------------------------------------------- 1 | package zzb.db.schema 2 | 3 | import org.squeryl._ 4 | //import org.squeryl.PrimitiveTypeMode._ 5 | import zzb.db.entites.{ Company, User } 6 | 7 | object Util extends PrimitiveTypeMode 8 | 9 | import Util._ 10 | 11 | /** 12 | * Created with IntelliJ IDEA. 13 | * User: Simon Xiao 14 | * Date: 13-8-20 15 | * Time: 下午5:00 16 | * Copyright baoxian.com 2012~2020 17 | */ 18 | object FirstSchema extends Schema { 19 | 20 | val users = table[User] 21 | 22 | on(users)(user ⇒ declare( 23 | user.id is autoIncremented, 24 | user.email is unique)) 25 | 26 | } 27 | 28 | object SecondSchema extends Schema { 29 | 30 | val company = table[Company] 31 | 32 | on(company)(company ⇒ declare( 33 | company.id is autoIncremented)) 34 | 35 | } -------------------------------------------------------------------------------- /zzb-dbaccess/src/test/scala/zzb/db/schema/SchemasSlick.scala: -------------------------------------------------------------------------------- 1 | package zzb.db.schema 2 | 3 | import zzb.db.entites.Entites._ 4 | 5 | /** 6 | * 7 | * Created by blackangel on 2014/8/21 8 | */ 9 | object SchemasSlick { 10 | import scala.slick.driver.H2Driver.simple._ 11 | class Companys(tag: Tag) extends Table[Company](tag,"Company"){ 12 | 13 | def id = column[Int]("id", O.PrimaryKey, O.AutoInc) 14 | def name = column[String]("name") 15 | def address = column[String]("address") 16 | def * = (id.?,name,address) <> (Company.tupled,Company.unapply) 17 | } 18 | 19 | class Users(tag: Tag) extends Table[User](tag,"User"){ 20 | 21 | def id = column[Int]("id", O.PrimaryKey, O.AutoInc) 22 | def email = column[String]("email") 23 | def password = column[String]("password") 24 | def * = (id.?,email,password) <> (User.tupled,User.unapply) 25 | } 26 | 27 | val companys =TableQuery[Companys] 28 | val users =TableQuery[Users] 29 | } 30 | -------------------------------------------------------------------------------- /zzb-domain/src/main/resources/theme/css/background.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepover/zzb/0a7fc2518fd3f34b66e22f81c045b54360971657/zzb-domain/src/main/resources/theme/css/background.gif -------------------------------------------------------------------------------- /zzb-domain/src/main/resources/theme/css/stylesheet.css: -------------------------------------------------------------------------------- 1 | /*reset*/ 2 | *{ margin:0; padding:0;font-family:Arial, Helvetica, sans-serif; } 3 | body{ font-size:100%;} 4 | li{ list-style:none;} 5 | a{ color:#06c; text-decoration:none;} 6 | /*main*/ 7 | 8 | .header{ background:#444;} 9 | .header .subTitle{ font-size:1em; padding:10px; color:#fff;} 10 | .header .title{ background:#1675D3; padding:10px; font-size:1em; color:#fff;} 11 | .header .title a{ color:#ccc;} 12 | 13 | .inheritance{ background:#f7f7f7; padding:5px 10px; font-size:12px;} 14 | .inheritance ul li{ border-left:1px solid #c2c2c2; padding-left:10px; margin-left:10px;} 15 | 16 | .contentContainer{ padding:20px;} 17 | .summary{ border:1px solid #c2c2c2; background:#f7f7f7; box-shadow:#ccc 0px 0px 5px;} 18 | .summary .blockList h3{ padding:10px; color:#888; font-size:1em} 19 | .summary .blockList table.overviewSummary{ width:100%;font-size:.8em} 20 | .summary .blockList table th{ background:#06c; color:#fff; text-align:left; padding:5px 0; width:10%; text-indent:.5em;} 21 | .summary .blockList table td{ background:#fff; color:#444; text-align:left; padding:5px 0; border-top:1px solid #ececec; text-indent:.5em;} 22 | .summary .blockList table tr:hover td{background:#f7f7f7;} 23 | .summary .blockList caption{ text-align:left; padding:5px; background:#444; color:#fff} -------------------------------------------------------------------------------- /zzb-domain/src/main/resources/theme/css/tab.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepover/zzb/0a7fc2518fd3f34b66e22f81c045b54360971657/zzb-domain/src/main/resources/theme/css/tab.gif -------------------------------------------------------------------------------- /zzb-domain/src/main/resources/theme/css/titlebar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepover/zzb/0a7fc2518fd3f34b66e22f81c045b54360971657/zzb-domain/src/main/resources/theme/css/titlebar.gif -------------------------------------------------------------------------------- /zzb-domain/src/main/resources/theme/css/titlebar_end.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepover/zzb/0a7fc2518fd3f34b66e22f81c045b54360971657/zzb-domain/src/main/resources/theme/css/titlebar_end.gif -------------------------------------------------------------------------------- /zzb-domain/src/main/scala/zzb/domain/ActionError.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain 2 | 3 | import spray.http.LazyValueBytesRenderable 4 | 5 | /** 6 | * Created by Simon on 2014/6/11 7 | */ 8 | trait ActionError extends LazyValueBytesRenderable { 9 | def intValue: Int 10 | 11 | def value: String = intValue.toString + ' ' + reason 12 | 13 | def reason: String 14 | 15 | def isSuccess: Boolean = false 16 | 17 | def isFailure: Boolean = true 18 | } 19 | 20 | -------------------------------------------------------------------------------- /zzb-domain/src/main/scala/zzb/domain/DomainLogging.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain 2 | 3 | import akka.actor.{ActorLogging, Actor} 4 | import akka.event.{BusLogging, DiagnosticLoggingAdapter, LogSource} 5 | import zzb.domain.directive.AuthorizedOperator 6 | import zzb.util.MdcLoggingContext 7 | 8 | /** 9 | * Created by Simon on 2014/6/29 10 | */ 11 | trait DomainLogging extends ActorLogging{ this: Actor ⇒ 12 | private var _log: DiagnosticLoggingAdapter = _ 13 | 14 | protected var mdc = Map[String,Any]() 15 | 16 | def logName :String 17 | 18 | //def withHeritLog(herit:String): ( =>Unit) => Unit = logStatement => withHerit(herit)(logStatement) 19 | 20 | case class HeritLog(herit:String){ 21 | def apply(logStatement: ⇒ Unit):Unit = { 22 | log.mdc(log.mdc ++ mdc ++ Map("herit" -> herit)) 23 | logStatement 24 | } 25 | def apply(mdcMap :Map[String,Any])(logStatement: ⇒ Unit):Unit = { 26 | log.mdc(log.mdc ++ mdc ++ mdcMap ++ Map("herit" -> herit)) 27 | logStatement 28 | } 29 | def apply(opt:AuthorizedOperator)(logStatement: ⇒ Unit):Unit = { 30 | apply(Map("opt" -> opt))(logStatement) 31 | } 32 | } 33 | 34 | final def withHerit(herit :String,otherMdc:Map[String,Any]=Map())(logStatement: ⇒ Unit){ 35 | withMdc(otherMdc ++ Map("herit" -> herit))(logStatement) 36 | } 37 | 38 | @inline 39 | final def withMdc(mdcMap :Map[String,Any])(logStatement: ⇒ Unit){ 40 | log.mdc( this.mdc ++ mdcMap) 41 | logStatement 42 | } 43 | 44 | protected def logFactory : DiagnosticLoggingAdapter = { 45 | 46 | val (str, clazz) = LogSource( logName ) 47 | new BusLogging(context.system.eventStream, str, clazz) with DiagnosticLoggingAdapter 48 | } 49 | 50 | override def log: DiagnosticLoggingAdapter = { 51 | // only used in Actor, i.e. thread safe 52 | if (_log eq null) 53 | _log = logFactory 54 | _log 55 | } 56 | 57 | implicit lazy val mdcLogCtx = MdcLoggingContext.fromAdapter(log) 58 | 59 | } 60 | -------------------------------------------------------------------------------- /zzb-domain/src/main/scala/zzb/domain/http/TypeViewApi.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain.http 2 | 3 | import zzb.datatype.meta.EnumTypeInfo 4 | import zzb.rest.http2akka.HttpApi 5 | import spray.routing.Route 6 | import zzb.datatype.{TEnum, EnumRegistry, StructRegistry} 7 | import spray.json.DefaultJsonProtocol 8 | import DefaultJsonProtocol._ 9 | import spray.httpx.SprayJsonSupport._ 10 | import spray.httpx.TwirlSupport 11 | 12 | /** 13 | * Created by Simon on 2014/6/12 14 | */ 15 | trait TypeViewApi extends HttpApi with TwirlSupport with DomainHttpDirectives { 16 | val rootPath: String 17 | 18 | def domainRoute: DomainHttpRoute = structPath => 19 | parameter("json".as[Boolean] ?) { json: Option[Boolean] => 20 | if (json.isDefined && json.get) { 21 | complete(structPath.targetType.typeInfo) 22 | } else 23 | structPath.targetType match { 24 | case eumnType: TEnum => 25 | complete(html.typzenum.render(rootPath, structPath, structPath.targetType.typeInfo.asInstanceOf[EnumTypeInfo])) 26 | case _ => 27 | complete(html.typz.render(rootPath, structPath, structPath.targetType.typeInfo)) 28 | } 29 | } 30 | 31 | override def api: Route = getFromResourceDirectory("theme") ~ 32 | pathEndOrSingleSlash { 33 | complete(html.typeList.render(rootPath)) 34 | } ~ pathPrefix(Segment) { typeName => 35 | (StructRegistry.get(typeName), EnumRegistry.get(typeName)) match { 36 | case (Some(theType), _) => 37 | handDoc(theType, domainRoute) 38 | case (_, Some(theType)) => 39 | parameter("json".as[Boolean] ?) { json: Option[Boolean] => 40 | if (json.isDefined && json.get) { 41 | complete(theType.typeInfo) 42 | } else 43 | complete(html.typzenum.render(rootPath, () => theType, theType.typeInfo)) 44 | } 45 | case _ => reject 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /zzb-domain/src/main/scala/zzb/domain/package.scala: -------------------------------------------------------------------------------- 1 | package zzb 2 | 3 | import spray.json.DefaultJsonProtocol 4 | import DefaultJsonProtocol._ 5 | import zzb.domain.directive.AuthorizedOperator 6 | 7 | /** 8 | * Created by Simon on 2014/4/24 9 | */ 10 | package object domain { 11 | // implicit val versionReviseFormat = jsonFormat2(VersionRevise.apply) 12 | // implicit val alterInfoFormat = jsonFormat3(AlterInfo.apply) 13 | // implicit val aoFormat = jsonFormat2(AuthorizedOperator.apply) 14 | } 15 | -------------------------------------------------------------------------------- /zzb-domain/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 14 | 15 | 16 | 21 | 22 | %date{ISO8601} %-5level %logger{16} [t:%-3.3X{sourceThread}][h:%-3.3X{herit}][op:%-8X{opt}][ow:%X{owner}][pid:%X{pid}] - %msg%n 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /zzb-domain/src/test/scala/zzb/domain/ActionExtractorTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain 2 | 3 | import org.scalatest.{MustMatchers, WordSpec} 4 | import zzb.datatype._ 5 | import zzb.domain.directive.AuthorizedOperator 6 | import zzb.domain.plane.Plane 7 | 8 | /** 9 | * Created by Simon on 2014/7/16 10 | */ 11 | class ActionExtractorTest extends WordSpec with MustMatchers { 12 | 13 | "Action " must { 14 | 15 | val manager = AuthorizedOperator("admin",isManager = true) 16 | 17 | val user = AuthorizedOperator("jack",isManager = false) 18 | 19 | val params = Map("width" -> "100","height" -> "300") 20 | 21 | "正常解析出各项元素" in { 22 | val a = Action("fly",manager,params,Some(TString("hello"))) 23 | val res = a match { 24 | case Action(name,opt,p,Some(TString(h))) => 25 | name + "-" + opt.id + "-" + p("width") + "-" + h 26 | case _ => "no match" 27 | } 28 | res mustBe "fly-admin-100-hello" 29 | } 30 | 31 | "匹配指定名称,大小写无关未实现" in { 32 | 33 | val a = Action("fly",manager,params,Some(TString("hello"))) 34 | val res0 = a match { 35 | case Action("fly" ,opt,p,Some(TString(h))) => 36 | "fly" + "-" + opt.id + "-" + p("width") + "-" + h 37 | case _ => "no match" 38 | } 39 | res0 mustBe "fly-admin-100-hello" 40 | } 41 | "ddd" in { 42 | val plane = Plane(Plane.id := "1000",Plane.owner := "simon") 43 | val a = Action("fly",manager,params,Some(plane)) 44 | 45 | val res0 = a match { 46 | case Action(name,opt,p,Some(TString(h))) => 47 | name + "-" + opt.id + "-" + p("width") + "-" + h 48 | case _ => "no match" 49 | } 50 | res0 mustBe "no match" 51 | 52 | val res1 = a match { 53 | case Action(name,opt,p,Some(Plane(h))) => 54 | name + "-" + opt.id + "-" + p("width") + "-" + h.id 55 | case _ => "no match" 56 | } 57 | res1 mustBe "fly-admin-100-1000" 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /zzb-domain/src/test/scala/zzb/domain/ActionPostEntityTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain 2 | 3 | import spray.http.StatusCodes._ 4 | import spray.json.{JsString, JsonParser} 5 | import zzb.datatype._ 6 | import zzb.domain.plane.{Foods, PlaneState} 7 | 8 | /** 9 | * Created by Simon on 2014/7/18 10 | */ 11 | class ActionPostEntityTest extends PlaneHttpTestBase { 12 | 13 | "Plane Http Api " - { 14 | 15 | "可以创建一个新的领域对象 " in { 16 | user(Post("/api/planes", entity(TString("simon").json))) ~> check { 17 | status mustBe OK 18 | pid = JsonParser(body.asString).asInstanceOf[JsString].value 19 | pid.length mustBe 5 20 | } 21 | 22 | user(Get(s"/api/planes/$pid/latest/state")) ~> check { 23 | status mustBe OK 24 | PlaneState.fromJsValue(JsonParser(body.asString)).idx mustBe 1 //Stop 25 | } 26 | } 27 | 28 | "可以使用Acton Post 数据" in { 29 | 30 | val f1 = Foods(Foods.water := 2,Foods.bread := 2) 31 | 32 | val f2 = Foods(Foods.water := 5,Foods.bread := 2) 33 | 34 | manager(Post(s"/api/planes/$pid/action/reloadFools",f1.json)) ~> check{ 35 | status mustBe OK 36 | } 37 | 38 | user(Get(s"/api/planes/$pid/latest/foods/water")) ~> check { 39 | status mustBe OK 40 | TInt.fromJsValue(JsonParser(body.asString)).value mustBe 2 41 | } 42 | 43 | manager(Post(s"/api/planes/$pid/action/reloadFools",f2.json)) ~> check{ 44 | status mustBe OK 45 | } 46 | 47 | user(Get(s"/api/planes/$pid/latest/foods/water")) ~> check { 48 | status mustBe OK 49 | TInt.fromJsValue(JsonParser(body.asString)).value mustBe 5 50 | } 51 | 52 | manager(Post(s"/api/planes/$pid/action/nothis",f2.json)) ~> check{ 53 | status mustBe NotFound 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /zzb-domain/src/test/scala/zzb/domain/ErrorRequestTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain 2 | 3 | import spray.http.StatusCodes._ 4 | 5 | /** 6 | * Created by Simon on 2014/7/17 7 | */ 8 | class ErrorRequestTest extends PlaneHttpTestBase { 9 | 10 | "访问不存在的文档会报 404 " in { 11 | 12 | //管理员请求创建一个新的Alter会话 13 | manager(Post(s"/api/planes/nothis/alter")) ~> check { 14 | status mustBe NotFound 15 | } 16 | 17 | manager(Get(s"/api/planes/nothis/latest")) ~> check { 18 | val msg = body.asString 19 | status mustBe NotFound 20 | } 21 | 22 | manager(Post(s"/api/planes/nothis/alter")) ~> check { 23 | status mustBe NotFound 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /zzb-domain/src/test/scala/zzb/domain/PlaneListTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain 2 | 3 | import spray.http.StatusCodes._ 4 | import spray.json.{JsString, JsArray, JsonParser} 5 | import zzb.datatype.TString 6 | import zzb.domain.plane.Plane 7 | 8 | /** 9 | * Created by Simon on 2014/8/19 10 | */ 11 | class PlaneListTest extends PlaneHttpTestBase { 12 | 13 | "Plane Http Api " - { 14 | 15 | "可以取得列表空" in { 16 | user(Get("/api/planes/list") ) ~> check { 17 | println(body.asString) 18 | val list = JsonParser(body.asString).asInstanceOf[JsArray] 19 | list.elements.size mustBe 0 20 | status mustBe OK 21 | } 22 | } 23 | 24 | "可以取得非空列表" in { 25 | user(Post("/api/planes", entity(TString("simon").json))) ~> check { 26 | status mustBe OK 27 | pid = JsonParser(body.asString).asInstanceOf[JsString].value 28 | pid.length mustBe 5 29 | } 30 | 31 | user(Post("/api/planes", entity(TString("simon").json))) ~> check { 32 | status mustBe OK 33 | pid = JsonParser(body.asString).asInstanceOf[JsString].value 34 | pid.length mustBe 5 35 | } 36 | 37 | user(Get("/api/planes/list") ) ~> check { 38 | println(body.asString) 39 | val list = JsonParser(body.asString).asInstanceOf[JsArray] 40 | list.elements.size mustBe 2 41 | val plane = Plane.fromJsValue(list.elements(0)) 42 | plane.owner mustBe "simon" 43 | status mustBe OK 44 | } 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /zzb-domain/src/test/scala/zzb/domain/directive/AuthorizeDirectivesTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain.directive 2 | 3 | import akka.actor.{ActorSystem, Props} 4 | import akka.util.Timeout 5 | import org.scalatest.{BeforeAndAfterAll, MustMatchers, WordSpec} 6 | import spray.util._ 7 | import zzb.rest._ 8 | 9 | import scala.concurrent.duration._ 10 | 11 | /** 12 | * Created by Simon on 2014/3/25 13 | */ 14 | class AuthorizeDirectivesTest extends WordSpec with MustMatchers 15 | with Requester with BeforeAndAfterAll { 16 | 17 | implicit val system = ActorSystem() 18 | 19 | override protected def beforeAll() { 20 | 21 | system.actorOf(Props[WorkActor], "work") 22 | } 23 | 24 | override protected def afterAll() { 25 | super.afterAll() 26 | system.shutdown() 27 | } 28 | 29 | implicit val timeout = Timeout(5000000 milliseconds) 30 | 31 | "WorkActor " must { 32 | import zzb.rest.RestHeaders._ 33 | import zzb.rest.StatusCodes._ 34 | 35 | "check opetator before create new MultiQuote" in { 36 | 37 | val res1 = (Post("/work") ~> RawHeader("X-User-Id", "user-Joe42") ~> doRequest).await 38 | res1.status mustBe OK 39 | res1.entity.data.asInstanceOf[AuthorizedOperator].isManager mustBe false 40 | 41 | 42 | val res2 = (Post("/work") ~> doRequest).await 43 | res2.status mustBe Forbidden 44 | 45 | val res3 = (Post("/work") ~> RawHeader("X-Manager-Id", "manager-simon")~> doRequest).await 46 | res3.status mustBe OK 47 | res3.entity.data.asInstanceOf[AuthorizedOperator].isManager mustBe true 48 | 49 | } 50 | 51 | } 52 | 53 | } 54 | 55 | class WorkActor extends RestServiceActor with AuthorizeDirectives{ 56 | override def receive: Receive = runRoute(route) 57 | 58 | def route: Route = 59 | operator { 60 | operator => 61 | complete(operator) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /zzb-domain/src/test/scala/zzb/domain/plane/PlaneHttpApi.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain.plane 2 | 3 | import spray.httpx.SprayJsonSupport._ 4 | import spray.routing._ 5 | import zzb.datatype._ 6 | import zzb.domain.ActionBuilder 7 | import zzb.domain.http.DomainHttpApi 8 | 9 | /** 10 | * Created by Simon on 2014/5/15 11 | */ 12 | trait PlaneHttpApi extends DomainHttpApi[String, TString,Plane.type ]{ 13 | 14 | override val domainType = Plane 15 | 16 | override val actionBuilder: ActionBuilder = PlaneActionBuilder 17 | 18 | def logName :String = "zzb.domain.plane.http" 19 | 20 | //implicit def sss[T] : RootJsonWriter[List[T]] = ??? 21 | 22 | 23 | def api: Route = 24 | pathEndOrSingleSlash { 25 | post { 26 | entity(as[TString.Pack]) { 27 | owner => 28 | akkaWithEntityCompleteAs[TString.Pack](owner) 29 | } 30 | } 31 | } ~ pathPrefix(Segment) { 32 | case "list" => akkaCompleteAsList[Plane.Pack] 33 | case "options" => optionRoute 34 | case "force-gc" => java.lang.System.gc();complete("do gc now!") 35 | case id => //plane id 36 | domainApi 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /zzb-domain/src/test/scala/zzb/domain/plane/PlaneSvcActor.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain.plane 2 | 3 | import zzb.rest._ 4 | import akka.actor.Props 5 | import zzb.rest.util.StatableActor 6 | import spray.http.StatusCodes._ 7 | 8 | /** 9 | * Created by Simon on 2014/6/13 10 | */ 11 | 12 | class PlaneSvcActor extends RestServiceActor { 13 | override def receive: Receive = runRoute(route) 14 | 15 | 16 | implicit def childByName(name: String) = { 17 | name match { 18 | case "planes" => 19 | Right(context.actorOf(Props(new PlaneSetActor with StatableActor), "planes")) 20 | case _ => Left((NotFound, "error")) 21 | } 22 | } 23 | 24 | def route: Route = 25 | pathEndOrSingleSlash { 26 | post { 27 | complete("ok") 28 | } 29 | } ~ 30 | forwardChild 31 | } 32 | -------------------------------------------------------------------------------- /zzb-domain/src/test/scala/zzb/domain/plane/package.scala: -------------------------------------------------------------------------------- 1 | package zzb.domain 2 | 3 | import zzb.datatype.{TStrKeyPackMap, TPackList} 4 | 5 | /** 6 | * Created by Simon on 2014/5/14 7 | */ 8 | package object plane { 9 | 10 | 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /zzb-rest-testkit/src/main/scala/zzb/rest/testkit/ScalatestInterface.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.testkit 2 | 3 | import org.scalatest.exceptions.TestFailedException 4 | import org.scalatest.{ Suite, BeforeAndAfterAll } 5 | 6 | trait ScalatestInterface extends TestFrameworkInterface with BeforeAndAfterAll { 7 | this: Suite ⇒ 8 | 9 | def failTest(msg: String) = throw new TestFailedException(msg, 11) 10 | 11 | abstract override protected def afterAll(): Unit = { 12 | cleanUp() 13 | super.afterAll() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /zzb-rest-testkit/src/main/scala/zzb/rest/testkit/Specs2Interface.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.testkit 2 | 3 | import org.specs2.execute.{ Failure, FailureException } 4 | import org.specs2.specification.{ FragmentsBuilder, SpecificationStructure, Fragments, Step } 5 | 6 | trait Specs2Interface extends TestFrameworkInterface with SpecificationStructure { 7 | 8 | def failTest(msg: String) = { 9 | val trace = new Exception().getStackTrace.toList 10 | val fixedTrace = trace.drop(trace.indexWhere(_.getClassName.startsWith("org.specs2")) - 1) 11 | throw new FailureException(Failure(msg, stackTrace = fixedTrace)) 12 | } 13 | 14 | override def map(fs: ⇒ Fragments) = super.map(fs).add(Step(cleanUp())) 15 | } 16 | 17 | trait NoAutoHtmlLinkFragments extends FragmentsBuilder { 18 | override def stringToHtmlLinkFragments2(s: String): HtmlLinkFragments2 = super.stringToHtmlLinkFragments2(s) 19 | override def stringToHtmlLinkFragments(s: String): HtmlLinkFragments = super.stringToHtmlLinkFragments(s) 20 | } -------------------------------------------------------------------------------- /zzb-rest-testkit/src/main/scala/zzb/rest/testkit/TestFrameworkInterface.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.testkit 2 | 3 | trait TestFrameworkInterface { 4 | 5 | def cleanUp() 6 | 7 | def failTest(msg: String): Nothing 8 | } 9 | -------------------------------------------------------------------------------- /zzb-rest-tests/src/test/scala/zzb/rest/directives/BasicDirectivesSpec.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package directives 3 | 4 | 5 | import zzb.rest.{RestResponse} 6 | 7 | class BasicDirectivesSpec extends RoutingSpec { 8 | 9 | "The 'routeRouteResponse' directive" should { 10 | "in its simple String form" in { 11 | val addYeah = routeRouteResponse { 12 | case RestResponse(_, entity, _) ⇒ complete(entity.asString + "Yeah") 13 | } 14 | Get() ~> addYeah(complete("abc")) ~> check { responseAs[String] === "abcYeah" } 15 | } 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /zzb-rest-tests/src/test/scala/zzb/rest/directives/ExecutionDirectivesSpec.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package directives 3 | 4 | import StatusCodes._ 5 | 6 | class ExecutionDirectivesSpec extends RoutingSpec { 7 | 8 | "the 'dynamicIf' directive" should { 9 | "cause its inner route to be revaluated for every request anew, if enabled" in { 10 | var a = "" 11 | val staticRoute = get { dynamicIf(enabled = false) { a += "x"; complete(a) } } 12 | val dynamicRoute = get { dynamic { a += "x"; complete(a) } } 13 | def expect(route: Route, s: String) = Get() ~> route ~> check { responseAs[String] === s } 14 | expect(staticRoute, "x") 15 | expect(staticRoute, "x") 16 | expect(staticRoute, "x") 17 | expect(dynamicRoute, "xx") 18 | expect(dynamicRoute, "xxx") 19 | expect(dynamicRoute, "xxxx") 20 | } 21 | } 22 | 23 | "the 'detach directive" should { 24 | "handle exceptions thrown inside its inner future" in { 25 | 26 | implicit val exceptionHandler = ExceptionHandler { 27 | case e: ArithmeticException ⇒ ctx ⇒ 28 | ctx.complete(InternalServerError, "Oops.") 29 | } 30 | 31 | val route = get { 32 | detach() { 33 | complete((3 / 0).toString) 34 | } 35 | } 36 | 37 | Get() ~> route ~> check { 38 | status === InternalServerError 39 | responseAs[String] === "Oops." 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /zzb-rest-tests/src/test/scala/zzb/rest/directives/FutureDirectivesSpec.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package directives 3 | 4 | import scala.concurrent.Future 5 | import spray.util.SingletonException 6 | 7 | 8 | class FutureDirectivesSpec extends RoutingSpec { 9 | 10 | object TestException extends SingletonException 11 | 12 | "The `onComplete` directive" should { 13 | "properly unwrap a Future in the success case" in { 14 | var i = 0 15 | def nextNumber() = { i += 1; i } 16 | val route = onComplete(Future.successful(nextNumber())) { echoComplete } 17 | Get() ~> route ~> check { 18 | responseAs[String] === "Success(1)" 19 | } 20 | Get() ~> route ~> check { 21 | responseAs[String] === "Success(2)" 22 | } 23 | } 24 | "properly unwrap a Future in the failure case" in { 25 | Get() ~> onComplete(Future.failed(new RuntimeException("no"))) { echoComplete } ~> check { 26 | responseAs[String] === "Failure(java.lang.RuntimeException: no)" 27 | } 28 | } 29 | } 30 | 31 | "The `onSuccess` directive" should { 32 | "properly unwrap a Future in the success case" in { 33 | Get() ~> onSuccess(Future.successful("yes")) { echoComplete } ~> check { 34 | responseAs[String] === "yes" 35 | } 36 | } 37 | "throw an exception in the failure case" in { 38 | Get() ~> onSuccess(Future.failed(TestException)) { echoComplete } ~> check { 39 | status === StatusCodes.InternalServerError 40 | } 41 | } 42 | } 43 | 44 | "The `onFailure` directive" should { 45 | "properly unwrap a Future in the success case" in { 46 | Get() ~> onFailure(Future.successful("yes")) { echoComplete } ~> check { 47 | responseAs[String] === "yes" 48 | } 49 | } 50 | "throw an exception in the failure case" in { 51 | Get() ~> onFailure(Future.failed[String](TestException)) { echoComplete } ~> check { 52 | responseAs[String] === "zzb.rest.directives.FutureDirectivesSpec$TestException$" 53 | } 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /zzb-rest-tests/src/test/scala/zzb/rest/directives/RespondWithDirectivesSpec.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package directives 3 | 4 | import StatusCodes._ 5 | import RestHeaders.RawHeader 6 | 7 | class RespondWithDirectivesSpec extends RoutingSpec { 8 | 9 | "respondWithStatus" should { 10 | "set the given status on successful responses" in { 11 | Get() ~> { 12 | respondWithStatus(Created) { completeOk } 13 | } ~> check { response === RestResponse(Created) } 14 | } 15 | "leave rejections unaffected" in { 16 | Get() ~> { 17 | respondWithStatus(Created) { reject } 18 | } ~> check { rejections === Nil } 19 | } 20 | } 21 | 22 | "respondWithHeader" should { 23 | val customHeader = RawHeader("custom", "custom") 24 | "add the given headers to successful responses" in { 25 | Get() ~> { 26 | respondWithHeader(customHeader) { completeOk } 27 | } ~> check { response === RestResponse(headers = customHeader :: Nil) } 28 | } 29 | "leave rejections unaffected" in { 30 | Get() ~> { 31 | respondWithHeader(customHeader) { reject } 32 | } ~> check { rejections === Nil } 33 | } 34 | } 35 | 36 | // "The 'respondWithMediaType' directive" should { 37 | // 38 | // "override the media-type of its inner route response" in { 39 | // Get() ~> { 40 | // respondWithMediaType(`text/html`) { 41 | // complete(yeah) 42 | // } 43 | // } ~> check { mediaType === `text/html` } 44 | // } 45 | // 46 | // "disable content-negotiation for its inner marshaller" in { 47 | // Get() ~> addHeader(Accept(`text/css`)) ~> { 48 | // respondWithMediaType(`text/css`) { 49 | // complete(yeah) 50 | // } 51 | // } ~> check { mediaType === `text/css` } 52 | // } 53 | // 54 | // "reject an unacceptable request" in { 55 | // Get() ~> addHeader(Accept(`text/css`)) ~> { 56 | // respondWithMediaType(`text/xml`) { 57 | // complete(yeah) 58 | // } 59 | // } ~> check { rejection === UnacceptedResponseContentTypeRejection(List(ContentType(`text/xml`))) } 60 | // } 61 | // } 62 | 63 | } -------------------------------------------------------------------------------- /zzb-rest-tests/src/test/scala/zzb/rest/directives/RoutingSpec.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package directives 3 | 4 | import org.specs2.mutable.Specification 5 | import zzb.rest.testkit.Specs2RouteTest 6 | 7 | class RoutingSpec extends Specification with Directives with Specs2RouteTest { 8 | 9 | val Ok = RestResponse() 10 | val completeOk = complete(Ok) 11 | 12 | def echoComplete[T]: T ⇒ Route = { x ⇒ complete(x.toString) } 13 | def echoComplete2[T, U]: (T, U) ⇒ Route = { (x, y) ⇒ complete(s"$x $y") } 14 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/resources/reference.conf: -------------------------------------------------------------------------------- 1 | zzb.rest { 2 | 3 | # Enables/disables the returning of more detailed error messages to the 4 | # client in the error response 5 | # Should be disabled for browser-facing APIs due to the risk of XSS attacks 6 | # and (probably) enabled for internal or non-browser APIs 7 | # (Note that spray will always produce log messages containing the full error details) 8 | verbose-error-messages = off 9 | 10 | 11 | # Enables/disables the rendering of the "rendered by" footer in directory listings 12 | render-vanity-footer = yes 13 | 14 | # a config section holding plain-text user/password entries 15 | # for the default FromConfigUserPassAuthenticator 16 | users { 17 | # bob = secret 18 | } 19 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/ApplyConverter.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import shapeless._ 4 | 5 | abstract class ApplyConverter[L <: HList] { 6 | type In 7 | def apply(f: In): L ⇒ Route 8 | } 9 | 10 | object ApplyConverter extends ApplyConverterInstances { 11 | implicit val hac0 = new ApplyConverter[HNil] { 12 | type In = Route 13 | def apply(fn: In) = { 14 | case HNil ⇒ fn 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/Directives.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import directives._ 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 14-3-1 9 | * Time: 上午10:34 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | trait Directives extends RouteConcatenation 13 | with AnyParamDirectives 14 | with BasicDirectives 15 | with ExecutionDirectives 16 | with ForwardDirectives 17 | with FormFieldDirectives 18 | with FutureDirectives 19 | with HeaderDirectives 20 | with MethodDirectives 21 | with MiscDirectives 22 | with MarshallingDirectives 23 | with ParameterDirectives 24 | with PathDirectives 25 | with RouteDirectives 26 | with RespondWithDirectives 27 | with ZzbDatatypeDirectives 28 | with SecurityDirectives 29 | 30 | object Directives extends Directives -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/ExceptionHandler.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import scala.util.control.NonFatal 4 | import spray.util.LoggingContext 5 | import spray.http.{ RequestProcessingException, IllegalRequestException } 6 | 7 | //import spray.http._ 8 | import StatusCodes._ 9 | 10 | trait ExceptionHandler extends ExceptionHandler.PF 11 | 12 | object ExceptionHandler { 13 | type PF = PartialFunction[Throwable, Route] 14 | 15 | implicit def apply(pf: PF): ExceptionHandler = 16 | new ExceptionHandler { 17 | def isDefinedAt(error: Throwable) = pf.isDefinedAt(error) 18 | def apply(error: Throwable) = pf(error) 19 | } 20 | 21 | implicit def default(implicit log: LoggingContext): ExceptionHandler = 22 | apply { 23 | case e: IllegalRequestException ⇒ ctx ⇒ 24 | log.warning("Illegal request {}\n\t{}\n\tCompleting with '{}' response", 25 | ctx.request, e.getMessage, e.status) 26 | ctx.complete(e.status) 27 | 28 | case e: RequestProcessingException ⇒ ctx ⇒ 29 | log.warning("Request {} could not be handled normally\n\t{}\n\tCompleting with '{}' response", 30 | ctx.request, e.getMessage, e.status) 31 | ctx.complete(e.status) 32 | 33 | case NonFatal(e) ⇒ ctx ⇒ 34 | log.error(e, "Error during processing of request {}", ctx.request) 35 | ctx.complete(InternalServerError, e.getMessage) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/HListDeserializer.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import shapeless._ 4 | import zzb.rest.unmarshalling._ 5 | import spray.util.pimpString_ 6 | 7 | // I don't think we can get around spelling out 22 different cases without giving up on our short 8 | // directive.as(CaseClass) notation (since we have to provide a dedicated magnet for the proper 9 | // apply function type (e.g. (A, B, C) => CC), but we might be able to simplify the implementation 10 | // of the 22 cases by converting into an HList that can then be mapped/folded over 11 | 12 | trait HListDeserializer[L <: HList, T] extends Deserializer[L, T] 13 | 14 | object HListDeserializer extends HListDeserializerInstances { 15 | 16 | protected type DS[A, AA] = Deserializer[A, AA] // alias for brevity 17 | 18 | implicit def fromDeserializer[L <: HList, T](ds: DS[L, T]) = new HListDeserializer[L, T] { 19 | def apply(list: L) = ds(list) 20 | } 21 | 22 | /////////////////////////////// CASE CLASS DESERIALIZATION //////////////////////////////// 23 | 24 | // we use a special exception to bubble up errors rather than relying on long "right.flatMap" cascades in order to 25 | // save lines of code as well as excessive closure class creation in the many "hld" methods below 26 | private class BubbleLeftException(val left: Left[Any, Any]) extends RuntimeException 27 | 28 | protected def create[L <: HList, T](deserialize: L ⇒ T): HListDeserializer[L, T] = 29 | new HListDeserializer[L, T] { 30 | def apply(list: L): Deserialized[T] = 31 | try Right(deserialize(list)) 32 | catch { 33 | case e: BubbleLeftException ⇒ e.left.asInstanceOf[Left[DeserializationError, T]] 34 | case e: IllegalArgumentException ⇒ Left(MalformedContent(e.getMessage.nullAsEmpty, e)) 35 | } 36 | } 37 | 38 | protected def get[T](either: Either[DeserializationError, T]): T = either match { 39 | case Right(x) ⇒ x 40 | case left: Left[_, _] ⇒ throw new BubbleLeftException(left) 41 | } 42 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/HListable.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import shapeless._ 4 | 5 | /** 6 | * Provides a way to convert a value into an HList. 7 | * If the value is already an HList then it is returned unchanged, otherwise it's wrapped into a single-element HList. 8 | */ 9 | trait HListable[T] { 10 | type Out <: HList 11 | def apply(value: T): Out 12 | } 13 | 14 | object HListable extends LowerPriorityHListable { 15 | implicit def fromHList[T <: HList] = new HListable[T] { 16 | type Out = T 17 | def apply(value: T) = value 18 | } 19 | } 20 | 21 | private[rest] abstract class LowerPriorityHListable { 22 | implicit def fromAnyRef[T] = new HListable[T] { 23 | type Out = T :: HNil 24 | def apply(value: T) = value :: HNil 25 | } 26 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/ObjectRegistry.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import java.util.concurrent.atomic.AtomicReference 4 | import scala.annotation.tailrec 5 | 6 | private[rest] trait ObjectRegistry[K, V <: AnyRef] { 7 | private[this] val _registry = new AtomicReference(Map.empty[K, V]) 8 | 9 | @tailrec 10 | protected final def register(key: K, obj: V): obj.type = { 11 | val reg = registry 12 | val updated = reg.updated(key, obj) 13 | if (_registry.compareAndSet(reg, updated)) obj 14 | else register(key, obj) 15 | } 16 | 17 | protected def registry: Map[K, V] = _registry.get 18 | 19 | def getForKey(key: K): Option[V] = registry.get(key) 20 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/Prepender.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import shapeless._ 4 | 5 | trait Prepender[P <: HList, S <: HList] { 6 | type Out <: HList 7 | def apply(prefix: P, suffix: S): Out 8 | } 9 | 10 | object Prepender { 11 | implicit def hnilPrepend[P <: HList, S <: HNil] = new Prepender[P, S] { 12 | type Out = P 13 | def apply(prefix: P, suffix: S) = prefix 14 | } 15 | 16 | implicit def apply[P <: HList, S <: HList, Out0 <: HList](implicit prepend: PrependAux[P, S, Out0]) = 17 | new Prepender[P, S] { 18 | type Out = Out0 19 | def apply(prefix: P, suffix: S): Out = prepend(prefix, suffix) 20 | } 21 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/RestSettings.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | import com.typesafe.config.Config 6 | import akka.actor.ActorRefFactory 7 | import spray.util._ 8 | 9 | import scala.concurrent.duration.FiniteDuration 10 | 11 | /** 12 | * Created by Simon on 2014/7/12 13 | */ 14 | case class RestSettings(verboseErrorMessages: Boolean, 15 | users: Config, 16 | renderVanityFooter: Boolean) 17 | 18 | object RestSettings extends SettingsCompanion[RestSettings]("zzb.rest") { 19 | def fromSubConfig(c: Config) = apply( 20 | c getBoolean "verbose-error-messages", 21 | c getConfig "users", 22 | c getBoolean "render-vanity-footer") 23 | 24 | implicit def default(implicit refFactory: ActorRefFactory) = 25 | apply(actorSystem) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/Route.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import shapeless.HList 4 | 5 | object Route { 6 | def apply(f: Route): Route = f 7 | 8 | /** 9 | * Converts the route into a directive that never passes the request to its inner route 10 | * (and always returns its underlying route). 11 | */ 12 | def toDirective[L <: HList](route: Route): Directive[L] = new Directive[L] { 13 | def happly(f: L ⇒ Route) = route 14 | } 15 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/RouteConcatenation.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: Simon Xiao 6 | * Date: 14-3-1 7 | * Time: 上午10:36 8 | * Copyright baoxian.com 2012~2020 9 | */ 10 | trait RouteConcatenation { 11 | 12 | implicit def pimpRouteWithConcatenation(route: Route) = new RouteConcatenation(route: Route) 13 | 14 | class RouteConcatenation(route: Route) { 15 | 16 | /** 17 | * Returns a Route that chains two Routes. If the first Route rejects the request the second route is given a 18 | * chance to act upon the request. 19 | */ 20 | def ~(other: Route): Route = { ctx ⇒ 21 | route { 22 | ctx.withRejectionHandling { rejections ⇒ 23 | other(ctx.withRejectionsMapped(rejections1 ⇒ rejections ++ rejections1)) 24 | } 25 | } 26 | } 27 | } 28 | 29 | } 30 | 31 | object RouteConcatenation extends RouteConcatenation -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/StandardRoute.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import shapeless.HList 4 | 5 | /** 6 | * A Route that can be implicitly converted into a Directive (fitting any signature). 7 | */ 8 | abstract class StandardRoute extends Route { 9 | def toDirective[L <: HList]: Directive[L] = StandardRoute.toDirective(this) 10 | } 11 | 12 | object StandardRoute { 13 | def apply(route: Route): StandardRoute = route match { 14 | case x: StandardRoute ⇒ x 15 | case x ⇒ new StandardRoute { def apply(ctx: RestReqContext): Unit = { x(ctx) } } 16 | } 17 | 18 | /** 19 | * Converts the route into a directive that never passes the request to its inner route 20 | * (and always returns its underlying route). 21 | */ 22 | implicit def toDirective[L <: HList](route: Route): Directive[L] = Route.toDirective(route) 23 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/TransformerPipelineSupport.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import scala.concurrent.{ ExecutionContext, Future } 4 | import akka.event.{ Logging, LoggingAdapter } 5 | 6 | trait TransformerPipelineSupport { 7 | 8 | def logValue[T](log: LoggingAdapter, level: Logging.LogLevel = Logging.DebugLevel): T ⇒ T = 9 | logValue { value ⇒ log.log(level, value.toString) } 10 | 11 | def logValue[T](logFun: T ⇒ Unit): T ⇒ T = { response ⇒ 12 | logFun(response) 13 | response 14 | } 15 | 16 | implicit class WithTransformation[A](value: A) { 17 | def ~>[B](f: A ⇒ B): B = f(value) 18 | } 19 | 20 | implicit class WithTransformerConcatenation[A, B](f: A ⇒ B) extends (A ⇒ B) { 21 | def apply(input: A) = f(input) 22 | def ~>[AA, BB, R](g: AA ⇒ BB)(implicit aux: TransformerAux[A, B, AA, BB, R]) = 23 | new WithTransformerConcatenation[A, R](aux(f, g)) 24 | } 25 | } 26 | 27 | object TransformerPipelineSupport extends TransformerPipelineSupport 28 | 29 | trait TransformerAux[A, B, AA, BB, R] { 30 | def apply(f: A ⇒ B, g: AA ⇒ BB): A ⇒ R 31 | } 32 | 33 | object TransformerAux { 34 | implicit def aux1[A, B, C] = new TransformerAux[A, B, B, C, C] { 35 | def apply(f: A ⇒ B, g: B ⇒ C): A ⇒ C = f andThen g 36 | } 37 | implicit def aux2[A, B, C](implicit ec: ExecutionContext) = 38 | new TransformerAux[A, Future[B], B, C, Future[C]] { 39 | def apply(f: A ⇒ Future[B], g: B ⇒ C): A ⇒ Future[C] = f(_).map(g) 40 | } 41 | implicit def aux3[A, B, C](implicit ec: ExecutionContext) = 42 | new TransformerAux[A, Future[B], B, Future[C], Future[C]] { 43 | def apply(f: A ⇒ Future[B], g: B ⇒ Future[C]): A ⇒ Future[C] = f(_).flatMap(g) 44 | } 45 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/authentication/UserPassAuthenticator.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.authentication 2 | 3 | /** 4 | * Created by Simon on 2014/7/12 5 | */ 6 | import com.typesafe.config.{ Config, ConfigException } 7 | import spray.util.pimpString_ 8 | 9 | import scala.concurrent.Future 10 | 11 | object UserPassAuthenticator { 12 | 13 | def apply[T](f: UserPassAuthenticator[T]) = f 14 | 15 | /** 16 | * Creats a UserPassAuthenticator that uses plain-text username/password definitions from a given 17 | * spray/akka config file section for authentication. The config section should look like this: 18 | * {{{ 19 | * zzb.rest.users { 20 | * username = "password" 21 | * ... 22 | * } 23 | * }}} 24 | */ 25 | def fromConfig[T](config: Config)(createUser: UserPass ⇒ T): UserPassAuthenticator[T] = 26 | userPassOption ⇒ 27 | Future.successful { 28 | userPassOption.flatMap { userPass ⇒ 29 | try { 30 | val pw = config.getString(userPass.user) 31 | if (pw secure_== userPass.pass) Some(createUser(userPass)) else None 32 | } catch { 33 | case _: ConfigException ⇒ None 34 | } 35 | } 36 | } 37 | } 38 | 39 | //object CachedUserPassAuthenticator { 40 | // /** 41 | // * Creates a wrapper around an UserPassAuthenticator providing authentication lookup caching using the given cache. 42 | // * Note that you need to manually add a dependency to the spray-caching module in order to be able to use this method. 43 | // */ 44 | // def apply[T](inner: UserPassAuthenticator[T], cache: Cache[Option[T]] = LruCache[Option[T]]())(implicit ec: ExecutionContext): UserPassAuthenticator[T] = 45 | // userPassOption ⇒ 46 | // cache(userPassOption) { 47 | // inner(userPassOption) 48 | // } 49 | //} 50 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/authentication/package.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import scala.concurrent.Future 4 | 5 | /** 6 | * Created by Simon on 2014/7/12 7 | */ 8 | package object authentication { 9 | //# auth-types 10 | type Authentication[T] = Either[Rejection, T] 11 | type ContextAuthenticator[T] = RestReqContext ⇒ Future[Authentication[T]] 12 | //# 13 | //# user-pass-authenticator 14 | type UserPassAuthenticator[T] = Option[UserPass] ⇒ Future[Option[T]] 15 | //# 16 | } 17 | 18 | package authentication { 19 | 20 | /** 21 | * Simple case class model of a username/password combination. 22 | */ 23 | case class UserPass(user: String, pass: String) 24 | 25 | /** 26 | * A very basic user context object. 27 | * In your application you probably want to use some more specific custom class. 28 | */ 29 | case class BasicUserContext(username: String) 30 | 31 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/directives/NameReceptacle.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.directives 2 | 3 | import zzb.rest.unmarshalling.{ FromStringOptionDeserializer ⇒ FSOD, Deserializer } 4 | 5 | trait ToNameReceptaclePimps { 6 | implicit def symbol2NR(symbol: Symbol) = new NameReceptacle[String](symbol.name) 7 | implicit def string2NR(string: String) = new NameReceptacle[String](string) 8 | } 9 | 10 | case class NameReceptacle[A](name: String) { 11 | def as[B] = NameReceptacle[B](name) 12 | def as[B](deserializer: FSOD[B]) = NameDeserializerReceptacle(name, deserializer) 13 | def ? = as[Option[A]] 14 | def ?[B](default: B) = NameDefaultReceptacle(name, default) 15 | def ![B](requiredValue: B) = RequiredValueReceptacle(name, requiredValue) 16 | } 17 | 18 | case class NameDeserializerReceptacle[A](name: String, deserializer: FSOD[A]) { 19 | def ? = NameDeserializerReceptacle(name, Deserializer.liftToTargetOption(deserializer)) 20 | def ?(default: A) = NameDeserializerDefaultReceptacle(name, deserializer, default) 21 | def !(requiredValue: A) = RequiredValueDeserializerReceptacle(name, deserializer, requiredValue) 22 | } 23 | 24 | case class NameDefaultReceptacle[A](name: String, default: A) 25 | 26 | case class RequiredValueReceptacle[A](name: String, requiredValue: A) 27 | 28 | case class NameDeserializerDefaultReceptacle[A](name: String, deserializer: FSOD[A], default: A) 29 | 30 | case class RequiredValueDeserializerReceptacle[A](name: String, deserializer: FSOD[A], requiredValue: A) -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/directives/RouteDirectives.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package directives 3 | 4 | import spray.http.Uri 5 | import zzb.rest.marshalling.ToResponseMarshallable 6 | import akka.actor.{ ActorContext, ActorRef } 7 | 8 | trait RouteDirectives { 9 | 10 | /** 11 | * Rejects the request with an empty set of rejections. 12 | */ 13 | def reject: StandardRoute = RouteDirectives._reject 14 | 15 | /** 16 | * Rejects the request with the given rejections. 17 | */ 18 | def reject(rejections: Rejection*): StandardRoute = new StandardRoute { 19 | def apply(ctx: RestReqContext): Unit = ctx.reject(rejections: _*) 20 | } 21 | 22 | // /** 23 | // * Completes the request with redirection response of the given type to the given URI. 24 | // */ 25 | // def redirect(uri: Uri, redirectionType: Redirection): StandardRoute = new StandardRoute { 26 | // def apply(ctx: RestReqContext): Unit = ctx.redirect(uri, redirectionType) 27 | // } 28 | 29 | /** 30 | * Completes the request using the given arguments. 31 | */ 32 | def complete: (⇒ ToResponseMarshallable) ⇒ StandardRoute = marshallable ⇒ new StandardRoute { 33 | def apply(ctx: RestReqContext): Unit = ctx.complete(marshallable) 34 | } 35 | 36 | /** 37 | * Bubbles the given error up the response chain, where it is dealt with by the closest `handleExceptions` 38 | * directive and its ExceptionHandler. 39 | */ 40 | def failWith(error: Throwable): StandardRoute = new StandardRoute { 41 | def apply(ctx: RestReqContext): Unit = ctx.failWith(error) 42 | } 43 | 44 | } 45 | 46 | object RouteDirectives extends RouteDirectives { 47 | private val _reject: StandardRoute = new StandardRoute { 48 | def apply(ctx: RestReqContext): Unit = ctx.reject() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/directives/SecurityDirectives.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package directives 3 | 4 | import scala.concurrent.{ ExecutionContext, Future } 5 | import shapeless.HNil 6 | import zzb.rest.authentication._ 7 | import BasicDirectives._ 8 | import FutureDirectives._ 9 | import MiscDirectives._ 10 | import RouteDirectives._ 11 | 12 | /** 13 | * Created by Simon on 2014/7/12 14 | */ 15 | trait SecurityDirectives { 16 | 17 | /** 18 | * Wraps its inner Route with authentication support. 19 | * Can be called either with a ``Future[Authentication[T]]`` or ``ContextAuthenticator[T]``. 20 | */ 21 | def authenticate[T](magnet: AuthMagnet[T]): Directive1[T] = magnet.directive 22 | 23 | /** 24 | * Applies the given authorization check to the request. 25 | * If the check fails the route is rejected with an [[zzb.rest.AuthorizationFailedRejection]]. 26 | */ 27 | def authorize(check: ⇒ Boolean): Directive0 = authorize(_ ⇒ check) 28 | 29 | /** 30 | * Applies the given authorization check to the request. 31 | * If the check fails the route is rejected with an [[zzb.rest.AuthorizationFailedRejection]]. 32 | */ 33 | def authorize(check: RestReqContext ⇒ Boolean): Directive0 = 34 | extract(check).flatMap[HNil](if (_) pass else reject(AuthorizationFailedRejection)) & 35 | cancelRejection(AuthorizationFailedRejection) 36 | } 37 | 38 | class AuthMagnet[T](authDirective: Directive1[Authentication[T]])(implicit executor: ExecutionContext) { 39 | val directive: Directive1[T] = authDirective.flatMap { 40 | case Right(user) ⇒ provide(user) 41 | case Left(rejection) ⇒ reject(rejection) 42 | } 43 | } 44 | 45 | object AuthMagnet { 46 | implicit def fromFutureAuth[T](auth: ⇒ Future[Authentication[T]])(implicit executor: ExecutionContext): AuthMagnet[T] = 47 | new AuthMagnet(onSuccess(auth)) 48 | 49 | implicit def fromContextAuthenticator[T](auth: ContextAuthenticator[T])(implicit executor: ExecutionContext): AuthMagnet[T] = 50 | new AuthMagnet(extract(auth).flatMap(onSuccess(_))) 51 | } 52 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/directives/ZzbDatatypeDirectives.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.directives 2 | 3 | import zzb.datatype._ 4 | import zzb.rest._ 5 | import zzb.rest.unmarshalling._ 6 | 7 | /** 8 | * Created by Simon on 2014/6/24 9 | */ 10 | trait ZzbDatatypeDirectives { 11 | 12 | def unpack(dt: DataType[Any], failHandler: () ⇒ Unit = null) = new Deserializer[RestRequest, ValuePack[Any]] { 13 | def apply(req: RestRequest): unmarshalling.Deserialized[ValuePack[Any]] = req.entity match { 14 | case RestEntity.NonEmpty(v) ⇒ 15 | val p = dt.AnyToPack(v) 16 | p match { 17 | case Some(pk) ⇒ Right(pk) 18 | case None ⇒ 19 | if (failHandler != null) failHandler() 20 | Left(MalformedContent("The request content was malformed")) 21 | } 22 | case _ ⇒ 23 | if (failHandler != null) failHandler() 24 | Left(ContentExpected) 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/http2akka/HttpApi.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.http2akka 2 | 3 | import akka.event.{ BusLogging, LogSource, DiagnosticLoggingAdapter } 4 | import spray.routing._ 5 | import akka.util.Timeout 6 | import akka.actor.ActorSystem 7 | import zzb.util.MdcLoggingContext 8 | import scala.reflect.macros.Context 9 | import language.experimental.macros 10 | 11 | /** 12 | * Created by Simon on 2014/6/9 13 | */ 14 | trait HttpApi extends Directives with Http2AkkaDirectives { 15 | 16 | implicit val timeout: Timeout 17 | implicit val system: ActorSystem 18 | 19 | def logName: String 20 | 21 | implicit lazy val log = logFactory(logName) 22 | 23 | implicit lazy val mdcLogCtx = MdcLoggingContext.fromAdapter(log) 24 | 25 | protected def logFactory(logName: String): DiagnosticLoggingAdapter = { 26 | 27 | val (str, clazz) = LogSource(logName) 28 | new BusLogging(system.eventStream, str, clazz) with DiagnosticLoggingAdapter 29 | } 30 | 31 | def api: Route 32 | } 33 | 34 | object HttpApi { 35 | def apply[T <: HttpApi](implicit sys: ActorSystem, time: Timeout): Route = macro api_generator[T] 36 | 37 | def api_generator[T <: HttpApi](c: Context)(sys: c.Expr[ActorSystem], time: c.Expr[Timeout])(implicit T: c.WeakTypeTag[T]) = { 38 | import c.universe._ 39 | 40 | c.Expr[T]( 41 | q""" new $T { 42 | implicit val system = ${sys.tree} 43 | implicit val timeout = ${time.tree} 44 | }.api """) 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/marshalling/BasicToResponseMarshallers.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package marshalling 3 | 4 | import akka.actor.ActorRef 5 | 6 | trait BasicToResponseMarshallers { 7 | implicit def fromResponse: ToResponseMarshaller[RestResponse] = ToResponseMarshaller((value, ctx) ⇒ ctx.marshalTo(value)) 8 | 9 | // No implicit conversion to StatusCode allowed here: in `complete(i: Int)`, `i` shouldn't be marshalled 10 | // as a StatusCode 11 | implicit def fromStatusCode: ToResponseMarshaller[StatusCode] = 12 | fromResponse.compose(s ⇒ RestResponse(status = s, entity = s.defaultMessage)) 13 | 14 | implicit def fromStatusCodeAndT[S, T](implicit sConv: S ⇒ StatusCode, tMarshaller: Marshaller[T]): ToResponseMarshaller[(S, T)] = 15 | fromStatusCodeAndHeadersAndT[T].compose { case (s, t) ⇒ (sConv(s), Nil, t) } 16 | 17 | implicit def fromStatusCodeConvertibleAndHeadersAndT[S, T](implicit sConv: S ⇒ StatusCode, tMarshaller: Marshaller[T]): ToResponseMarshaller[(S, Seq[RestHeader], T)] = 18 | fromStatusCodeAndHeadersAndT[T].compose { case (s, headers, t) ⇒ (sConv(s), headers, t) } 19 | 20 | implicit def fromStatusCodeAndHeadersAndT[T](implicit tMarshaller: Marshaller[T]): ToResponseMarshaller[(StatusCode, Seq[RestHeader], T)] = 21 | new ToResponseMarshaller[(StatusCode, Seq[RestHeader], T)] { 22 | def apply(value: (StatusCode, Seq[RestHeader], T), ctx: ToResponseMarshallingContext): Unit = { 23 | val status = value._1 24 | val headers = value._2 25 | val mCtx = new MarshallingContext { 26 | //def tryAccept(contentTypes: Seq[ContentType]): Option[ContentType] = ctx.tryAccept(contentTypes) 27 | def handleError(error: Throwable): Unit = ctx.handleError(error) 28 | def marshalTo(entity: RestEntity, hs: RestHeader*): Unit = 29 | ctx.marshalTo(RestResponse(status, entity, (headers ++ hs).toList)) 30 | //def rejectMarshalling(supported: Seq[ContentType]): Unit = ctx.rejectMarshalling(supported) 31 | //def startChunkedMessage(entity: RestEntity, ack: Option[Any], hs: Seq[RestHeader])(implicit sender: ActorRef): ActorRef = 32 | // ctx.startChunkedMessage(RestResponse(status, entity, (headers ++ hs).toList), ack) 33 | } 34 | tMarshaller(value._3, mCtx) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/marshalling/MetaToResponseMarshallers.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package marshalling 3 | 4 | import scala.concurrent.{ ExecutionContext, Future } 5 | import scala.util.{ Failure, Success, Try } 6 | //import spray.http._ 7 | 8 | trait MetaToResponseMarshallers { 9 | 10 | implicit def optionMarshaller[T](implicit m: ToResponseMarshaller[T]) = 11 | ToResponseMarshaller[Option[T]] { (value, ctx) ⇒ 12 | value match { 13 | case Some(v) ⇒ m(v, ctx) 14 | case None ⇒ ctx.marshalTo(RestResponse(StatusCodes.NotFound)) 15 | } 16 | } 17 | 18 | implicit def eitherMarshaller[A, B](implicit ma: ToResponseMarshaller[A], mb: ToResponseMarshaller[B]) = 19 | ToResponseMarshaller[Either[A, B]] { (value, ctx) ⇒ 20 | value match { 21 | case Left(a) ⇒ ma(a, ctx) 22 | case Right(b) ⇒ mb(b, ctx) 23 | } 24 | } 25 | 26 | implicit def futureMarshaller[T](implicit m: ToResponseMarshaller[T], ec: ExecutionContext) = 27 | ToResponseMarshaller[Future[T]] { (value, ctx) ⇒ 28 | value.onComplete { 29 | case Success(v) ⇒ m(v, ctx) 30 | case Failure(error) ⇒ ctx.handleError(error) 31 | } 32 | } 33 | 34 | implicit def tryMarshaller[T](implicit m: ToResponseMarshaller[T]) = 35 | ToResponseMarshaller[Try[T]] { (value, ctx) ⇒ 36 | value match { 37 | case Success(v) ⇒ m(v, ctx) 38 | case Failure(t) ⇒ ctx.handleError(t) 39 | } 40 | } 41 | } 42 | 43 | object MetaToResponseMarshallers extends MetaToResponseMarshallers -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/marshalling/package.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import scala.concurrent.duration._ 4 | import scala.util.control.NonFatal 5 | import akka.util.Timeout 6 | import akka.actor.ActorRefFactory 7 | import spray.util.identityFunc 8 | 9 | package object marshalling { 10 | 11 | def marshal[T](value: T, ctx: CollectingMarshallingContext = new CollectingMarshallingContext)(implicit marshaller: Marshaller[T], actorRefFactory: ActorRefFactory = null, 12 | timeout: Timeout = 1.second): Either[Throwable, RestEntity] = 13 | marshalToEntityAndHeaders(value, ctx).right.map(_._1) 14 | 15 | def marshalToEntityAndHeaders[T](value: T, ctx: CollectingMarshallingContext = new CollectingMarshallingContext)(implicit marshaller: Marshaller[T], actorRefFactory: ActorRefFactory = null, 16 | timeout: Timeout = 1.second): Either[Throwable, (RestEntity, Seq[RestHeader])] = { 17 | marshalCollecting(value, ctx) 18 | ctx.entityAndHeaders match { 19 | case Some(value) ⇒ Right(value) 20 | case None ⇒ 21 | Left(ctx.error.getOrElse(new RuntimeException("Marshaller for %s did not produce result" format value))) 22 | } 23 | } 24 | 25 | def marshalCollecting[T](value: T, ctx: CollectingMarshallingContext)(implicit marshaller: Marshaller[T], actorRefFactory: ActorRefFactory = null, 26 | timeout: Timeout = 1.second): Unit = 27 | try { 28 | marshaller(value, ctx) 29 | ctx.awaitResults 30 | } catch { 31 | case NonFatal(e) ⇒ ctx.handleError(e) 32 | } 33 | 34 | def marshalUnsafe[T: Marshaller](value: T): RestEntity = marshal(value).fold(throw _, identityFunc) 35 | } 36 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/package.scala: -------------------------------------------------------------------------------- 1 | package zzb 2 | 3 | import spray.http.{ HttpHeaders, HttpHeader, HttpMethods, HttpMethod } 4 | import shapeless._ 5 | import spray.routing.PathMatcher 6 | 7 | /** 8 | * Created with IntelliJ IDEA. 9 | * User: Simon Xiao 10 | * Date: 14-2-27 11 | * Time: 下午2:05 12 | * Copyright baoxian.com 2012~2020 13 | */ 14 | package object rest { 15 | 16 | type RestMethod = HttpMethod 17 | val RestMethods = HttpMethods 18 | type RestHeader = HttpHeader 19 | val RestHeader = HttpHeader 20 | val RestHeaders = HttpHeaders 21 | type StatusCode = spray.http.StatusCode 22 | val StatusCodes = spray.http.StatusCodes 23 | type RestData = Any 24 | 25 | type Route = RestReqContext ⇒ Unit 26 | type RouteGenerator[T] = T ⇒ Route 27 | type Directive0 = Directive[HNil] 28 | type Directive1[T] = Directive[T :: HNil] 29 | type PathMatcher0 = PathMatcher[HNil] 30 | type PathMatcher1[T] = PathMatcher[T :: HNil] 31 | 32 | val PostmanName = "RestRequestPostman" 33 | } 34 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/unmarshalling/BasicUnmarshallers.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package unmarshalling 3 | 4 | import java.nio.ByteBuffer 5 | import java.io.{ InputStreamReader, ByteArrayInputStream } 6 | import scala.xml.{ XML, NodeSeq } 7 | 8 | trait BasicUnmarshallers { 9 | 10 | // implicit val ByteArrayUnmarshaller = new Unmarshaller[Array[Byte]] { 11 | // def apply(entity: RestEntity) = Right(entity.data.toByteArray) 12 | // } 13 | // 14 | // implicit val CharArrayUnmarshaller = new Unmarshaller[Array[Char]] { 15 | // def apply(entity: RestEntity) = Right { // we can convert anything to a char array 16 | // entity match { 17 | // case RestEntity.NonEmpty(contentType, data) ⇒ 18 | // val charBuffer = contentType.charset.nioCharset.decode(ByteBuffer.wrap(data.toByteArray)) 19 | // val array = new Array[Char](charBuffer.length()) 20 | // charBuffer.get(array) 21 | // array 22 | // case RestEntity.Empty ⇒ Array.empty[Char] 23 | // } 24 | // } 25 | // } 26 | 27 | implicit val StringUnmarshaller = new Unmarshaller[String] { 28 | def apply(entity: RestEntity) = Right(entity.asString) 29 | } 30 | 31 | // //# nodeseq-unmarshaller 32 | // implicit val NodeSeqUnmarshaller = 33 | // Unmarshaller[NodeSeq](`text/xml`, `application/xml`, `text/html`, `application/xhtml+xml`) { 34 | // case RestEntity.NonEmpty(contentType, data) ⇒ 35 | // XML.load(new InputStreamReader(new ByteArrayInputStream(data.toByteArray), contentType.charset.nioCharset)) 36 | // case RestEntity.Empty ⇒ NodeSeq.Empty 37 | // } 38 | // //# 39 | } 40 | 41 | object BasicUnmarshallers extends BasicUnmarshallers 42 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/unmarshalling/DeserializationError.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.unmarshalling 2 | 3 | sealed trait DeserializationError 4 | 5 | case object ContentExpected extends DeserializationError 6 | 7 | case class MalformedContent(errorMessage: String, cause: Option[Throwable] = None) extends DeserializationError 8 | 9 | object MalformedContent { 10 | def apply(errorMessage: String, cause: Throwable): MalformedContent = new MalformedContent(errorMessage, Some(cause)) 11 | } 12 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/unmarshalling/Deserializer.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.unmarshalling 2 | import scala.util.control.NonFatal 3 | 4 | trait Deserializer[A, B] extends (A ⇒ Deserialized[B]) { self ⇒ 5 | 6 | def withDefaultValue(defaultValue: B): Deserializer[A, B] = 7 | new Deserializer[A, B] { 8 | def apply(value: A) = self(value).left.flatMap { 9 | case ContentExpected ⇒ Right(defaultValue) 10 | case error ⇒ Left(error) 11 | } 12 | } 13 | } 14 | 15 | object Deserializer extends DeserializerLowerPriorityImplicits 16 | with BasicUnmarshallers 17 | with UnmarshallerLifting 18 | with FromStringDeserializers { 19 | //with FormDataUnmarshallers { 20 | 21 | implicit def fromFunction2Converter[A, B](implicit f: A ⇒ B) = 22 | new Deserializer[A, B] { 23 | def apply(a: A) = { 24 | try Right(f(a)) 25 | catch { 26 | case NonFatal(ex) ⇒ Left(MalformedContent(ex.toString, ex)) 27 | } 28 | } 29 | } 30 | 31 | implicit def liftToTargetOption[A, B](implicit converter: Deserializer[A, B]) = 32 | new Deserializer[A, Option[B]] { 33 | def apply(value: A) = converter(value) match { 34 | case Right(a) ⇒ Right(Some(a)) 35 | case Left(ContentExpected) ⇒ Right(None) 36 | case Left(error) ⇒ Left(error) 37 | } 38 | } 39 | } 40 | 41 | private[unmarshalling] abstract class DeserializerLowerPriorityImplicits { 42 | implicit def lift2SourceOption[A, B](converter: Deserializer[A, B]) = liftToSourceOption(converter) 43 | implicit def liftToSourceOption[A, B](implicit converter: Deserializer[A, B]) = { 44 | new Deserializer[Option[A], B] { 45 | def apply(value: Option[A]) = value match { 46 | case Some(a) ⇒ converter(a) 47 | case None ⇒ Left(ContentExpected) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/unmarshalling/SimpleUnmarshaller.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package unmarshalling 3 | 4 | import scala.util.control.NonFatal 5 | import spray.util._ 6 | 7 | abstract class SimpleUnmarshaller[T] extends Unmarshaller[T] { 8 | //val canUnmarshalFrom: Seq[ContentTypeRange] 9 | 10 | def apply(entity: RestEntity): Deserialized[T] = 11 | entity match { 12 | case RestEntity.Empty ⇒ unmarshal(entity) 13 | case _ ⇒ unmarshal(entity) 14 | } 15 | 16 | protected def unmarshal(entity: RestEntity): Either[DeserializationError, T] 17 | 18 | /** 19 | * Helper method for turning exceptions occuring during evaluation of the named parameter into 20 | * [[zzb.rest.unmarshalling.MalformedContent]] instances. 21 | */ 22 | protected def protect(f: ⇒ T): Either[DeserializationError, T] = 23 | try Right(f) 24 | catch { 25 | case NonFatal(ex) ⇒ Left(MalformedContent(ex.getMessage.nullAsEmpty, ex)) 26 | } 27 | } -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/unmarshalling/Unmarshaller.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package unmarshalling 3 | 4 | object Unmarshaller { 5 | // format: OFF 6 | def apply[T](f: PartialFunction[RestEntity, T]): Unmarshaller[T] = // unmarshaller-apply 7 | // format: ON 8 | new SimpleUnmarshaller[T] { 9 | def unmarshal(entity: RestEntity) = 10 | if (f.isDefinedAt(entity)) protect(f(entity)) else Left(ContentExpected) 11 | } 12 | 13 | def delegate[A, B](f: A ⇒ B)(implicit mb: Unmarshaller[A]): Unmarshaller[B] = 14 | new SimpleUnmarshaller[B] { 15 | def unmarshal(entity: RestEntity) = mb(entity).right.flatMap(a ⇒ protect(f(a))) 16 | } 17 | 18 | def forNonEmpty[T](implicit um: Unmarshaller[T]): Unmarshaller[T] = 19 | new Unmarshaller[T] { 20 | def apply(entity: RestEntity) = if (entity.isEmpty) Left(ContentExpected) else um(entity) 21 | } 22 | 23 | def unmarshaller[T](implicit um: Unmarshaller[T]) = um 24 | 25 | def unmarshal[T: Unmarshaller](entity: RestEntity): Deserialized[T] = unmarshaller.apply(entity) 26 | def unmarshalUnsafe[T: Unmarshaller](entity: RestEntity): T = unmarshaller.apply(entity) match { 27 | case Right(value) ⇒ value 28 | case Left(error) ⇒ sys.error(error.toString) 29 | } 30 | 31 | // def oneOf[T](unmarshallers: Unmarshaller[T]*): Unmarshaller[T] = 32 | // new Unmarshaller[T] { 33 | // def apply(entity: RestEntity): Deserialized[T] = { 34 | // def tryNext(unmarshallers: Seq[Unmarshaller[T]]): Deserialized[T] = unmarshallers match { 35 | // case head +: tail ⇒ head(entity).left.flatMap(_ ⇒ tryNext(tail)) 36 | // case Nil ⇒ 37 | // def tpeString = entity match { 38 | // case RestEntity.NonEmpty(tpe, _) ⇒ tpe.value 39 | // case RestEntity.Empty ⇒ "an empty entity" 40 | // } 41 | // Left(UnsupportedContentType("Can't unmarshal from " + tpeString)) 42 | // } 43 | // tryNext(unmarshallers) 44 | // } 45 | // } 46 | } 47 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/unmarshalling/UnmarshallerLifting.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | package unmarshalling 3 | 4 | trait UnmarshallerLifting { 5 | 6 | implicit def fromRequestUnmarshaller[T](implicit um: FromMessageUnmarshaller[T]): FromRequestUnmarshaller[T] = 7 | new FromRequestUnmarshaller[T] { 8 | def apply(request: RestRequest): Deserialized[T] = um(request) 9 | } 10 | 11 | implicit def fromResponseUnmarshaller[T](implicit um: FromMessageUnmarshaller[T]): FromResponseUnmarshaller[T] = 12 | new FromResponseUnmarshaller[T] { 13 | def apply(response: RestResponse): Deserialized[T] = um(response) 14 | } 15 | 16 | implicit def fromMessageUnmarshaller[T](implicit um: Unmarshaller[T]): FromMessageUnmarshaller[T] = 17 | new FromMessageUnmarshaller[T] { 18 | def apply(msg: RestMessage): Deserialized[T] = um(msg.entity) 19 | } 20 | } 21 | 22 | object UnmarshallerLifting extends UnmarshallerLifting 23 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/unmarshalling/package.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | package object unmarshalling { 4 | 5 | type Deserialized[T] = Either[DeserializationError, T] 6 | type FromStringDeserializer[T] = Deserializer[String, T] 7 | type FromStringOptionDeserializer[T] = Deserializer[Option[String], T] 8 | type Unmarshaller[T] = Deserializer[RestEntity, T] 9 | type FromEntityOptionUnmarshaller[T] = Deserializer[Option[RestEntity], T] 10 | //type FromBodyPartOptionUnmarshaller[T] = Deserializer[Option[BodyPart], T] 11 | type FromMessageUnmarshaller[T] = Deserializer[RestMessage, T] // source-quote-FromMessageUnmarshaller 12 | type FromRequestUnmarshaller[T] = Deserializer[RestRequest, T] // source-quote-FromRequestUnmarshaller 13 | type FromResponseUnmarshaller[T] = Deserializer[RestResponse, T] // source-quote-FromResponseUnmarshaller 14 | 15 | //implicit def formFieldExtractor(form: HttpForm) = FormFieldExtractor(form) 16 | //implicit def pimpBodyPart(bodyPart: BodyPart) = new PimpedRestEntity(bodyPart.entity) 17 | 18 | implicit class PimpedRestEntity(entity: RestEntity) { 19 | def as[T](implicit unmarshaller: Unmarshaller[T]): Deserialized[T] = unmarshaller(entity) 20 | } 21 | 22 | implicit class PimpedRestMessage(msg: RestMessage) { 23 | def as[T](implicit unmarshaller: FromMessageUnmarshaller[T]): Deserialized[T] = unmarshaller(msg) 24 | } 25 | 26 | implicit class PimpedRestRequest(request: RestRequest) { 27 | def as[T](implicit unmarshaller: FromRequestUnmarshaller[T]): Deserialized[T] = unmarshaller(request) 28 | } 29 | 30 | implicit class PimpedRestResponse(response: RestResponse) { 31 | def as[T](implicit unmarshaller: FromResponseUnmarshaller[T]): Deserialized[T] = unmarshaller(response) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/util/IdleReleasable.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.util 2 | 3 | import spray.http.DateTime 4 | import akka.actor.{ Actor, PoisonPill } 5 | 6 | /** 7 | * Created by Simon on 2014/3/27 8 | */ 9 | trait IdleReleasable extends Actor { 10 | this: StatableActor ⇒ 11 | 12 | //seconds 13 | val maxIdle: Int 14 | 15 | def preIdleRelease: Boolean 16 | 17 | val initDelay = maxIdle / 3 18 | val interval = maxIdle / 3 19 | 20 | import scala.concurrent.duration._ 21 | import context.dispatcher 22 | 23 | val cancelCheck = context.system.scheduler.schedule(initDelay.seconds, interval.seconds, self, RequestStat) 24 | 25 | abstract override def receive: Receive = { 26 | case MessageStat(start, first, last, count) ⇒ 27 | val lastTime = if (last.isEmpty) start else last.get 28 | 29 | val diff = DateTime.now.clicks - lastTime.clicks 30 | 31 | if (diff > maxIdle * 1000 && preIdleRelease) { 32 | cancelCheck.cancel() 33 | self ! PoisonPill 34 | 35 | } 36 | 37 | case msg ⇒ super.receive(msg) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /zzb-rest/src/main/scala/zzb/rest/util/Statable.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.util 2 | 3 | import spray.http.DateTime 4 | import akka.actor.Actor 5 | import spray.json._ 6 | 7 | /** 8 | * Created by Simon on 2014/3/25 9 | */ 10 | 11 | /** 12 | * 给Actor 增加消息统计的功能 13 | */ 14 | trait StatableActor extends Actor { 15 | /** 16 | * actor 启动时间 17 | */ 18 | val startTime = DateTime.now 19 | 20 | /** 21 | * 收到第一个消息的时间 22 | */ 23 | var firstMessageTime: Option[DateTime] = None 24 | /** 25 | * 最近消息的时间 26 | */ 27 | var lastMessageTime: Option[DateTime] = None 28 | /** 29 | * 消息数量 30 | */ 31 | var messageCount: Long = 0 32 | 33 | abstract override def receive: Receive = { 34 | case RequestStat ⇒ 35 | sender ! stat 36 | case msg: MessageStat ⇒ 37 | super.receive(msg) 38 | case msg ⇒ 39 | val nowTime = DateTime.now 40 | if (firstMessageTime.isEmpty) firstMessageTime = Some(nowTime) 41 | lastMessageTime = Some(nowTime) 42 | messageCount = messageCount + 1 43 | super.receive(msg) 44 | 45 | } 46 | 47 | def stat = MessageStat(startTime, firstMessageTime, lastMessageTime, messageCount) 48 | 49 | //def msgReceive: Receive 50 | 51 | } 52 | 53 | case class MessageStat(start: DateTime, first: Option[DateTime], last: Option[DateTime], count: Long) 54 | 55 | object MessageStat extends DefaultJsonProtocol { 56 | 57 | implicit val dateTimeFormat = new RootJsonFormat[DateTime] { 58 | def write(c: DateTime) = 59 | JsString(c.toIsoDateTimeString) 60 | 61 | def read(value: JsValue) = DateTime.fromIsoDateTimeString(value.toString()).get 62 | 63 | } 64 | 65 | implicit val statFormat = jsonFormat4(MessageStat.apply) 66 | } 67 | 68 | case object RequestStat 69 | -------------------------------------------------------------------------------- /zzb-rest/src/multi-jvm/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %date{ISO8601} %-5level %logger{16} [t:%-3.3X{sourceThread}][h:%-3.5X{herit}][p:%-3.5X{pid}] - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /zzb-rest/src/multi-jvm/scala/zzb/mvm/rest/DataEntityMultiNodeSpec.scala: -------------------------------------------------------------------------------- 1 | //package zzb.rest.mvm 2 | // 3 | //import akka.testkit.ImplicitSender 4 | //import akka.remote.testkit.{MultiNodeSpec, MultiNodeConfig} 5 | //import akka.actor.{ActorIdentity, Identify} 6 | // 7 | // 8 | ///** 9 | //* Created by Simon on 2014/5/26 10 | //*/ 11 | //object DataEntityMultiNodeSpec extends MultiNodeConfig { 12 | // commonConfig(debugConfig(on = false)) 13 | // 14 | // val master = role("master") 15 | // val slave = role("slave") 16 | // 17 | // testTransport(on = true) 18 | //} 19 | // 20 | //class DataEntityNodeSpec extends MultiNodeSpec(DataEntityMultiNodeSpec) with STMultiNodeSpec with ImplicitSender { 21 | // override def initialParticipants: Int = 2 22 | // 23 | // import DataEntityMultiNodeSpec._ 24 | // 25 | // lazy val echo = { 26 | // system.actorSelection(node(master) / "user" / "echo") ! Identify(None) 27 | // expectMsgType[ActorIdentity].ref.get 28 | // } 29 | // 30 | // "sss" must { 31 | // "ddd" taggedAs LongRunningTest in { 32 | // 33 | // } 34 | // } 35 | //} 36 | // 37 | // 38 | //class DataEntityMultiNode1 extends DataEntityNodeSpec{ 39 | // 40 | //} 41 | // 42 | //class DataEntityMultiNode2 extends DataEntityNodeSpec{ 43 | // 44 | //} 45 | // 46 | // 47 | // 48 | -------------------------------------------------------------------------------- /zzb-rest/src/multi-jvm/scala/zzb/mvm/rest/STMultiNodeSpec.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.mvm 2 | 3 | /** 4 | * Created by Simon on 2014/5/26 5 | */ 6 | import org.scalatest.{ BeforeAndAfterAll, WordSpec } 7 | import org.scalatest.MustMatchers 8 | import akka.remote.testkit.MultiNodeSpecCallbacks 9 | import org.scalatest.Tag 10 | //#imports 11 | 12 | //#trait 13 | /** 14 | * Hooks up MultiNodeSpec with ScalaTest 15 | */ 16 | trait STMultiNodeSpec extends WordSpec 17 | with MultiNodeSpecCallbacks with MustMatchers with BeforeAndAfterAll { 18 | 19 | override def beforeAll() = multiNodeSpecBeforeAll() 20 | 21 | override def afterAll() = multiNodeSpecAfterAll() 22 | } 23 | 24 | 25 | 26 | object TimingTest extends Tag("timing") 27 | object LongRunningTest extends Tag("long-running") 28 | -------------------------------------------------------------------------------- /zzb-rest/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %date{ISO8601} %-5level %logger{16} [t:%-3.3X{sourceThread}][h:%-3.5X{herit}][p:%-3.5X{pid}] - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /zzb-rest/src/test/scala/zzb/rest/http2akka/HttpApiGenerateTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.http2akka 2 | 3 | import org.scalatest.{ FreeSpec, BeforeAndAfterAll, MustMatchers } 4 | import akka.util.Timeout 5 | import scala.concurrent.duration._ 6 | import spray.testkit.ScalatestRouteTest 7 | import spray.routing.Directives 8 | import spray.http.HttpRequest 9 | import spray.http.StatusCodes 10 | import StatusCodes._ 11 | 12 | /** 13 | * Created by Simon on 2014/6/10 14 | */ 15 | class HttpApiGenerateTest extends FreeSpec with MustMatchers with Directives with Http2AkkaDirectives with ScalatestRouteTest 16 | with BeforeAndAfterAll { 17 | override protected def afterAll() { 18 | super.afterAll() 19 | system.shutdown() 20 | } 21 | 22 | implicit val timeout = Timeout(5 seconds) 23 | 24 | val api = HttpApi[SomeApi] 25 | def doReq(request: HttpRequest) = (request ~> api).asInstanceOf[RouteResult] 26 | 27 | "ddd" - { 28 | "good" in { 29 | 30 | doReq(Get("/ok")) ~> check { 31 | status mustBe OK 32 | } 33 | doReq(Get("/404")) ~> check { 34 | status mustBe NotFound 35 | } 36 | } 37 | } 38 | 39 | } 40 | 41 | trait SomeApi extends HttpApi { 42 | 43 | def logName = "zzb.test.some" 44 | 45 | def api = get { 46 | pathPrefix(Segment) { 47 | case "ok" ⇒ complete("ok") 48 | case "404" ⇒ complete(NotFound) 49 | 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /zzb-rest/src/test/scala/zzb/rest/testkit/QuietReporter.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | 5 | package org.scalatest.akka 6 | 7 | import org.scalatest.tools.StandardOutReporter 8 | import org.scalatest.events._ 9 | import java.lang.Boolean.getBoolean 10 | 11 | class QuietReporter(inColor: Boolean, withDurations: Boolean = false) extends StandardOutReporter(withDurations, 12 | inColor, false, true, true, true, true, true, true) { 13 | 14 | def this() = this(!getBoolean("akka.test.nocolor"), !getBoolean("akka.test.nodurations")) 15 | 16 | override def apply(event: Event): Unit = event match { 17 | case _: RunStarting ⇒ () 18 | case _ ⇒ super.apply(event) 19 | } 20 | 21 | //override def makeFinalReport(resourceName: String, duration: Option[Long], summaryOption: Option[Summary]) {} 22 | } 23 | -------------------------------------------------------------------------------- /zzb-rest/src/test/scala/zzb/rest/testuse/package.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest 2 | 3 | import com.github.nscala_time.time.Imports._ 4 | import spray.json.JsonFormat 5 | 6 | import scala.reflect._ 7 | 8 | /** 9 | * Created by Simon on 2014/6/24 10 | */ 11 | package object testuse { 12 | 13 | import zzb.datatype._ 14 | import BasicFormats._ 15 | 16 | val Phones = TList[Int]("phones", "电话号码") 17 | 18 | object Student extends TStruct { 19 | 20 | val name = Field(TString("name", "姓名")) 21 | val age = Field(TInt("age", "年龄")) 22 | val height = Field(TFloat("height", "身高")) 23 | val birthDay = Field(TDateTime("birthday", "初生日期")) 24 | val sex = FieldBoolean(TBoolean("sex", "性别"), isRequired = true, default = true) 25 | val phones = Field(Phones) 26 | 27 | val t_memo_ : String = "学生信息" 28 | 29 | implicit def packWrap(p: Pack) = new PackWrap(p) 30 | 31 | class PackWrap(p: Pack) { 32 | def name = p(Student.name).get.value 33 | } 34 | 35 | } 36 | 37 | object Task extends TStruct { 38 | val t_memo_ = "在某状态下可执行的动作信息列表" 39 | val createDate = FieldString(TString("createDate", "任务创建日期"), default = () ⇒ DateTime.now.toString("yyyy-MM-dd")) 40 | val isAssignToUser = FieldBoolean(TBoolean("isAssignToUser", "是否分配到人"), isRequired = true, default = true) 41 | val track = Field(TrackInfos) 42 | } 43 | 44 | object TrackInfo extends TrackInfoBase 45 | 46 | trait TrackInfoListBase extends TPackList[TrackInfo.Pack] { 47 | val t_memo_ = "轨迹信息列表" 48 | override val lm: ClassTag[_] = classTag[TrackInfo.Pack] 49 | } 50 | 51 | /** 52 | * 任务轨迹信息 53 | */ 54 | object TrackInfos extends TrackInfoListBase { 55 | implicit val elementFormat: JsonFormat[TrackInfo.Pack] = TrackInfo.Format 56 | 57 | override def itemDataType: DataType[Any] = TrackInfo 58 | } 59 | 60 | /** 61 | * 轨迹信息 62 | */ 63 | trait TrackInfoBase extends TStruct { 64 | val t_memo_ : String = "轨迹信息" 65 | 66 | val actionName = Field(TString("actionName", "动作名称")) 67 | 68 | val operator = Field(TString("operator", "操作人")) 69 | 70 | val operaTime = FieldDateTime(TDateTime("operaTime", "操作时间"), default = () ⇒ DateTime.now) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /zzb-rest/src/test/scala/zzb/rest/util/IdleReleasableTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.util 2 | 3 | import org.scalatest.{ BeforeAndAfterAll, WordSpec } 4 | import org.scalatest.MustMatchers 5 | import zzb.rest._ 6 | import akka.actor.{ ActorNotFound, Props, ActorSystem, Actor } 7 | import spray.util._ 8 | import akka.util.Timeout 9 | import scala.concurrent.duration._ 10 | 11 | /** 12 | * Created by Simon on 2014/3/27 13 | */ 14 | class IdleReleasableTest extends WordSpec 15 | with MustMatchers 16 | with Requester with BeforeAndAfterAll { 17 | implicit val system = ActorSystem() 18 | 19 | override protected def beforeAll() { 20 | 21 | } 22 | 23 | override protected def afterAll() { 24 | super.afterAll() 25 | system.shutdown() 26 | } 27 | 28 | implicit val timeout = Timeout(5000 milliseconds) 29 | 30 | "Idle Actor " must { 31 | "can be release after maxIdle time" in { 32 | system.actorOf(Props(new AutoReleaseActor(3) with IdleReleasable with StatableActor), "auto") 33 | val act = system.actorSelection("akka://default/user/auto") 34 | act.resolveOne().await 35 | 36 | Thread.sleep(6000) 37 | 38 | intercept[ActorNotFound] { 39 | act.resolveOne().await 40 | } 41 | } 42 | } 43 | } 44 | 45 | class AutoReleaseActor(val maxIdle: Int) extends Actor { 46 | def preIdleRelease: Boolean = true 47 | 48 | def receive = { 49 | case "hello" ⇒ sender ! "ok" 50 | } 51 | } -------------------------------------------------------------------------------- /zzb-rest/src/test/scala/zzb/rest/util/StatableTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.rest.util 2 | 3 | import org.scalatest.{ BeforeAndAfterAll, WordSpec } 4 | import org.scalatest.MustMatchers 5 | import zzb.rest._ 6 | import akka.actor.{ Actor, Props, ActorSystem } 7 | import akka.pattern._ 8 | import spray.util._ 9 | import akka.util.Timeout 10 | import scala.concurrent.duration._ 11 | 12 | /** 13 | * Created by Simon on 2014/3/25 14 | */ 15 | class StatableTest extends WordSpec 16 | with MustMatchers 17 | with Requester with BeforeAndAfterAll { 18 | 19 | implicit val system = ActorSystem() 20 | 21 | override protected def beforeAll() { 22 | 23 | system.actorOf(Props(new AccountActor2 with StatableActor), "account2") 24 | } 25 | 26 | override protected def afterAll() { 27 | super.afterAll() 28 | system.shutdown() 29 | } 30 | 31 | "StatableActor " must { 32 | val helloActor = system.actorOf(Props(new HelloActor with StatableActor), "hello") 33 | implicit val timeout = Timeout(5000000 milliseconds) 34 | "can count message" in { 35 | helloActor ! "hello" 36 | val stat = helloActor.ask(RequestStat).await 37 | stat.asInstanceOf[MessageStat].count mustBe 1 38 | } 39 | 40 | "rest actor can count message" in { 41 | val account = Account("simon", 39) 42 | 43 | val res1 = (Get("/account2") ~> doRequest).await 44 | res1.status must equal(StatusCodes.OK) 45 | res1.entity.data must equal(account) 46 | 47 | val res2 = (Get("/account2/stat") ~> doRequest).await 48 | res2.status must equal(StatusCodes.OK) 49 | val stat = res2.entity.data.asInstanceOf[MessageStat] 50 | stat.count mustBe 2 51 | } 52 | } 53 | 54 | } 55 | 56 | class HelloActor extends Actor { 57 | override def receive = { 58 | case "hello" ⇒ 59 | println("ok") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /zzb-shell/doc/Wash-Shell Quick Guide.txt: -------------------------------------------------------------------------------- 1 | 2 | 任务定义文件格式 3 | 4 | 任务定义文件采用 typesafe (AKKA 开发商)conf 格式定义。示例如下: 5 | 6 | task { 7 | 8 | demo = wash.shell.task.Task1 ; 这是一个示例的任务,分号后是任务的说明 9 | 10 | mole { 11 | list-car = wash.mole.ListCars ; 列出所有车辆 12 | 13 | car { 14 | 15 | del-car = wosh.mole.DelCar ; 删除某个车辆 16 | } 17 | } 18 | 19 | cartype { 20 | list-car = car.type.ListCars ; 列出车型库中的所有车辆 21 | } 22 | } 23 | 24 | 25 | 要点说明: 26 | 27 | 1· 所有任务定义必须在 task{ } 范围内定义 28 | 2. 任务支持名字空间,task{} 内的直接任务在默认名字空间 "_" 中, 可以用 spacename{} 29 | 格式依次嵌套名字空间。 30 | 31 | 每一个任务的 “ 全名 = 名字空间 + 短名称 ”,每个字符中间用 "." 隔开。 32 | 上述例子定义了如下任务全名: 33 | 34 | _.demo 35 | mole.list-car 36 | mole.car.del-car 37 | cartype.list-car 38 | 39 | 全名的最后一段为"短名称" 40 | 41 | 3. 多个任务的全名不能重复(如果重复,程序启动时解析会报错),短名称可以重复 42 | 43 | 4. 在 shell 中输入 短名称即可运行,如果短名称有重复,会有提示,再输入全名即可执行。 44 | 45 | 5. 分号前为任务类型名称,分号后为任务描述。 46 | 47 | shell 中实现了三个缺省任务: 48 | 49 | 1. exit , 退出. 50 | 51 | 2. tasks , 列出系统中的所有任务 52 | 53 | 3. 以”*“结尾的字符串,会列出以给定字符序列开头的所有命令短名称。例如:输入 "ta*"回车,会列出 54 | 所有以”ta“开头的任务 。 (其实是一个土鳖的自动补全功能,本来应该用tab键,没做出来,以后再改。 55 | 56 | 配置文件装载: 57 | 58 | wash-shell 库本身并不管从哪个配置文件装载命令。 59 | Task 伴生对象的 parseConfig 的输入参数是一个Config,是一个符合上述规范的Config配置段。 60 | 参见wash-main 的 WashApp 61 | 62 | Shell 启动: 63 | 64 | 使用 wash-shell 库启动 shell 非常简单 65 | 66 | import wash.shell.Shell 67 | 68 | Shell("wash", "wash-data").commandLoop 69 | 70 | Shell工厂方法的第一个参数是命令行提示符,第二个参数是应用的名称。 71 | commandLoop 方法启动命令循环。 72 | 73 | shell启动后,前面提到的三个任务就默认已经装载。 74 | 你自己的任务应该在启动Shell之前调用 Task.parseConfig() 方法加载。 75 | 76 | -------------------------------------------------------------------------------- /zzb-shell/src/main/java/wash/shell/ConsolePrintStream.java: -------------------------------------------------------------------------------- 1 | package wash.shell; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.PrintStream; 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-9-25 10 | * Time: 上午10:18 11 | */ 12 | public class ConsolePrintStream extends PrintStream { 13 | 14 | private ByteArrayOutputStream out = new ByteArrayOutputStream(); 15 | 16 | public ConsolePrintStream(ByteArrayOutputStream out){ 17 | super(out); 18 | this.out = out; 19 | } 20 | 21 | public String content(){ 22 | return out.toString(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /zzb-shell/src/main/resources/defaultShellRemote.conf: -------------------------------------------------------------------------------- 1 | daemon { 2 | username = "admin" 3 | password = "baoxian.com!@#$%" 4 | session-timeout = 600 # seconds 5 | } 6 | -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/CLIException.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: Simon Xiao 6 | * Date: 13-9-13 7 | * Time: 下午5:14 8 | * Copyright baoxian.com 2012~2020 9 | */ 10 | /** 11 | * 12 | * Root exception for Cliche. 13 | * 14 | * @author ASG 15 | */ 16 | object CLIException { 17 | def createCommandNotFound(commandName: String): CLIException = { 18 | return new CLIException("Unknown command: " + Token.escapeString(commandName)) 19 | } 20 | 21 | def createCommandNotFoundForArgNum(commandName: String, argCount: Int): CLIException = { 22 | return new CLIException("There's no command " + Token.escapeString(commandName) + " taking " + argCount + " arguments") 23 | } 24 | 25 | def createAmbiguousCommandExc(commandName: String, argCount: Int): CLIException = { 26 | return new CLIException("Ambiguous command " + Token.escapeString(commandName) + " taking " + argCount + " arguments") 27 | } 28 | } 29 | 30 | class CLIException(message: String, cause: Option[Exception]) extends Exception(message, cause.orNull) { 31 | 32 | def this(message: String) = { 33 | this(message, null) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/Input.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | import java.io.InputStream 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-9-13 9 | * Time: 下午4:00 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | trait Input { 13 | 14 | def readCommand(path: List[String]): String 15 | 16 | def readClearCommand : String 17 | 18 | def inStream : InputStream 19 | } 20 | -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/Output.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | import java.io.PrintStream 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-9-13 9 | * Time: 下午4:01 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | trait Output { 13 | 14 | def output(obj: AnyRef, oce: OutputConversionEngine) 15 | 16 | def outputException(input: String, error: TokenException) 17 | 18 | def outputException(e: Throwable) 19 | 20 | def outputException(input: String, e: Throwable) 21 | 22 | def outputHeader(text: String) 23 | 24 | def out: PrintStream 25 | def err: PrintStream 26 | 27 | } 28 | -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/OutputConversionEngine.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: Simon Xiao 6 | * Date: 13-9-13 7 | * Time: 下午4:04 8 | * Copyright baoxian.com 2012~2020 9 | */ 10 | class OutputConversionEngine { 11 | 12 | type OutputConverter = AnyRef ⇒ AnyRef //Function1[AnyRef,AnyRef] 13 | 14 | private val outputConverters = scala.collection.mutable.ListBuffer[OutputConverter]() 15 | 16 | def addConverter(converter: OutputConverter) { 17 | if (converter == null) { 18 | throw new IllegalArgumentException("Converter == null") 19 | } 20 | outputConverters += converter 21 | 22 | } 23 | 24 | def removeConverter(converter: OutputConverter): Boolean = { 25 | val has = outputConverters.contains(converter) 26 | 27 | if (has) outputConverters -= converter 28 | 29 | has 30 | } 31 | 32 | def convertOutput(anObject: AnyRef): AnyRef = { 33 | var convertedOutput: AnyRef = anObject 34 | outputConverters.toList.reverse.foreach { converter ⇒ 35 | val conversionResult = converter.apply(convertedOutput) 36 | if (conversionResult != null) 37 | convertedOutput = conversionResult 38 | } 39 | 40 | convertedOutput 41 | } 42 | 43 | // def addDeclaredConverters(handler: AnyRef) { 44 | // val fields: Array[Field] = handler.getClass.getFields 45 | // val PREFIX: String = "CLI_OUTPUT_CONVERTERS" 46 | // for (field <- fields) { 47 | // if (field.getName.startsWith(PREFIX) && field.getType.isArray && classOf[OutputConverter].isAssignableFrom(field.getType.getComponentType)) { 48 | // try { 49 | // val convertersArray: AnyRef = field.get(handler) 50 | // { 51 | // var i: Int = 0 52 | // while (i < Array.getLength(convertersArray)) { 53 | // { 54 | // addConverter(Array.get(convertersArray, i).asInstanceOf[OutputConverter]) 55 | // } 56 | // ({ 57 | // i += 1; i - 1 58 | // }) 59 | // } 60 | // } 61 | // } 62 | // catch { 63 | // case ex: Exception => { 64 | // throw new RuntimeException("Error getting converter from field " + field.getName, ex) 65 | // } 66 | // } 67 | // } 68 | // } 69 | // } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/ShellManageable.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: Simon Xiao 6 | * Date: 13-9-13 7 | * Time: 下午5:32 8 | * Copyright baoxian.com 2012~2020 9 | */ 10 | /** 11 | * This interface is for classes that want to be aware of entering 12 | * and leaving each command loop. 13 | * 14 | * You might want some special resources to be allocated before CLI 15 | * starts; after conversation you want to free those resources. 16 | * By implementing this interface you get the ability to handle these 17 | * events. 18 | * 19 | * Note that since Shell can possibly have other means of operation 20 | * instead of commandLoop(), these methods may be not called. 21 | * 22 | * @author ASG 23 | */ 24 | trait ShellManageable { 25 | /** 26 | * This method is called when it is about to enter the command loop. 27 | */ 28 | def cliEnterLoop 29 | 30 | /** 31 | * This method is called when Shell is leaving the command loop. 32 | */ 33 | def cliLeaveLoop 34 | } -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/TokenException.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | /** 4 | * Created with IntelliJ IDEA. 5 | * User: Simon Xiao 6 | * Date: 13-9-13 7 | * Time: 下午5:22 8 | * Copyright baoxian.com 2012~2020 9 | */ 10 | /** 11 | * Exception pointing at the token which caused it. 12 | * Used to report invalid parameter types. 13 | * 14 | * @author ASG 15 | */ 16 | class TokenException(val token: Token, message: String, cause: Option[Exception]) extends CLIException(message, cause) { 17 | 18 | def this(token: Token) { 19 | this(token, "", None) 20 | } 21 | 22 | def this(token: Token, message: String) { 23 | this(token, message, None) 24 | } 25 | 26 | def this(token: Token, cause: Option[Exception]) { 27 | this(token, "", cause) 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/remote/ActorOutputStream.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell.remote 2 | 3 | import java.io.OutputStream 4 | import akka.actor.ActorRef 5 | import zzb.shell.remote.ShellProtocol.Data 6 | 7 | /** 8 | * Created with IntelliJ IDEA. 9 | * User: Simon Xiao 10 | * Date: 13-10-18 11 | * Time: 上午10:49 12 | * Copyright baoxian.com 2012~2020 13 | */ 14 | class ActorOutputStream(val actor:ActorRef) extends OutputStream{ 15 | def write(b: Int) = { 16 | actor ! Data(b) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/remote/ShellProtocol.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell.remote 2 | 3 | import zzb.shell.ActionStat 4 | import akka.actor.ActorRef 5 | 6 | /** 7 | * Created with IntelliJ IDEA. 8 | * User: Simon Xiao 9 | * Date: 13-10-16 10 | * Time: 下午2:07 11 | * Copyright baoxian.com 2012~2020 12 | */ 13 | object ShellProtocol { 14 | 15 | case class Login(userName: String, passWord: String,peer:ActorRef) 16 | 17 | case class Logout(sid:String) 18 | 19 | case class LoginSuccess(sid: String,peer:ActorRef) 20 | 21 | case object LoginFailed 22 | 23 | case class SessionLost(sid: String,reason:String) 24 | 25 | case class Data(d:Int) 26 | 27 | case class Ping(sid:String) 28 | 29 | case object Pong 30 | 31 | case object KickAll 32 | } -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/task/Tasks.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell.task 2 | 3 | import zzb.shell.Task 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-9-17 9 | * Time: 上午9:32 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | class Tasks extends Task { 13 | 14 | val sortedSpaces = collection.SortedSet(Task.spacesIndex.keySet.toList: _*) 15 | 16 | sortedSpaces.foreach { space ⇒ 17 | 18 | println(s"\t$space:") 19 | 20 | Task.spacesIndex(space).foreach { info ⇒ 21 | println(f"\t ${info.shortName}%-25s ----- ${info.desc}%-35s") 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/task/Verbose.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell.task 2 | 3 | import zzb.shell.Task 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-12-10 9 | * Time: 上午9:00 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | class Verbose extends Task { 13 | 14 | if(args.length == 0) 15 | shell.verbose_ = !shell.verbose_ 16 | else args(0).toUpperCase match{ 17 | case "ON" => shell.verbose_ = true 18 | case "OFF" => shell.verbose_ = false 19 | } 20 | 21 | println("verbose " + (if(shell.verbose_) "ON" else "OFF")) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/task/Wildcard.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell.task 2 | 3 | import zzb.shell.{ TaskInfo, Task } 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-9-17 9 | * Time: 上午11:26 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | class WildCard() extends Task { 13 | 14 | val wide = args.head 15 | var count = 0 16 | if (wide.length > 0) { 17 | for (shortName ← Task.shortNameIndex.keySet if shortName.startsWith(wide)) { 18 | print(f"$shortName%-15s") 19 | count += 1 20 | if (count == 6) { count = 0; println() } 21 | } 22 | } 23 | println() 24 | 25 | } 26 | -------------------------------------------------------------------------------- /zzb-shell/src/main/scala/zzb/shell/util/Page.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell.util 2 | 3 | import scala.concurrent.{ExecutionContext, Future} 4 | 5 | /** 6 | * Created with IntelliJ IDEA. 7 | * User: Simon Xiao 8 | * Date: 13-12-10 9 | * Time: 上午8:44 10 | * Copyright baoxian.com 2012~2020 11 | */ 12 | 13 | /** 用于分页操作,每一页出了本页的内容外,还有下一页的 future */ 14 | case class Page[T](pageIdx: Int, pageSize: Int, content: Seq[T], total: Long, next: Option[Future[Page[T]]]) 15 | 16 | object Page { 17 | 18 | case class Content[T](content: Seq[T], total: Long) 19 | 20 | /** 21 | * 获取分页内容的递归算法 22 | * @param firstPageIdx 第一页的索引 23 | * @param pageSize 页面大小 24 | * @param passOver 已经获取过的内容长度 25 | * @param pageFunc 页面读取函数(该函数的第一个参数是 要读取的页面索引,第二个参数是页面大小) 26 | * @param execctx 执行上下文 27 | * @tparam T 28 | * @return 29 | */ 30 | def nextPage[T](firstPageIdx: Int, pageSize: Int, passOver: Int, 31 | pageFunc: (Int, Int) => Content[T])(implicit execctx: ExecutionContext): Page[T] = { 32 | 33 | val contentFirst = pageFunc(firstPageIdx, pageSize) 34 | 35 | if (contentFirst.content.size + passOver >= contentFirst.total || contentFirst.content.size == 0) 36 | Page(firstPageIdx, pageSize, contentFirst.content, contentFirst.total, None) 37 | else 38 | Page(firstPageIdx, pageSize, contentFirst.content, contentFirst.total, Some( 39 | Future { 40 | nextPage(firstPageIdx + 1, pageSize, passOver + contentFirst.content.size, pageFunc) 41 | } 42 | ) 43 | ) 44 | } 45 | } -------------------------------------------------------------------------------- /zzb-shell/src/test/scala/zzb/shell/ActionErrorTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | import scala.util.{Failure, Success} 6 | import com.typesafe.config.ConfigFactory 7 | import akka.actor.ActorSystem 8 | import spray.util._ 9 | import scala.concurrent.Future 10 | 11 | 12 | /** 13 | * Created with IntelliJ IDEA. 14 | * User: Simon Xiao 15 | * Date: 13-10-9 16 | * Time: 下午2:49 17 | * Copyright baoxian.com 2012~2020 18 | */ 19 | class ActionErrorTest extends WordSpec with MustMatchers { 20 | 21 | Shell.init(ConfigFactory.load("defaultShellRemote.conf"), ActorSystem()) 22 | val system = Shell.system 23 | 24 | import system.dispatcher 25 | 26 | "when action generate error" must { 27 | " stat data can get exception msg" in { 28 | val ea = EchoAction(1) 29 | val stat = ea.stat.await 30 | stat.isSucceed mustBe false 31 | stat.ex.get.getMessage mustBe EchoAction.errMsg 32 | } 33 | 34 | " error can spread to cascade future" in { 35 | //val ea = EchoAction(1) 36 | val dv_f: Future[Int] = for { 37 | v <- EchoAction(1) 38 | dv <- DoubleAction(v) 39 | } yield dv 40 | 41 | intercept[ActionException]{ 42 | dv_f.await 43 | } 44 | // 45 | // 46 | // 47 | // dv_f.onComplete { 48 | // case Success(s) => throw new Exception("see ghost 2") 49 | // case Failure(ex: ActionException) => assert(ex.getCause.getMessage == EchoAction.errMsg) 50 | // case Failure(ex) => ex.printStackTrace(); throw new Exception("see ghost 3") 51 | // } 52 | } 53 | } 54 | 55 | } 56 | 57 | 58 | case class EchoAction(v: Int) extends Action[Int] { 59 | 60 | def func(): Int = { 61 | if (v >= 100) v 62 | else throw new Exception(EchoAction.errMsg) 63 | } 64 | } 65 | 66 | object EchoAction { 67 | val errMsg = "echo value less than 100" 68 | } 69 | 70 | case class DoubleAction(v: Int) extends Action[Int] { 71 | 72 | def func(): Int = { 73 | v * 2 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /zzb-shell/src/test/scala/zzb/shell/ArgsParseTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | import com.typesafe.config.ConfigFactory 6 | import akka.actor.ActorSystem 7 | 8 | /** 9 | * Created with IntelliJ IDEA. 10 | * User: Simon Xiao 11 | * Date: 13-12-12 12 | * Time: 下午1:31 13 | * Copyright baoxian.com 2012~2020 14 | */ 15 | class ArgsParseTest extends WordSpec with MustMatchers { 16 | Shell.init(ConfigFactory.load("defaultShellRemote.conf"),ActorSystem()) 17 | 18 | 19 | "Args Parse" must { 20 | 21 | Task.parseOne("argsParse", "zzb.shell.ParseAgrs") 22 | 23 | "args " in { 24 | val shell = Shell("test","test",notUseIoIn = true,sync = false) 25 | 26 | shell.requestExeCmd("argsParse name=Simon") 27 | shell.requestExeCmd("exit") 28 | Thread.sleep(500) 29 | } 30 | } 31 | 32 | 33 | } 34 | 35 | 36 | 37 | class ParseAgrs extends Task { 38 | 39 | Console.withOut(out){ 40 | println(params) 41 | 42 | assert(params("name") == "Simon") 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /zzb-shell/src/test/scala/zzb/shell/AsynTaskTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | import scala.util.{ Failure, Success } 6 | import com.typesafe.config.ConfigFactory 7 | import akka.actor.ActorSystem 8 | 9 | 10 | /** 11 | * Created with IntelliJ IDEA. 12 | * User: Simon Xiao 13 | * Date: 13-9-25 14 | * Time: 下午2:01 15 | * Copyright baoxian.com 2012~2020 16 | */ 17 | class AsynTaskTest extends WordSpec with MustMatchers { 18 | 19 | Shell.init(ConfigFactory.load("defaultShellRemote.conf"),ActorSystem()) 20 | 21 | val system = Shell.system 22 | import system.dispatcher 23 | 24 | 25 | "asyn task " must { 26 | " run in different thread " in { 27 | val mainThreadId = Thread.currentThread().getId 28 | QueryThreadId(mainThreadId).future.andThen { 29 | 30 | case Success(subThreadId) ⇒ mainThreadId must not equal (subThreadId) 31 | 32 | case Failure(_) ⇒ throw new Exception("error") 33 | } 34 | } 35 | 36 | "can get task time span " in { 37 | 38 | LongTimeWore("simon").stat.andThen { 39 | 40 | case Success(s) ⇒ s.timeSpan must (be > (1950L)) 41 | 42 | case Failure(_) ⇒ throw new Exception("error") 43 | } 44 | 45 | } 46 | } 47 | 48 | } 49 | 50 | case class QueryThreadId(id: Long) extends Action[Long] { 51 | def func(): Long = { 52 | Thread.currentThread().getId 53 | } 54 | } 55 | 56 | case class LongTimeWore(name: String) extends Action[String] { 57 | def func(): String = { 58 | Thread.sleep(2000) 59 | name 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /zzb-shell/src/test/scala/zzb/shell/DaemonTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.shell 2 | 3 | import akka.testkit.{ImplicitSender, TestKit} 4 | import akka.actor.ActorSystem 5 | import org.scalatest.{WordSpecLike, BeforeAndAfterAll, MustMatchers} 6 | import zzb.shell.remote.ShellProtocol._ 7 | import zzb.shell.remote.ShellProtocol.Login 8 | import zzb.shell.remote.ShellProtocol.LoginSuccess 9 | import zzb.shell.remote.ShellDaemon 10 | import com.typesafe.config.ConfigFactory 11 | import scala.concurrent.duration._ 12 | 13 | /** 14 | * Created with IntelliJ IDEA. 15 | * User: Simon Xiao 16 | * Date: 13-10-17 17 | * Time: 下午2:38 18 | * Copyright baoxian.com 2012~2020 19 | */ 20 | class DaemonTest extends TestKit(ActorSystem("testSystem")) with WordSpecLike 21 | with MustMatchers 22 | with ImplicitSender 23 | with BeforeAndAfterAll { 24 | 25 | override protected def afterAll() { 26 | super.afterAll() 27 | system.shutdown() 28 | } 29 | 30 | "inner shell " must { 31 | 32 | Shell.init(ConfigFactory.load("defaultShellRemote.conf"), system) 33 | 34 | val managerActor = ShellDaemon.install("test") 35 | 36 | "can login from remote" in { 37 | 38 | val wait = 3.seconds 39 | 40 | managerActor ! Login("not_me", "wrongPass", self) 41 | 42 | expectMsgPF(wait,"no this user , login failed") { 43 | case LoginFailed ⇒ () 44 | } 45 | 46 | managerActor ! Login("admin", "wrongPass", self) 47 | expectMsgPF(wait,"wrong password,login failed") { 48 | case LoginFailed ⇒ () 49 | } 50 | 51 | managerActor ! Login("admin", "baoxian.com!@#$%", self) 52 | expectMsgPF(wait,"login success") { 53 | case LoginSuccess(sid, peer) ⇒ 54 | sid.length must equal(36) 55 | } 56 | expectMsgPF(wait,"received data") { 57 | case Data(d) ⇒ () 58 | 59 | } 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /zzb-storage/src/main/scala/zzb/storage/TStorable.scala: -------------------------------------------------------------------------------- 1 | package zzb.storage 2 | 3 | import zzb.datatype.{Versioned, DataType, TStruct} 4 | 5 | /** 6 | * Created by Simon on 2014/3/31 7 | */ 8 | trait TStorable[K,KT <: DataType[K] ] extends TStruct with Versioned{ 9 | 10 | def keyType: KT 11 | 12 | override protected def afterPackCreated(p:Pack) = require(requiredField.contains(keyType)) 13 | } 14 | -------------------------------------------------------------------------------- /zzb-storage/src/main/scala/zzb/storage/package.scala: -------------------------------------------------------------------------------- 1 | package zzb 2 | 3 | import akka.event.{NoLogging, LoggingAdapter} 4 | import zzb.datatype._ 5 | import zzb.storage.dirvers.{MongoDriver, MemoryDriver} 6 | 7 | /** 8 | * Created with IntelliJ IDEA. 9 | * User: Simon Xiao 10 | * Date: 13-12-4 11 | * Time: 下午5:34 12 | * Copyright baoxian.com 2012~2020 13 | */ 14 | package object storage { 15 | 16 | def memoryStorage[K, KT <: DataType[K], T <: TStorable[K, KT]](dType: T, maxCache: Int = 1000, initCache: Int = 50,log :LoggingAdapter = NoLogging) = { 17 | 18 | val driver = new MemoryDriver[K, KT, T]() { 19 | override val docType = dType 20 | override val logger = log 21 | } 22 | new Storage[K, KT, T](driver, maxCache, initCache) 23 | } 24 | 25 | def mongodbStorage[K, KT <: DataType[K], T <: TStorable[K, KT]](dbName: String, dType: T, maxCache: Int = 1000, initCache: Int = 50,log :LoggingAdapter = NoLogging) = { 26 | 27 | val driver = new MongoDriver[K, KT, T]() { 28 | override val docType = dType 29 | override val logger = log 30 | 31 | def dbname = dbName 32 | } 33 | new Storage[K, KT, T](driver, maxCache, initCache) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /zzb-storage/src/test/scala/zzb/storage/DBObjectHelperTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.storage 2 | 3 | import org.scalatest.FlatSpec 4 | import zzb.datatype.StructPath 5 | import zzb.storage.data.HomeInfo 6 | import zzb.storage.data.HomeInfo._ 7 | 8 | /** 9 | * todo: 10 | * @author 何伟波 11 | * @since 0.1.0 12 | */ 13 | class DBObjectHelperTest extends FlatSpec with DBObjectHelper{ 14 | 15 | val structPath = StructPath(HomeInfo.carInfo().carLicense().path) 16 | 17 | it should "structPathToString" in { 18 | 19 | val result = structPathToString(structPath) 20 | assert(result === "carInfo.carLicense") 21 | } 22 | 23 | it should "makeFilterByConditionName" in { 24 | val result = makeFilterByConditionName(structPath,123,"eq") 25 | println (result) 26 | val result2 = makeFilterByConditionName(structPath,123,"lt") 27 | println (result2) 28 | val list = List(result,result2).flatten 29 | val result3=makeAndFilter(list) 30 | println (result3) 31 | val result4= List((StructPath(carInfo().createDate().path),"2014-08-30","lt"),(StructPath(carInfo().createDate().path),"2014-08-01","gt")) 32 | println(result4) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /zzb-util/src/main/scala/zzb/util/log/Clock.scala: -------------------------------------------------------------------------------- 1 | package zzb.util.log 2 | 3 | import akka.event.LoggingAdapter 4 | 5 | /** 6 | * Created by Simon on 2015/3/31 7 | */ 8 | object Clock { 9 | 10 | def timeSpan2String(millis:Long):String = { 11 | 12 | val second = millis / 1000 13 | val ms = millis % 1000 14 | 15 | val msStr = if(ms < 10) "00" + ms else if (ms < 100) "0" + ms else ms.toString 16 | 17 | second + "." + msStr + " seconds" 18 | } 19 | 20 | def clocking[T](message: String)( body: => T)(implicit logger : LoggingAdapter):T = { 21 | logger.info("|<--- " + message) 22 | doClocking(body) 23 | } 24 | 25 | def clocking[T](template: String, arg1: Any)( body: => T)(implicit logger : LoggingAdapter):T = { 26 | logger.info("|<--- " + template,arg1) 27 | doClocking(body) 28 | } 29 | 30 | def clocking[T](template: String, arg1: Any, arg2: Any)( body: => T)(implicit logger : LoggingAdapter):T = { 31 | logger.info("|<--- " + template,arg1,arg2) 32 | doClocking(body) 33 | } 34 | 35 | def clocking[T](template: String, arg1: Any, arg2: Any, arg3: Any)( body: => T)(implicit logger : LoggingAdapter):T = { 36 | logger.info("|<--- " + template,arg1,arg2,arg3) 37 | doClocking(body) 38 | } 39 | 40 | def clocking[T](template: String, arg1: Any, arg2: Any, arg3: Any, arg4: Any)( body: => T)(implicit logger : LoggingAdapter):T = { 41 | logger.info("|<--- " + template,arg1,arg2,arg3,arg4) 42 | doClocking(body) 43 | } 44 | 45 | private def doClocking[T]( body: => T)(implicit logger : LoggingAdapter) :T= { 46 | val beginClock = System.currentTimeMillis() 47 | val result = body 48 | val endClock = System.currentTimeMillis() 49 | 50 | logger.info("|<--- Elapsed time: {}",timeSpan2String(endClock - beginClock)) 51 | 52 | result 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /zzb-util/src/main/scala/zzb/util/log/HeritLog.scala: -------------------------------------------------------------------------------- 1 | package zzb.util.log 2 | 3 | import akka.event._ 4 | /** 5 | * Created by Simon on 2014/7/23 6 | */ 7 | case class HeritLog(herit:String){ 8 | def apply(logStatement: ⇒ Unit)(implicit log:LoggingAdapter ):Unit = { 9 | log match { 10 | case mdcLog : DiagnosticLoggingAdapter => 11 | mdcLog.mdc( mdcLog.mdc ++ Map("herit" -> herit)) 12 | logStatement 13 | case _ => 14 | logStatement 15 | } 16 | } 17 | } 18 | 19 | case class HeritLogAdapter(herit:String,la: DiagnosticLoggingAdapter) extends LoggingAdapter{ 20 | 21 | def isErrorEnabled = la.isErrorEnabled 22 | def isWarningEnabled = la.isWarningEnabled 23 | def isInfoEnabled = la.isInfoEnabled 24 | def isDebugEnabled = la.isDebugEnabled 25 | 26 | protected def notifyError(message: String): Unit = { 27 | la.mdc( la.mdc ++ Map("herit" -> herit)) 28 | la.error(message) 29 | } 30 | protected def notifyError(cause: Throwable, message: String): Unit = { 31 | la.mdc( la.mdc ++ Map("herit" -> herit)) 32 | la.error(cause, message) 33 | } 34 | protected def notifyWarning(message: String): Unit = { 35 | val mmm = la.mdc 36 | la.mdc( la.mdc ++ Map("herit" -> herit)) 37 | la.warning(message) 38 | } 39 | protected def notifyInfo(message: String): Unit = { 40 | la.mdc( la.mdc ++ Map("herit" -> herit)) 41 | la.info(message) 42 | } 43 | protected def notifyDebug(message: String): Unit = { 44 | la.mdc( la.mdc ++ Map("herit" -> herit)) 45 | la.debug(message) 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /zzb-util/src/main/scala/zzb/util/log/MDCFilter.scala: -------------------------------------------------------------------------------- 1 | package zzb.util.log 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent 4 | import ch.qos.logback.core.filter.AbstractMatcherFilter 5 | import ch.qos.logback.core.spi.FilterReply 6 | 7 | import scala.beans.BeanProperty 8 | 9 | /** 10 | * Created by Simon on 2014/6/30 11 | */ 12 | class MDCFilter extends AbstractMatcherFilter[ILoggingEvent] { 13 | override def decide(event: ILoggingEvent): FilterReply = { 14 | if (!isStarted) { 15 | return FilterReply.NEUTRAL 16 | } 17 | val mdc = event.getMDCPropertyMap 18 | if (mdc == null || MDCKey == null || Value == null || !mdc.containsKey(MDCKey)) return FilterReply.NEUTRAL 19 | if (mdc.get(MDCKey) == Value) onMatch else onMismatch 20 | } 21 | 22 | @BeanProperty 23 | var Value: String = _ 24 | 25 | @BeanProperty 26 | var MDCKey: String = _ 27 | 28 | 29 | private var accepts = Set[String]() 30 | 31 | def allowValues_(keys: String) = { 32 | accepts = keys.split(",").toSet 33 | println("accepts-->" + accepts) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /zzb-util/src/main/scala/zzb/util/package.scala: -------------------------------------------------------------------------------- 1 | package zzb 2 | 3 | import java.io.{StringWriter, PrintWriter} 4 | 5 | /** 6 | * Created by Simon on 2014/6/30 7 | */ 8 | package object util { 9 | 10 | implicit class ExceptionStackTrace(ex :Throwable){ 11 | def stackTrace = { 12 | val errors = new StringWriter() 13 | ex.printStackTrace(new PrintWriter(errors)) 14 | errors.toString 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /zzb-util/src/test/scala/zzb/util/IDGeneratorTest.scala: -------------------------------------------------------------------------------- 1 | package zzb.util 2 | 3 | import org.scalatest.WordSpec 4 | import org.scalatest.MustMatchers 5 | 6 | /** 7 | * Created by Simon on 2014/3/26 8 | */ 9 | class IDGeneratorTest extends WordSpec with MustMatchers { 10 | 11 | "IDGenerator" must { 12 | "generate 30000 id not duplicate,all id length is 19" in { 13 | import scala.collection.mutable 14 | val usedIds = mutable.Set[String]() 15 | 16 | var bitLengthIs19 = true 17 | 18 | val startTime = System.currentTimeMillis() 19 | 20 | val g = new IDGenerator(2,2) 21 | val total = 30000 22 | var idx = 0 23 | while (idx < total){ 24 | idx += 1 25 | val id = g.next 26 | usedIds.add(id) 27 | if(bitLengthIs19) bitLengthIs19 = id.toString.length == 19 28 | } 29 | 30 | val endTime = System.currentTimeMillis() 31 | 32 | val usedSeconds = (endTime - startTime)/1000 33 | val usedMilliSeconds = (endTime - startTime)%1000 34 | 35 | println(s"used $usedSeconds seconds , $usedMilliSeconds milliseconds ") 36 | 37 | usedIds.size mustBe total 38 | bitLengthIs19 mustBe true 39 | } 40 | } 41 | 42 | } 43 | --------------------------------------------------------------------------------