Browse Source

jkzl-apollo

chenyongxing 6 years ago
parent
commit
e308771045
100 changed files with 5404 additions and 75 deletions
  1. 1 0
      .gitattributes
  2. 14 1
      .gitignore
  3. 42 0
      .travis.yml
  4. 46 0
      CODE_OF_CONDUCT.md
  5. 25 0
      CONTRIBUTING.md
  6. 201 72
      LICENSE
  7. 220 2
      README.md
  8. 115 0
      apollo-adminservice/pom.xml
  9. 55 0
      apollo-adminservice/src/assembly/assembly-descriptor.xml
  10. 3 0
      apollo-adminservice/src/main/config/apollo-adminservice.conf
  11. 2 0
      apollo-adminservice/src/main/config/app.properties
  12. 4 0
      apollo-adminservice/src/main/config/application-github.properties
  13. 28 0
      apollo-adminservice/src/main/docker/Dockerfile
  14. 33 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceApplication.java
  15. 32 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceHealthIndicator.java
  16. 147 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/NamespaceAcquireLockAspect.java
  17. 157 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/NamespaceUnlockAspect.java
  18. 15 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/PreAcquireNamespaceLock.java
  19. 94 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AppController.java
  20. 75 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AppNamespaceController.java
  21. 82 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ClusterController.java
  22. 32 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/CommitController.java
  23. 15 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/IndexController.java
  24. 189 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/InstanceConfigController.java
  25. 157 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemController.java
  26. 33 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemSetController.java
  27. 145 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceBranchController.java
  28. 105 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceController.java
  29. 50 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceLockController.java
  30. 179 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ReleaseController.java
  31. 97 0
      apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ReleaseHistoryController.java
  32. 2 0
      apollo-adminservice/src/main/resources/META-INF/app.properties
  33. 5 0
      apollo-adminservice/src/main/resources/adminservice.properties
  34. 31 0
      apollo-adminservice/src/main/resources/application.yml
  35. 23 0
      apollo-adminservice/src/main/resources/bootstrap.yml
  36. 23 0
      apollo-adminservice/src/main/resources/logback.xml
  37. 17 0
      apollo-adminservice/src/main/scripts/shutdown.sh
  38. 121 0
      apollo-adminservice/src/main/scripts/startup.sh
  39. 6 0
      apollo-adminservice/src/test/resources/application.properties
  40. 14 0
      apollo-adminservice/src/test/resources/application.yml
  41. 20 0
      apollo-adminservice/src/test/resources/bootstrap.yml
  42. 8 0
      apollo-adminservice/src/test/resources/controller/cleanup.sql
  43. 7 0
      apollo-adminservice/src/test/resources/controller/test-itemset.sql
  44. 11 0
      apollo-adminservice/src/test/resources/controller/test-release.sql
  45. 34 0
      apollo-adminservice/src/test/resources/data.sql
  46. 15 0
      apollo-adminservice/src/test/resources/logback-test.xml
  47. 47 0
      apollo-assembly/pom.xml
  48. 63 0
      apollo-assembly/src/main/java/com/ctrip/framework/apollo/assembly/ApolloApplication.java
  49. 2 0
      apollo-assembly/src/main/resources/META-INF/app.properties
  50. 9 0
      apollo-assembly/src/main/resources/application.yml
  51. 12 0
      apollo-assembly/src/main/resources/logback.xml
  52. 62 0
      apollo-assembly/src/test/java/com/ctrip/framework/apollo/assembly/LocalApolloApplication.java
  53. 6 0
      apollo-assembly/src/test/resources/application.properties
  54. 3 0
      apollo-assembly/src/test/resources/application.yml
  55. 15 0
      apollo-assembly/src/test/resources/logback-test.xml
  56. 33 0
      apollo-biz/pom.xml
  57. 12 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/ApolloBizConfig.java
  58. 39 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/auth/WebSecurityConfig.java
  59. 149 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java
  60. 28 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/customize/BizLoggingCustomizer.java
  61. 4 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/customize/package-info.java
  62. 70 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Audit.java
  63. 71 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Cluster.java
  64. 80 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Commit.java
  65. 94 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/GrayReleaseRule.java
  66. 122 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Instance.java
  67. 150 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/InstanceConfig.java
  68. 79 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Item.java
  69. 65 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Namespace.java
  70. 26 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/NamespaceLock.java
  71. 55 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Privilege.java
  72. 115 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Release.java
  73. 114 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ReleaseHistory.java
  74. 70 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ReleaseMessage.java
  75. 67 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ServerConfig.java
  76. 33 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/eureka/ApolloEurekaClientConfig.java
  77. 70 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/grayReleaseRule/GrayReleaseRuleCache.java
  78. 273 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/grayReleaseRule/GrayReleaseRulesHolder.java
  79. 110 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/DatabaseMessageSender.java
  80. 8 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/MessageSender.java
  81. 10 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/ReleaseMessageListener.java
  82. 124 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/ReleaseMessageScanner.java
  83. 8 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/Topics.java
  84. 36 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/AppNamespaceRepository.java
  85. 17 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/AppRepository.java
  86. 19 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/AuditRepository.java
  87. 19 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ClusterRepository.java
  88. 21 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/CommitRepository.java
  89. 20 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/GrayReleaseRuleRepository.java
  90. 48 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/InstanceConfigRepository.java
  91. 9 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/InstanceRepository.java
  92. 28 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java
  93. 13 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceLockRepository.java
  94. 28 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java
  95. 16 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/PrivilegeRepository.java
  96. 26 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseHistoryRepository.java
  97. 26 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseMessageRepository.java
  98. 38 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseRepository.java
  99. 12 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ServerConfigRepository.java
  100. 0 0
      apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AdminService.java

+ 1 - 0
.gitattributes

@ -0,0 +1 @@
text=auto

+ 14 - 1
.gitignore

@ -1,5 +1,6 @@
# ---> Java
*.class
.DS_Store
application.pid
# Mobile Tools for Java (J2ME)
.mtj.tmp/
@ -12,3 +13,15 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# Eclipse
.classpath
.project
target
.settings
# Idea
.idea
*.iml
# git
*.orig

File diff suppressed because it is too large
+ 42 - 0
.travis.yml


+ 46 - 0
CODE_OF_CONDUCT.md

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at apollo-config@googlegroups.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

+ 25 - 0
CONTRIBUTING.md

@ -0,0 +1,25 @@
## Contributing to apollo
Apollo is released under the non-restrictive Apache 2.0 license, and follows a very standard Github development process, using Github tracker for issues and merging pull requests into master. If you want to contribute even something trivial please do not hesitate, but follow the guidelines below.
### Sign the Contributor License Agreement
Before we accept a non-trivial patch or pull request we will need you to sign the Contributor License Agreement. Signing the contributor’s agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests.
### Code Conventions
Our code style is in line with [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html).
We provide template files [intellij-java-google-style.xml](https://github.com/ctripcorp/apollo/blob/master/apollo-buildtools/style/intellij-java-google-style.xml) for IntelliJ IDEA and [eclipse-java-google-style.xml](https://github.com/ctripcorp/apollo/blob/master/apollo-buildtools/style/eclipse-java-google-style.xml) for Eclipse. If you use other IDEs, then you may config manually by referencing the template files.
* Make sure all new .java files have a simple Javadoc class comment with at least an `@author` tag identifying you, and preferably at least a paragraph on what the class is for.
* Add yourself as an @author to the .java files that you modify substantially (more than cosmetic changes).
* Add some Javadocs and, if you change the namespace, some XSD doc elements.
* A few unit tests should be added for a new feature or an important bug fix.
* If no-one else is using your branch, please rebase it against the current master (or other target branch in the main project).
* When writing a commit message please follow these conventions: if you are fixing an existing issue, please add Fixes #XXX at the end of the commit message (where XXX is the issue number).

File diff suppressed because it is too large
+ 201 - 72
LICENSE


+ 220 - 2
README.md

@ -1,3 +1,221 @@
# jkzl-apollo
对携程apollo spring-boot进行升级,以适用于wlyy2.0需求,
后期apollo的spring-boot若有升级,也可将wlyy2.0的spring boot一起升级,直接使用携程apollo相关jar包
阿波罗自动化配置更新服务
![apollo-logo](apollo-portal/src/main/resources/static/img/logo-detail.png)
================
[![Build Status](https://travis-ci.org/ctripcorp/apollo.svg?branch=master)](https://travis-ci.org/ctripcorp/apollo)
[![GitHub release](https://img.shields.io/github/release/ctripcorp/apollo.svg)](https://github.com/ctripcorp/apollo/releases)
[![Maven Central Repo](https://img.shields.io/maven-central/v/com.ctrip.framework.apollo/apollo.svg)](https://mvnrepository.com/artifact/com.ctrip.framework.apollo/apollo-client)
[![Coverage Status](https://coveralls.io/repos/github/ctripcorp/apollo/badge.svg?branch=master)](https://coveralls.io/github/ctripcorp/apollo?branch=master)
<a href="https://scan.coverity.com/projects/ctripcorp-apollo">
  <img alt="Coverity Scan Build Status" src="https://img.shields.io/coverity/scan/8244.svg"/>
</a>
[![codecov.io](https://codecov.io/github/ctripcorp/apollo/coverage.svg?branch=master)](https://codecov.io/github/ctripcorp/apollo?branch=master)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。
服务端基于Spring Boot和Spring Cloud开发,打包后可以直接运行,不需要额外安装Tomcat等应用容器。
Java客户端不依赖任何框架,能够运行于所有Java运行时环境,同时对Spring/Spring Boot环境也有较好的支持。
.Net客户端不依赖任何框架,能够运行于所有.Net运行时环境。
更多产品介绍参见[Apollo配置中心介绍](https://github.com/ctripcorp/apollo/wiki/Apollo%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E4%BB%8B%E7%BB%8D)
本地快速部署请参见[Quick Start](https://github.com/ctripcorp/apollo/wiki/Quick-Start)
公益演示环境(Demo):
- [140.143.100.23:8070](http://140.143.100.23:8070/)
- 账号/密码:apollo/admin
# Screenshots
![配置界面](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/apollo-home-screenshot.png)
# Features
* **统一管理不同环境、不同集群的配置**
  * Apollo提供了一个统一界面集中式管理不同环境(environment)、不同集群(cluster)、不同命名空间(namespace)的配置。
  * 同一份代码部署在不同的集群,可以有不同的配置,比如zk的地址等
  * 通过命名空间(namespace)可以很方便的支持多个不同应用共享同一份配置,同时还允许应用对共享的配置进行覆盖
* **配置修改实时生效(热发布)**
  * 用户在Apollo修改完配置并发布后,客户端能实时(1秒)接收到最新的配置,并通知到应用程序。
* **版本发布管理**
  * 所有的配置发布都有版本概念,从而可以方便的支持配置的回滚。
* **灰度发布**
  * 支持配置的灰度发布,比如点了发布后,只对部分应用实例生效,等观察一段时间没问题后再推给所有应用实例。
* **权限管理、发布审核、操作审计**
  * 应用和配置的管理都有完善的权限管理机制,对配置的管理还分为了编辑和发布两个环节,从而减少人为的错误。
  * 所有的操作都有审计日志,可以方便的追踪问题。
* **客户端配置信息监控**
  * 可以方便的看到配置在被哪些实例使用
* **提供Java和.Net原生客户端**
  * 提供了Java和.Net的原生客户端,方便应用集成
  * 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便应用使用(需要Spring 3.1.1+)
  * 同时提供了Http接口,非Java和.Net应用也可以方便的使用
* **提供开放平台API**
  * Apollo自身提供了比较完善的统一配置管理界面,支持多环境、多数据中心配置管理、权限、流程治理等特性。
  * 不过Apollo出于通用性考虑,对配置的修改不会做过多限制,只要符合基本的格式就能够保存。
  * 在我们的调研中发现,对于有些使用方,它们的配置可能会有比较复杂的格式,如xml, json,需要对格式做校验。
  * 还有一些使用方如DAL,不仅有特定的格式,而且对输入的值也需要进行校验后方可保存,如检查数据库、用户名和密码是否匹配。
  * 对于这类应用,Apollo支持应用方通过开放接口在Apollo进行配置的修改和发布,并且具备完善的授权和权限控制
* **部署简单**
  * 配置中心作为基础服务,可用性要求非常高,这就要求Apollo对外部依赖尽可能地少
  * 目前唯一的外部依赖是MySQL,所以部署非常简单,只要安装好Java和MySQL就可以让Apollo跑起来
  * Apollo还提供了打包脚本,一键就可以生成所有需要的安装包,并且支持自定义运行时参数
# Usage
  1. [Apollo使用指南](https://github.com/ctripcorp/apollo/wiki/Apollo%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
  2. [Java客户端使用指南](https://github.com/ctripcorp/apollo/wiki/Java%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
  3. [.Net客户端使用指南](https://github.com/ctripcorp/apollo/wiki/.Net%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97)
  4. [其它语言客户端接入指南](https://github.com/ctripcorp/apollo/wiki/%E5%85%B6%E5%AE%83%E8%AF%AD%E8%A8%80%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97)
  5. [Apollo开放平台接入指南](https://github.com/ctripcorp/apollo/wiki/Apollo%E5%BC%80%E6%94%BE%E5%B9%B3%E5%8F%B0)
  6. [Apollo使用场景和示例代码](https://github.com/ctripcorp/apollo-use-cases)
# Design
  * [Apollo配置中心设计](https://github.com/ctripcorp/apollo/wiki/Apollo%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E8%AE%BE%E8%AE%A1)
  * [Apollo核心概念之“Namespace”](https://github.com/ctripcorp/apollo/wiki/Apollo%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5%E4%B9%8B%E2%80%9CNamespace%E2%80%9D)
  * [Apollo配置中心架构剖析](https://mp.weixin.qq.com/s/-hUaQPzfsl9Lm3IqQW3VDQ)
  * [Apollo源码解析](http://www.iocoder.cn/categories/Apollo/)(据说Apollo非常适合作为初学者第一个通读源码学习的分布式中间件产品)
# Development
  * [Apollo开发指南](https://github.com/ctripcorp/apollo/wiki/Apollo%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97)
  * Code Styles
    * [Eclipse Code Style](https://github.com/ctripcorp/apollo/blob/master/apollo-buildtools/style/eclipse-java-google-style.xml)
    * [Intellij Code Style](https://github.com/ctripcorp/apollo/blob/master/apollo-buildtools/style/intellij-java-google-style.xml)
# Deployment
  * [Quick Start](https://github.com/ctripcorp/apollo/wiki/Quick-Start)
  * [分布式部署指南](https://github.com/ctripcorp/apollo/wiki/%E5%88%86%E5%B8%83%E5%BC%8F%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97)
# FAQ
  * [常见问题回答](https://github.com/ctripcorp/apollo/wiki/FAQ)
  * [部署&开发遇到的常见问题](https://github.com/ctripcorp/apollo/wiki/%E9%83%A8%E7%BD%B2&%E5%BC%80%E5%8F%91%E9%81%87%E5%88%B0%E7%9A%84%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
# Presentation
  * [携程开源配置中心Apollo的设计与实现](http://www.itdks.com/dakalive/detail/3420)
  * [Slides](http://techshow.ctrip.com/wp-content/uploads/2017/08/%E5%BC%80%E6%BA%90%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83Apollo%E7%9A%84%E8%AE%BE%E8%AE%A1%E4%B8%8E%E5%AE%9E%E7%8E%B0-%E6%90%BA%E7%A8%8B%E5%AE%8B%E9%A1%BA.pdf)
# Publication
  * [开源配置中心Apollo的设计与实现](http://www.infoq.com/cn/articles/open-source-configuration-center-apollo)
# Support
![tech-support-qq](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/tech-support-qq.png)
# Contribution
  * Source Code: https://github.com/ctripcorp/apollo
  * Issue Tracker: https://github.com/ctripcorp/apollo/issues
# License
The project is licensed under the [Apache 2 license](https://github.com/ctripcorp/apollo/blob/master/LICENSE).
# Known Users
> 按照登记顺序排序,更多接入公司,欢迎在[https://github.com/ctripcorp/apollo/issues/451](https://github.com/ctripcorp/apollo/issues/451)登记(仅供开源用户参考)
![携程](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/ctrip.png) 
![青石证券](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/bluestone.png) 
![沙绿](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/sagreen.png) 
![航旅纵横](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/umetrip.jpg) 
![58转转](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/zhuanzhuan.png) 
![蜂助手](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/phone580.png) 
![海南航空](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/hainan-airlines.png) 
![CVTE](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/cvte.png) 
![明博教育](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/mainbo.jpg) 
![麻袋理财](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/madailicai.png) 
![美行科技](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/mxnavi.jpg) 
![首展科技](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/fshows.jpg) 
![易微行](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/feezu.png) 
![人才加](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/rencaijia.png) 
![凯京集团](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/keking.png) 
![乐刻运动](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/leoao.png) 
![大疆](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/dji.png) 
![快看漫画](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/kkmh.png) 
![我来贷](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/wolaidai.png) 
![虚实软件](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/xsrj.png) 
![网易严选](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/yanxuan.png) 
![视觉中国](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/sjzg.png) 
![资产360](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/zc360.png) 
![亿咖通](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/ecarx.png) 
![5173](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/5173.png) 
![沪江](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/hujiang.png) 
![网易云基础服务](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/163yun.png) 
![现金巴士](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/cash-bus.png) 
![锤子科技](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/smartisan.png) 
![头等仓](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/toodc.png) 
![吉祥航空](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/juneyaoair.png) 
![263移动通信](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/263mobile.png) 
![投投金融](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/toutoujinrong.png) 
![每天健康](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/mytijian.png) 
![麦芽金服](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/maiyabank.png) 
![蜂向科技](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/fengunion.png) 
![即科金融](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/geex-logo.png) 
![贝壳网](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/beike.png) 
![有赞](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/youzan.png) 
![云集汇通](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/yunjihuitong.png) 
![犀牛瀚海科技](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/rhinotech.png) 
![农信互联](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/nxin.png) 
![蘑菇租房](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/mgzf.png) 
![狐狸金服](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/huli-logo.png) 
![漫道集团](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/mandao.png) 
![怪兽充电](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/enmonster.png) 
![南瓜租房](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/nanguazufang.png) 
![石投金融](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/shitoujinrong.png) 
![土巴兔](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/tubatu.png) 
![平安银行](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/payh_logo.png) 
![新新贷](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/xinxindai.png) 
![中国华戎科技集团](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/chrtc.png) 
![涂鸦智能](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/tuya_logo.png) 
![立创商城](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/szlcsc.jpg) 
![乐赚金服](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/hairongyi.png) 
![开心汽车](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/kxqc.png) 
![乐赚金服](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/ppcredit.png) 
![普元信息](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/primeton.png) 
![医帮管家](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/hoskeeper.png) 
![付啦信用卡管家](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/fula.png) 
![悠哉网](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/uzai.png) 
![梧桐诚选](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/91wutong.png) 
![拍拍贷](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/ppdai.png) 
![信用飞](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/xinyongfei.png) 
![丁香园](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/dxy.png) 
![国槐科技](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/ghtech.png) 
![亲宝宝](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/qbb.png) 
![华为视频直播](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/huawei_logo.png) 
![微播易](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/weiboyi.png) 
![欧飞](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/ofpay.png) 
![迷说](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/mishuo.png) 
![一下科技](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/yixia.png) 
![DaoCloud](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/daocloud.png) 
![汽摩交易所](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/cnvex.png) 
![好未来教育集团](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/100tal.png) 
![猎户星空](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/ainirobot.png) 
![卓健科技](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/zhuojian.png) 
![银江股份](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/enjoyor.png) 
![途虎养车](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/tuhu.png) 
![河姆渡](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/homedo.png) 
![新网银行](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/xwbank.png) 
![中旅安信云贷](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/ctspcl.png) 
![美柚](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/meiyou.png) 
![震坤行](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/zkh-logo.png) 
![万谷盛世](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/wgss.png) 
![铂涛旅行](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/plateno.png) 
![乐心](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/lifesense.png) 
![亿投传媒](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/reachmedia.png) 
![股先生](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/guxiansheng.png) 
![财学堂](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/caixuetang.png) 
![4399](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/4399.png) 
![汽车之家](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/autohome.png) 
![面包财经](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/mbcaijing.png) 
![虎扑](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/hoopchina.png) 
![搜狐汽车](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/sohu-auto.png) 
![量富征信](https://raw.githubusercontent.com/ctripcorp/apollo/master/doc/images/known-users/liangfuzhengxin.png) 

+ 115 - 0
apollo-adminservice/pom.xml

@ -0,0 +1,115 @@
<?xml version="1.0"  encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<parent>
		<groupId>com.yihu.jkzl</groupId>
		<artifactId>apollo</artifactId>
		<version>1.1.0-RELEASE</version>
		<relativePath>../pom.xml</relativePath>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>apollo-adminservice</artifactId>
	<name>Apollo AdminService</name>
	<properties>
		<github.path>${project.artifactId}</github.path>
	</properties>
	<dependencies>
		<!-- apollo -->
		<dependency>
			<groupId>com.yihu.jkzl</groupId>
			<artifactId>apollo-biz</artifactId>
		</dependency>
		<!-- end of apollo -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka-server</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<artifactId>
                        spring-cloud-starter-archaius
                    </artifactId>
					<groupId>org.springframework.cloud</groupId>
				</exclusion>
				<exclusion>
					<artifactId>spring-cloud-starter-ribbon</artifactId>
					<groupId>org.springframework.cloud</groupId>
				</exclusion>
				<exclusion>
					<artifactId>ribbon-eureka</artifactId>
					<groupId>com.netflix.ribbon</groupId>
				</exclusion>
				<exclusion>
					<artifactId>aws-java-sdk-core</artifactId>
					<groupId>com.amazonaws</groupId>
				</exclusion>
				<exclusion>
					<artifactId>aws-java-sdk-ec2</artifactId>
					<groupId>com.amazonaws</groupId>
				</exclusion>
				<exclusion>
					<artifactId>aws-java-sdk-autoscaling</artifactId>
					<groupId>com.amazonaws</groupId>
				</exclusion>
				<exclusion>
					<artifactId>aws-java-sdk-sts</artifactId>
					<groupId>com.amazonaws</groupId>
				</exclusion>
				<exclusion>
					<artifactId>aws-java-sdk-route53</artifactId>
					<groupId>com.amazonaws</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<executable>true</executable>
				</configuration>
			</plugin>
			<plugin>
				<groupId>com.spotify</groupId>
				<artifactId>docker-maven-plugin</artifactId>
				<version>0.4.13</version>
				<configuration>
					<imageName>${project.artifactId}</imageName>
					<dockerDirectory>src/main/docker</dockerDirectory>
					<resources>
						<resource>
							<targetPath>/</targetPath>
							<directory>${project.build.directory}</directory>
							<include>*.zip</include>
						</resource>
					</resources>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-assembly-plugin</artifactId>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>single</goal>
						</goals>
						<configuration>
							<finalName>${project.artifactId}-${project.version}-${package.environment}</finalName>
							<appendAssemblyId>false</appendAssemblyId>
							<descriptors>
								<descriptor>src/assembly/assembly-descriptor.xml</descriptor>
							</descriptors>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>
</project>

+ 55 - 0
apollo-adminservice/src/assembly/assembly-descriptor.xml

@ -0,0 +1,55 @@
<assembly
	xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
	<id>apollo-assembly</id>
	<formats>
		<format>zip</format>
	</formats>
	<includeBaseDirectory>false</includeBaseDirectory>
	<fileSets>
		<!--scripts -->
		<fileSet>
			<directory>src/main/scripts</directory>
			<outputDirectory>scripts</outputDirectory>
			<includes>
				<include>*.sh</include>
			</includes>
			<fileMode>0755</fileMode>
			<lineEnding>unix</lineEnding>
		</fileSet>
		<fileSet>
			<directory>src/main/config</directory>
			<outputDirectory>config</outputDirectory>
			<excludes>
				<exclude>apollo-adminservice.conf</exclude>
				<exclude>application-github.properties</exclude>
			</excludes>
			<lineEnding>unix</lineEnding>
		</fileSet>
		<fileSet>
			<directory>src/main/config</directory>
			<outputDirectory>/</outputDirectory>
			<includes>
				<include>apollo-adminservice.conf</include>
			</includes>
			<lineEnding>unix</lineEnding>
		</fileSet>
		<fileSet>
			<directory>target/classes</directory>
			<outputDirectory>/config</outputDirectory>
			<includes>
				<include>application-github.properties</include>
			</includes>
		</fileSet>
		<!--artifact -->
		<fileSet>
			<directory>target</directory>
			<outputDirectory>/</outputDirectory>
			<includes>
				<include>${project.artifactId}-*.jar</include>
			</includes>
			<fileMode>0755</fileMode>
		</fileSet>
	</fileSets>
</assembly>

+ 3 - 0
apollo-adminservice/src/main/config/apollo-adminservice.conf

@ -0,0 +1,3 @@
MODE=service
PID_FOLDER=.
LOG_FOLDER=/opt/logs/100003172/

+ 2 - 0
apollo-adminservice/src/main/config/app.properties

@ -0,0 +1,2 @@
appId=100003172
jdkVersion=1.8

+ 4 - 0
apollo-adminservice/src/main/config/application-github.properties

@ -0,0 +1,4 @@
# DataSource
spring.datasource.url = ${spring_datasource_url}
spring.datasource.username = ${spring_datasource_username}
spring.datasource.password = ${spring_datasource_password}

+ 28 - 0
apollo-adminservice/src/main/docker/Dockerfile

@ -0,0 +1,28 @@
# Dockerfile for apollo-adminservice
# Build with:
# docker build -t apollo-adminservice .
# Run with:
# docker run -p 8090:8090 -d -v /tmp/logs:/opt/logs --name apollo-adminservice apollo-adminservice
FROM openjdk:8-jre-alpine
MAINTAINER ameizi <sxyx2008@163.com>
ENV VERSION 1.1.0-RELEASE
RUN echo "http://mirrors.aliyun.com/alpine/v3.6/main" > /etc/apk/repositories \
    && echo "http://mirrors.aliyun.com/alpine/v3.6/community" >> /etc/apk/repositories \
    && apk update upgrade \
    && apk add --no-cache procps unzip curl bash tzdata \
    && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
    && echo "Asia/Shanghai" > /etc/timezone
ADD apollo-adminservice-${VERSION}-github.zip /apollo-adminservice/apollo-adminservice-${VERSION}-github.zip
RUN unzip /apollo-adminservice/apollo-adminservice-${VERSION}-github.zip -d /apollo-adminservice \
    && rm -rf /apollo-adminservice/apollo-adminservice-${VERSION}-github.zip \
    && sed -i '$d' /apollo-adminservice/scripts/startup.sh \
    && echo "tail -f /dev/null" >> /apollo-adminservice/scripts/startup.sh
EXPOSE 8090
CMD ["/apollo-adminservice/scripts/startup.sh"]

+ 33 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceApplication.java

@ -0,0 +1,33 @@
package com.ctrip.framework.apollo.adminservice;
import com.ctrip.framework.apollo.biz.ApolloBizConfig;
import com.ctrip.framework.apollo.common.ApolloCommonConfig;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.system.ApplicationPidFileWriter;
import org.springframework.boot.system.EmbeddedServerPortFileWriter;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableAspectJAutoProxy
@EnableEurekaClient
@Configuration
@PropertySource(value = {"classpath:adminservice.properties"})
@EnableAutoConfiguration
@EnableTransactionManagement
@ComponentScan(basePackageClasses = {ApolloCommonConfig.class,
    ApolloBizConfig.class,
    AdminServiceApplication.class})
public class AdminServiceApplication {
  public static void main(String[] args) {
    ConfigurableApplicationContext context =
        new SpringApplicationBuilder(AdminServiceApplication.class).run(args);
    context.addApplicationListener(new ApplicationPidFileWriter());
    context.addApplicationListener(new EmbeddedServerPortFileWriter());
  }
}

+ 32 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceHealthIndicator.java

@ -0,0 +1,32 @@
package com.ctrip.framework.apollo.adminservice;
import com.ctrip.framework.apollo.biz.service.AppService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;
@Component
public class AdminServiceHealthIndicator implements HealthIndicator {
  @Autowired
  private AppService appService;
  @Override
  public Health health() {
    int errorCode = check();
    if (errorCode != 0) {
      return Health.down().withDetail("Error Code", errorCode).build();
    }
    return Health.up().build();
  }
  private int check() {
    PageRequest pageable = new PageRequest(0, 1);
    appService.findAll(pageable);
    return 0;
  }
}

+ 147 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/NamespaceAcquireLockAspect.java

@ -0,0 +1,147 @@
package com.ctrip.framework.apollo.adminservice.aop;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.NamespaceLock;
import com.ctrip.framework.apollo.biz.service.ItemService;
import com.ctrip.framework.apollo.biz.service.NamespaceLockService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.ServiceException;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Component;
/**
 * 一个namespace在一次发布中只能允许一个人修改配置
 * 通过数据库lock表来实现
 */
@Aspect
@Component
public class NamespaceAcquireLockAspect {
  private static final Logger logger = LoggerFactory.getLogger(NamespaceAcquireLockAspect.class);
  @Autowired
  private NamespaceLockService namespaceLockService;
  @Autowired
  private NamespaceService namespaceService;
  @Autowired
  private ItemService itemService;
  @Autowired
  private BizConfig bizConfig;
  //create item
  @Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, item, ..)")
  public void requireLockAdvice(String appId, String clusterName, String namespaceName,
                                ItemDTO item) {
    acquireLock(appId, clusterName, namespaceName, item.getDataChangeLastModifiedBy());
  }
  //update item
  @Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, itemId, item, ..)")
  public void requireLockAdvice(String appId, String clusterName, String namespaceName, long itemId,
                                ItemDTO item) {
    acquireLock(appId, clusterName, namespaceName, item.getDataChangeLastModifiedBy());
  }
  //update by change set
  @Before("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, changeSet, ..)")
  public void requireLockAdvice(String appId, String clusterName, String namespaceName,
                                ItemChangeSets changeSet) {
    acquireLock(appId, clusterName, namespaceName, changeSet.getDataChangeLastModifiedBy());
  }
  //delete item
  @Before("@annotation(PreAcquireNamespaceLock) && args(itemId, operator, ..)")
  public void requireLockAdvice(long itemId, String operator) {
    Item item = itemService.findOne(itemId);
    if (item == null){
      throw new BadRequestException("item not exist.");
    }
    acquireLock(item.getNamespaceId(), operator);
  }
  void acquireLock(String appId, String clusterName, String namespaceName,
                           String currentUser) {
    if (bizConfig.isNamespaceLockSwitchOff()) {
      return;
    }
    Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
    acquireLock(namespace, currentUser);
  }
  void acquireLock(long namespaceId, String currentUser) {
    if (bizConfig.isNamespaceLockSwitchOff()) {
      return;
    }
    Namespace namespace = namespaceService.findOne(namespaceId);
    acquireLock(namespace, currentUser);
  }
  private void acquireLock(Namespace namespace, String currentUser) {
    if (namespace == null) {
      throw new BadRequestException("namespace not exist.");
    }
    long namespaceId = namespace.getId();
    NamespaceLock namespaceLock = namespaceLockService.findLock(namespaceId);
    if (namespaceLock == null) {
      try {
        tryLock(namespaceId, currentUser);
        //lock success
      } catch (DataIntegrityViolationException e) {
        //lock fail
        namespaceLock = namespaceLockService.findLock(namespaceId);
        checkLock(namespace, namespaceLock, currentUser);
      } catch (Exception e) {
        logger.error("try lock error", e);
        throw e;
      }
    } else {
      //check lock owner is current user
      checkLock(namespace, namespaceLock, currentUser);
    }
  }
  private void tryLock(long namespaceId, String user) {
    NamespaceLock lock = new NamespaceLock();
    lock.setNamespaceId(namespaceId);
    lock.setDataChangeCreatedBy(user);
    lock.setDataChangeLastModifiedBy(user);
    namespaceLockService.tryLock(lock);
  }
  private void checkLock(Namespace namespace, NamespaceLock namespaceLock,
                         String currentUser) {
    if (namespaceLock == null) {
      throw new ServiceException(
          String.format("Check lock for %s failed, please retry.", namespace.getNamespaceName()));
    }
    String lockOwner = namespaceLock.getDataChangeCreatedBy();
    if (!lockOwner.equals(currentUser)) {
      throw new BadRequestException(
          "namespace:" + namespace.getNamespaceName() + " is modified by " + lockOwner);
    }
  }
}

+ 157 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/NamespaceUnlockAspect.java

@ -0,0 +1,157 @@
package com.ctrip.framework.apollo.adminservice.aop;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.service.ItemService;
import com.ctrip.framework.apollo.biz.service.NamespaceLockService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.constants.GsonType;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
 * unlock namespace if is redo operation.
 * --------------------------------------------
 * For example: If namespace has a item K1 = v1
 * --------------------------------------------
 * First operate: change k1 = v2 (lock namespace)
 * Second operate: change k1 = v1 (unlock namespace)
 */
@Aspect
@Component
public class NamespaceUnlockAspect {
  private Gson gson = new Gson();
  @Autowired
  private NamespaceLockService namespaceLockService;
  @Autowired
  private NamespaceService namespaceService;
  @Autowired
  private ItemService itemService;
  @Autowired
  private ReleaseService releaseService;
  @Autowired
  private BizConfig bizConfig;
  //create item
  @After("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, item, ..)")
  public void requireLockAdvice(String appId, String clusterName, String namespaceName,
                                ItemDTO item) {
    tryUnlock(namespaceService.findOne(appId, clusterName, namespaceName));
  }
  //update item
  @After("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, itemId, item, ..)")
  public void requireLockAdvice(String appId, String clusterName, String namespaceName, long itemId,
                                ItemDTO item) {
    tryUnlock(namespaceService.findOne(appId, clusterName, namespaceName));
  }
  //update by change set
  @After("@annotation(PreAcquireNamespaceLock) && args(appId, clusterName, namespaceName, changeSet, ..)")
  public void requireLockAdvice(String appId, String clusterName, String namespaceName,
                                ItemChangeSets changeSet) {
    tryUnlock(namespaceService.findOne(appId, clusterName, namespaceName));
  }
  //delete item
  @After("@annotation(PreAcquireNamespaceLock) && args(itemId, operator, ..)")
  public void requireLockAdvice(long itemId, String operator) {
    Item item = itemService.findOne(itemId);
    if (item == null) {
      throw new BadRequestException("item not exist.");
    }
    tryUnlock(namespaceService.findOne(item.getNamespaceId()));
  }
  private void tryUnlock(Namespace namespace) {
    if (bizConfig.isNamespaceLockSwitchOff()) {
      return;
    }
    if (!isModified(namespace)) {
      namespaceLockService.unlock(namespace.getId());
    }
  }
  boolean isModified(Namespace namespace) {
    Release release = releaseService.findLatestActiveRelease(namespace);
    List<Item> items = itemService.findItemsWithoutOrdered(namespace.getId());
    if (release == null) {
      return hasNormalItems(items);
    }
    Map<String, String> releasedConfiguration = gson.fromJson(release.getConfigurations(), GsonType.CONFIG);
    Map<String, String> configurationFromItems = generateConfigurationFromItems(namespace, items);
    MapDifference<String, String> difference = Maps.difference(releasedConfiguration, configurationFromItems);
    return !difference.areEqual();
  }
  private boolean hasNormalItems(List<Item> items) {
    for (Item item : items) {
      if (!StringUtils.isEmpty(item.getKey())) {
        return true;
      }
    }
    return false;
  }
  private Map<String, String> generateConfigurationFromItems(Namespace namespace, List<Item> namespaceItems) {
    Map<String, String> configurationFromItems = Maps.newHashMap();
    Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
    //parent namespace
    if (parentNamespace == null) {
      generateMapFromItems(namespaceItems, configurationFromItems);
    } else {//child namespace
      Release parentRelease = releaseService.findLatestActiveRelease(parentNamespace);
      if (parentRelease != null) {
        configurationFromItems = gson.fromJson(parentRelease.getConfigurations(), GsonType.CONFIG);
      }
      generateMapFromItems(namespaceItems, configurationFromItems);
    }
    return configurationFromItems;
  }
  private Map<String, String> generateMapFromItems(List<Item> items, Map<String, String> configurationFromItems) {
    for (Item item : items) {
      String key = item.getKey();
      if (StringUtils.isBlank(key)) {
        continue;
      }
      configurationFromItems.put(key, item.getValue());
    }
    return configurationFromItems;
  }
}

+ 15 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/PreAcquireNamespaceLock.java

@ -0,0 +1,15 @@
package com.ctrip.framework.apollo.adminservice.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 标识方法需要获取到namespace的lock才能执行
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAcquireNamespaceLock {
}

+ 94 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AppController.java

@ -0,0 +1,94 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.service.AdminService;
import com.ctrip.framework.apollo.biz.service.AppService;
import com.ctrip.framework.apollo.common.dto.AppDTO;
import com.ctrip.framework.apollo.common.entity.App;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Objects;
@RestController
public class AppController {
  @Autowired
  private AppService appService;
  @Autowired
  private AdminService adminService;
  @RequestMapping(path = "/apps", method = RequestMethod.POST)
  public AppDTO create(@RequestBody AppDTO dto) {
    if (!InputValidator.isValidClusterNamespace(dto.getAppId())) {
      throw new BadRequestException(String.format("AppId格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
    }
    App entity = BeanUtils.transfrom(App.class, dto);
    App managedEntity = appService.findOne(entity.getAppId());
    if (managedEntity != null) {
      throw new BadRequestException("app already exist.");
    }
    entity = adminService.createNewApp(entity);
    dto = BeanUtils.transfrom(AppDTO.class, entity);
    return dto;
  }
  @RequestMapping(value = "/apps/{appId:.+}", method = RequestMethod.DELETE)
  public void delete(@PathVariable("appId") String appId, @RequestParam String operator) {
    App entity = appService.findOne(appId);
    if (entity == null) {
      throw new NotFoundException("app not found for appId " + appId);
    }
    adminService.deleteApp(entity, operator);
  }
  @RequestMapping(value = "/apps/{appId:.+}", method = RequestMethod.PUT)
  public void update(@PathVariable String appId, @RequestBody App app) {
    if (!Objects.equals(appId, app.getAppId())) {
      throw new BadRequestException("The App Id of path variable and request body is different");
    }
    appService.update(app);
  }
  @RequestMapping(value = "/apps", method = RequestMethod.GET)
  public List<AppDTO> find(@RequestParam(value = "name", required = false) String name,
                           Pageable pageable) {
    List<App> app = null;
    if (StringUtils.isBlank(name)) {
      app = appService.findAll(pageable);
    } else {
      app = appService.findByName(name);
    }
    return BeanUtils.batchTransform(AppDTO.class, app);
  }
  @RequestMapping(value = "/apps/{appId:.+}", method = RequestMethod.GET)
  public AppDTO get(@PathVariable("appId") String appId) {
    App app = appService.findOne(appId);
    if (app == null) {
      throw new NotFoundException("app not found for appId " + appId);
    }
    return BeanUtils.transfrom(AppDTO.class, app);
  }
  @RequestMapping(value = "/apps/{appId}/unique", method = RequestMethod.GET)
  public boolean isAppIdUnique(@PathVariable("appId") String appId) {
    return appService.isAppIdUnique(appId);
  }
}

+ 75 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AppNamespaceController.java

@ -0,0 +1,75 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.common.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class AppNamespaceController {
  @Autowired
  private AppNamespaceService appNamespaceService;
  @Autowired
  private NamespaceService namespaceService;
  @RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
  public AppNamespaceDTO create(@RequestBody AppNamespaceDTO appNamespace) {
    AppNamespace entity = BeanUtils.transfrom(AppNamespace.class, appNamespace);
    AppNamespace managedEntity = appNamespaceService.findOne(entity.getAppId(), entity.getName());
    if (managedEntity != null) {
      throw new BadRequestException("app namespaces already exist.");
    }
    if (StringUtils.isEmpty(entity.getFormat())){
      entity.setFormat(ConfigFileFormat.Properties.getValue());
    }
    entity = appNamespaceService.createAppNamespace(entity);
    return BeanUtils.transfrom(AppNamespaceDTO.class, entity);
  }
  @RequestMapping(value = "/apps/{appId}/appnamespaces/{namespaceName:.+}", method = RequestMethod.DELETE)
  public void delete(@PathVariable("appId") String appId, @PathVariable("namespaceName") String namespaceName,
      @RequestParam String operator) {
    AppNamespace entity = appNamespaceService.findOne(appId, namespaceName);
    if (entity == null) {
      throw new BadRequestException("app namespace not found for appId: " + appId + " namespace: " + namespaceName);
    }
    appNamespaceService.deleteAppNamespace(entity, operator);
  }
  @RequestMapping(value = "/appnamespaces/{publicNamespaceName}/namespaces", method = RequestMethod.GET)
  public List<NamespaceDTO> findPublicAppNamespaceAllNamespaces(@PathVariable String publicNamespaceName, Pageable pageable) {
    List<Namespace> namespaces = namespaceService.findPublicAppNamespaceAllNamespaces(publicNamespaceName, pageable);
    return BeanUtils.batchTransform(NamespaceDTO.class, namespaces);
  }
  @RequestMapping(value = "/appnamespaces/{publicNamespaceName}/associated-namespaces/count", method = RequestMethod.GET)
  public int countPublicAppNamespaceAssociatedNamespaces(@PathVariable String publicNamespaceName) {
    return namespaceService.countPublicAppNamespaceAssociatedNamespaces(publicNamespaceName);
  }
}

+ 82 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ClusterController.java

@ -0,0 +1,82 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.service.ClusterService;
import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class ClusterController {
  @Autowired
  private ClusterService clusterService;
  @RequestMapping(path = "/apps/{appId}/clusters", method = RequestMethod.POST)
  public ClusterDTO create(@PathVariable("appId") String appId,
                           @RequestParam(value = "autoCreatePrivateNamespace", defaultValue = "true") boolean autoCreatePrivateNamespace,
                           @RequestBody ClusterDTO dto) {
    if (!InputValidator.isValidClusterNamespace(dto.getName())) {
      throw new BadRequestException(String.format("Cluster格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
    }
    Cluster entity = BeanUtils.transfrom(Cluster.class, dto);
    Cluster managedEntity = clusterService.findOne(appId, entity.getName());
    if (managedEntity != null) {
      throw new BadRequestException("cluster already exist.");
    }
    if (autoCreatePrivateNamespace) {
      entity = clusterService.saveWithInstanceOfAppNamespaces(entity);
    } else {
      entity = clusterService.saveWithoutInstanceOfAppNamespaces(entity);
    }
    dto = BeanUtils.transfrom(ClusterDTO.class, entity);
    return dto;
  }
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName:.+}", method = RequestMethod.DELETE)
  public void delete(@PathVariable("appId") String appId,
                     @PathVariable("clusterName") String clusterName, @RequestParam String operator) {
    Cluster entity = clusterService.findOne(appId, clusterName);
    if (entity == null) {
      throw new NotFoundException("cluster not found for clusterName " + clusterName);
    }
    clusterService.delete(entity.getId(), operator);
  }
  @RequestMapping(value = "/apps/{appId}/clusters", method = RequestMethod.GET)
  public List<ClusterDTO> find(@PathVariable("appId") String appId) {
    List<Cluster> clusters = clusterService.findParentClusters(appId);
    return BeanUtils.batchTransform(ClusterDTO.class, clusters);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName:.+}", method = RequestMethod.GET)
  public ClusterDTO get(@PathVariable("appId") String appId,
                        @PathVariable("clusterName") String clusterName) {
    Cluster cluster = clusterService.findOne(appId, clusterName);
    if (cluster == null) {
      throw new NotFoundException("cluster not found for name " + clusterName);
    }
    return BeanUtils.transfrom(ClusterDTO.class, cluster);
  }
  @RequestMapping(value = "/apps/{appId}/cluster/{clusterName}/unique", method = RequestMethod.GET)
  public boolean isAppIdUnique(@PathVariable("appId") String appId,
                               @PathVariable("clusterName") String clusterName) {
    return clusterService.isClusterNameUnique(appId, clusterName);
  }
}

+ 32 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/CommitController.java

@ -0,0 +1,32 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.service.CommitService;
import com.ctrip.framework.apollo.common.dto.CommitDTO;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class CommitController {
  @Autowired
  private CommitService commitService;
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/commit", method = RequestMethod.GET)
  public List<CommitDTO> find(@PathVariable String appId, @PathVariable String clusterName,
                              @PathVariable String namespaceName, Pageable pageable){
    List<Commit> commits = commitService.find(appId, clusterName, namespaceName, pageable);
    return BeanUtils.batchTransform(CommitDTO.class, commits);
  }
}

+ 15 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/IndexController.java

@ -0,0 +1,15 @@
package com.ctrip.framework.apollo.adminservice.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "/")
public class IndexController {
  @RequestMapping(path = "", method = RequestMethod.GET)
  public String index() {
    return "apollo-adminservice";
  }
}

+ 189 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/InstanceConfigController.java

@ -0,0 +1,189 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.biz.entity.Instance;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.service.InstanceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.common.dto.InstanceConfigDTO;
import com.ctrip.framework.apollo.common.dto.InstanceDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@RestController
@RequestMapping("/instances")
public class InstanceConfigController {
  private static final Splitter RELEASES_SPLITTER = Splitter.on(",").omitEmptyStrings()
      .trimResults();
  @Autowired
  private ReleaseService releaseService;
  @Autowired
  private InstanceService instanceService;
  @RequestMapping(value = "/by-release", method = RequestMethod.GET)
  public PageDTO<InstanceDTO> getByRelease(@RequestParam("releaseId") long releaseId,
                                           Pageable pageable) {
    Release release = releaseService.findOne(releaseId);
    if (release == null) {
      throw new NotFoundException(String.format("release not found for %s", releaseId));
    }
    Page<InstanceConfig> instanceConfigsPage = instanceService.findActiveInstanceConfigsByReleaseKey
        (release.getReleaseKey(), pageable);
    List<InstanceDTO> instanceDTOs = Collections.emptyList();
    if (instanceConfigsPage.hasContent()) {
      Multimap<Long, InstanceConfig> instanceConfigMap = HashMultimap.create();
      Set<String> otherReleaseKeys = Sets.newHashSet();
      for (InstanceConfig instanceConfig : instanceConfigsPage.getContent()) {
        instanceConfigMap.put(instanceConfig.getInstanceId(), instanceConfig);
        otherReleaseKeys.add(instanceConfig.getReleaseKey());
      }
      Set<Long> instanceIds = instanceConfigMap.keySet();
      List<Instance> instances = instanceService.findInstancesByIds(instanceIds);
      if (!CollectionUtils.isEmpty(instances)) {
        instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances);
      }
      for (InstanceDTO instanceDTO : instanceDTOs) {
        Collection<InstanceConfig> configs = instanceConfigMap.get(instanceDTO.getId());
        List<InstanceConfigDTO> configDTOs = configs.stream().map(instanceConfig -> {
          InstanceConfigDTO instanceConfigDTO = new InstanceConfigDTO();
          //to save some space
          instanceConfigDTO.setRelease(null);
          instanceConfigDTO.setReleaseDeliveryTime(instanceConfig.getReleaseDeliveryTime());
          instanceConfigDTO.setDataChangeLastModifiedTime(instanceConfig
              .getDataChangeLastModifiedTime());
          return instanceConfigDTO;
        }).collect(Collectors.toList());
        instanceDTO.setConfigs(configDTOs);
      }
    }
    return new PageDTO<>(instanceDTOs, pageable, instanceConfigsPage.getTotalElements());
  }
  @RequestMapping(value = "/by-namespace-and-releases-not-in", method = RequestMethod.GET)
  public List<InstanceDTO> getByReleasesNotIn(@RequestParam("appId") String appId,
                                              @RequestParam("clusterName") String clusterName,
                                              @RequestParam("namespaceName") String namespaceName,
                                              @RequestParam("releaseIds") String releaseIds) {
    Set<Long> releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
        .collect(Collectors.toSet());
    List<Release> releases = releaseService.findByReleaseIds(releaseIdSet);
    if (CollectionUtils.isEmpty(releases)) {
      throw new NotFoundException(String.format("releases not found for %s", releaseIds));
    }
    Set<String> releaseKeys = releases.stream().map(Release::getReleaseKey).collect(Collectors
        .toSet());
    List<InstanceConfig> instanceConfigs = instanceService
        .findInstanceConfigsByNamespaceWithReleaseKeysNotIn(appId, clusterName, namespaceName,
            releaseKeys);
    Multimap<Long, InstanceConfig> instanceConfigMap = HashMultimap.create();
    Set<String> otherReleaseKeys = Sets.newHashSet();
    for (InstanceConfig instanceConfig : instanceConfigs) {
      instanceConfigMap.put(instanceConfig.getInstanceId(), instanceConfig);
      otherReleaseKeys.add(instanceConfig.getReleaseKey());
    }
    List<Instance> instances = instanceService.findInstancesByIds(instanceConfigMap.keySet());
    if (CollectionUtils.isEmpty(instances)) {
      return Collections.emptyList();
    }
    List<InstanceDTO> instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances);
    List<Release> otherReleases = releaseService.findByReleaseKeys(otherReleaseKeys);
    Map<String, ReleaseDTO> releaseMap = Maps.newHashMap();
    for (Release release : otherReleases) {
      //unset configurations to save space
      release.setConfigurations(null);
      ReleaseDTO releaseDTO = BeanUtils.transfrom(ReleaseDTO.class, release);
      releaseMap.put(release.getReleaseKey(), releaseDTO);
    }
    for (InstanceDTO instanceDTO : instanceDTOs) {
      Collection<InstanceConfig> configs = instanceConfigMap.get(instanceDTO.getId());
      List<InstanceConfigDTO> configDTOs = configs.stream().map(instanceConfig -> {
        InstanceConfigDTO instanceConfigDTO = new InstanceConfigDTO();
        instanceConfigDTO.setRelease(releaseMap.get(instanceConfig.getReleaseKey()));
        instanceConfigDTO.setReleaseDeliveryTime(instanceConfig.getReleaseDeliveryTime());
        instanceConfigDTO.setDataChangeLastModifiedTime(instanceConfig
            .getDataChangeLastModifiedTime());
        return instanceConfigDTO;
      }).collect(Collectors.toList());
      instanceDTO.setConfigs(configDTOs);
    }
    return instanceDTOs;
  }
  @RequestMapping(value = "/by-namespace", method = RequestMethod.GET)
  public PageDTO<InstanceDTO> getInstancesByNamespace(
      @RequestParam("appId") String appId, @RequestParam("clusterName") String clusterName,
      @RequestParam("namespaceName") String namespaceName,
      @RequestParam(value = "instanceAppId", required = false) String instanceAppId,
      Pageable pageable) {
    Page<Instance> instances;
    if (Strings.isNullOrEmpty(instanceAppId)) {
      instances = instanceService.findInstancesByNamespace(appId, clusterName,
          namespaceName, pageable);
    } else {
      instances = instanceService.findInstancesByNamespaceAndInstanceAppId(instanceAppId, appId,
          clusterName, namespaceName, pageable);
    }
    List<InstanceDTO> instanceDTOs = BeanUtils.batchTransform(InstanceDTO.class, instances.getContent());
    return new PageDTO<>(instanceDTOs, pageable, instances.getTotalElements());
  }
  @RequestMapping(value = "/by-namespace/count", method = RequestMethod.GET)
  public long getInstancesCountByNamespace(@RequestParam("appId") String appId,
                                          @RequestParam("clusterName") String clusterName,
                                          @RequestParam("namespaceName") String namespaceName) {
    Page<Instance> instances = instanceService.findInstancesByNamespace(appId, clusterName,
        namespaceName, new PageRequest(0, 1));
    return instances.getTotalElements();
  }
}

+ 157 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemController.java

@ -0,0 +1,157 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.adminservice.aop.PreAcquireNamespaceLock;
import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.service.CommitService;
import com.ctrip.framework.apollo.biz.service.ItemService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class ItemController {
  @Autowired
  private ItemService itemService;
  @Autowired
  private NamespaceService namespaceService;
  @Autowired
  private CommitService commitService;
  @PreAcquireNamespaceLock
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.POST)
  public ItemDTO create(@PathVariable("appId") String appId,
                        @PathVariable("clusterName") String clusterName,
                        @PathVariable("namespaceName") String namespaceName, @RequestBody ItemDTO dto) {
    Item entity = BeanUtils.transfrom(Item.class, dto);
    ConfigChangeContentBuilder builder = new ConfigChangeContentBuilder();
    Item managedEntity = itemService.findOne(appId, clusterName, namespaceName, entity.getKey());
    if (managedEntity != null) {
      throw new BadRequestException("item already exist");
    } else {
      entity = itemService.save(entity);
      builder.createItem(entity);
    }
    dto = BeanUtils.transfrom(ItemDTO.class, entity);
    Commit commit = new Commit();
    commit.setAppId(appId);
    commit.setClusterName(clusterName);
    commit.setNamespaceName(namespaceName);
    commit.setChangeSets(builder.build());
    commit.setDataChangeCreatedBy(dto.getDataChangeLastModifiedBy());
    commit.setDataChangeLastModifiedBy(dto.getDataChangeLastModifiedBy());
    commitService.save(commit);
    return dto;
  }
  @PreAcquireNamespaceLock
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/{itemId}", method = RequestMethod.PUT)
  public ItemDTO update(@PathVariable("appId") String appId,
                        @PathVariable("clusterName") String clusterName,
                        @PathVariable("namespaceName") String namespaceName,
                        @PathVariable("itemId") long itemId,
                        @RequestBody ItemDTO itemDTO) {
    Item entity = BeanUtils.transfrom(Item.class, itemDTO);
    ConfigChangeContentBuilder builder = new ConfigChangeContentBuilder();
    Item managedEntity = itemService.findOne(itemId);
    if (managedEntity == null) {
      throw new BadRequestException("item not exist");
    }
    Item beforeUpdateItem = BeanUtils.transfrom(Item.class, managedEntity);
    //protect. only value,comment,lastModifiedBy can be modified
    managedEntity.setValue(entity.getValue());
    managedEntity.setComment(entity.getComment());
    managedEntity.setDataChangeLastModifiedBy(entity.getDataChangeLastModifiedBy());
    entity = itemService.update(managedEntity);
    builder.updateItem(beforeUpdateItem, entity);
    itemDTO = BeanUtils.transfrom(ItemDTO.class, entity);
    if (builder.hasContent()) {
      Commit commit = new Commit();
      commit.setAppId(appId);
      commit.setClusterName(clusterName);
      commit.setNamespaceName(namespaceName);
      commit.setChangeSets(builder.build());
      commit.setDataChangeCreatedBy(itemDTO.getDataChangeLastModifiedBy());
      commit.setDataChangeLastModifiedBy(itemDTO.getDataChangeLastModifiedBy());
      commitService.save(commit);
    }
    return itemDTO;
  }
  @PreAcquireNamespaceLock
  @RequestMapping(path = "/items/{itemId}", method = RequestMethod.DELETE)
  public void delete(@PathVariable("itemId") long itemId, @RequestParam String operator) {
    Item entity = itemService.findOne(itemId);
    if (entity == null) {
      throw new NotFoundException("item not found for itemId " + itemId);
    }
    itemService.delete(entity.getId(), operator);
    Namespace namespace = namespaceService.findOne(entity.getNamespaceId());
    Commit commit = new Commit();
    commit.setAppId(namespace.getAppId());
    commit.setClusterName(namespace.getClusterName());
    commit.setNamespaceName(namespace.getNamespaceName());
    commit.setChangeSets(new ConfigChangeContentBuilder().deleteItem(entity).build());
    commit.setDataChangeCreatedBy(operator);
    commit.setDataChangeLastModifiedBy(operator);
    commitService.save(commit);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.GET)
  public List<ItemDTO> findItems(@PathVariable("appId") String appId,
                                 @PathVariable("clusterName") String clusterName,
                                 @PathVariable("namespaceName") String namespaceName) {
    return BeanUtils.batchTransform(ItemDTO.class, itemService.findItemsWithOrdered(appId, clusterName, namespaceName));
  }
  @RequestMapping(value = "/items/{itemId}", method = RequestMethod.GET)
  public ItemDTO get(@PathVariable("itemId") long itemId) {
    Item item = itemService.findOne(itemId);
    if (item == null) {
      throw new NotFoundException("item not found for itemId " + itemId);
    }
    return BeanUtils.transfrom(ItemDTO.class, item);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/{key:.+}", method = RequestMethod.GET)
  public ItemDTO get(@PathVariable("appId") String appId,
                     @PathVariable("clusterName") String clusterName,
                     @PathVariable("namespaceName") String namespaceName, @PathVariable("key") String key) {
    Item item = itemService.findOne(appId, clusterName, namespaceName, key);
    if (item == null) {
      throw new NotFoundException(
          String.format("item not found for %s %s %s %s", appId, clusterName, namespaceName, key));
    }
    return BeanUtils.transfrom(ItemDTO.class, item);
  }
}

+ 33 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemSetController.java

@ -0,0 +1,33 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.adminservice.aop.PreAcquireNamespaceLock;
import com.ctrip.framework.apollo.biz.service.ItemSetService;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ItemSetController {
  @Autowired
  private ItemSetService itemSetService;
  @PreAcquireNamespaceLock
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/itemset", method = RequestMethod.POST)
  public ResponseEntity<Void> create(@PathVariable String appId, @PathVariable String clusterName,
                                     @PathVariable String namespaceName, @RequestBody ItemChangeSets changeSet) {
    itemSetService.updateSet(appId, clusterName, namespaceName, changeSet);
    return ResponseEntity.status(HttpStatus.OK).build();
  }
}

+ 145 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceBranchController.java

@ -0,0 +1,145 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.message.MessageSender;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.service.NamespaceBranchService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.GrayReleaseRuleItemTransformer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NamespaceBranchController {
  @Autowired
  private MessageSender messageSender;
  @Autowired
  private NamespaceBranchService namespaceBranchService;
  @Autowired
  private NamespaceService namespaceService;
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches", method = RequestMethod.POST)
  public NamespaceDTO createBranch(@PathVariable String appId,
                                   @PathVariable String clusterName,
                                   @PathVariable String namespaceName,
                                   @RequestParam("operator") String operator) {
    checkNamespace(appId, clusterName, namespaceName);
    Namespace createdBranch = namespaceBranchService.createBranch(appId, clusterName, namespaceName, operator);
    return BeanUtils.transfrom(NamespaceDTO.class, createdBranch);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules",
      method = RequestMethod.GET)
  public GrayReleaseRuleDTO findBranchGrayRules(@PathVariable String appId,
                                                @PathVariable String clusterName,
                                                @PathVariable String namespaceName,
                                                @PathVariable String branchName) {
    checkBranch(appId, clusterName, namespaceName, branchName);
    GrayReleaseRule rules = namespaceBranchService.findBranchGrayRules(appId, clusterName, namespaceName, branchName);
    if (rules == null) {
      return null;
    }
    GrayReleaseRuleDTO ruleDTO =
        new GrayReleaseRuleDTO(rules.getAppId(), rules.getClusterName(), rules.getNamespaceName(),
                               rules.getBranchName());
    ruleDTO.setReleaseId(rules.getReleaseId());
    ruleDTO.setRuleItems(GrayReleaseRuleItemTransformer.batchTransformFromJSON(rules.getRules()));
    return ruleDTO;
  }
  @Transactional
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}/rules", method = RequestMethod.PUT)
  public void updateBranchGrayRules(@PathVariable String appId, @PathVariable String clusterName,
                                    @PathVariable String namespaceName, @PathVariable String branchName,
                                    @RequestBody GrayReleaseRuleDTO newRuleDto) {
    checkBranch(appId, clusterName, namespaceName, branchName);
    GrayReleaseRule newRules = BeanUtils.transfrom(GrayReleaseRule.class, newRuleDto);
    newRules.setRules(GrayReleaseRuleItemTransformer.batchTransformToJSON(newRuleDto.getRuleItems()));
    newRules.setBranchStatus(NamespaceBranchStatus.ACTIVE);
    namespaceBranchService.updateBranchGrayRules(appId, clusterName, namespaceName, branchName, newRules);
    messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
                              Topics.APOLLO_RELEASE_TOPIC);
  }
  @Transactional
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches/{branchName}", method = RequestMethod.DELETE)
  public void deleteBranch(@PathVariable String appId, @PathVariable String clusterName,
                           @PathVariable String namespaceName, @PathVariable String branchName,
                           @RequestParam("operator") String operator) {
    checkBranch(appId, clusterName, namespaceName, branchName);
    namespaceBranchService
        .deleteBranch(appId, clusterName, namespaceName, branchName, NamespaceBranchStatus.DELETED, operator);
    messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
                              Topics.APOLLO_RELEASE_TOPIC);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/branches", method = RequestMethod.GET)
  public NamespaceDTO loadNamespaceBranch(@PathVariable String appId, @PathVariable String clusterName,
                                          @PathVariable String namespaceName) {
    checkNamespace(appId, clusterName, namespaceName);
    Namespace childNamespace = namespaceBranchService.findBranch(appId, clusterName, namespaceName);
    if (childNamespace == null) {
      return null;
    }
    return BeanUtils.transfrom(NamespaceDTO.class, childNamespace);
  }
  private void checkBranch(String appId, String clusterName, String namespaceName, String branchName) {
    //1. check parent namespace
    checkNamespace(appId, clusterName, namespaceName);
    //2. check child namespace
    Namespace childNamespace = namespaceService.findOne(appId, branchName, namespaceName);
    if (childNamespace == null) {
      throw new BadRequestException(String.format("Namespace's branch not exist. AppId = %s, ClusterName = %s, "
                                                  + "NamespaceName = %s, BranchName = %s",
                                                  appId, clusterName, namespaceName, branchName));
    }
  }
  private void checkNamespace(String appId, String clusterName, String namespaceName) {
    Namespace parentNamespace = namespaceService.findOne(appId, clusterName, namespaceName);
    if (parentNamespace == null) {
      throw new BadRequestException(String.format("Namespace not exist. AppId = %s, ClusterName = %s, NamespaceName = %s", appId,
                                                  clusterName, namespaceName));
    }
  }
}

+ 105 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceController.java

@ -0,0 +1,105 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RestController
public class NamespaceController {
  @Autowired
  private NamespaceService namespaceService;
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces", method = RequestMethod.POST)
  public NamespaceDTO create(@PathVariable("appId") String appId,
                             @PathVariable("clusterName") String clusterName, @RequestBody NamespaceDTO dto) {
    if (!InputValidator.isValidClusterNamespace(dto.getNamespaceName())) {
      throw new BadRequestException(String.format("Namespace格式错误: %s", InputValidator.INVALID_CLUSTER_NAMESPACE_MESSAGE));
    }
    Namespace entity = BeanUtils.transfrom(Namespace.class, dto);
    Namespace managedEntity = namespaceService.findOne(appId, clusterName, entity.getNamespaceName());
    if (managedEntity != null) {
      throw new BadRequestException("namespace already exist.");
    }
    entity = namespaceService.save(entity);
    dto = BeanUtils.transfrom(NamespaceDTO.class, entity);
    return dto;
  }
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName:.+}", method = RequestMethod.DELETE)
  public void delete(@PathVariable("appId") String appId,
                     @PathVariable("clusterName") String clusterName,
                     @PathVariable("namespaceName") String namespaceName, @RequestParam String operator) {
    Namespace entity = namespaceService.findOne(appId, clusterName, namespaceName);
    if (entity == null) throw new NotFoundException(
            String.format("namespace not found for %s %s %s", appId, clusterName, namespaceName));
    namespaceService.deleteNamespace(entity, operator);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces", method = RequestMethod.GET)
  public List<NamespaceDTO> find(@PathVariable("appId") String appId,
                                 @PathVariable("clusterName") String clusterName) {
    List<Namespace> groups = namespaceService.findNamespaces(appId, clusterName);
    return BeanUtils.batchTransform(NamespaceDTO.class, groups);
  }
  @RequestMapping(value = "/namespaces/{namespaceId}", method = RequestMethod.GET)
  public NamespaceDTO get(@PathVariable("namespaceId") Long namespaceId) {
    Namespace namespace = namespaceService.findOne(namespaceId);
    if (namespace == null)
      throw new NotFoundException(String.format("namespace not found for %s", namespaceId));
    return BeanUtils.transfrom(NamespaceDTO.class, namespace);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName:.+}", method = RequestMethod.GET)
  public NamespaceDTO get(@PathVariable("appId") String appId,
                          @PathVariable("clusterName") String clusterName,
                          @PathVariable("namespaceName") String namespaceName) {
    Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
    if (namespace == null) throw new NotFoundException(
            String.format("namespace not found for %s %s %s", appId, clusterName, namespaceName));
    return BeanUtils.transfrom(NamespaceDTO.class, namespace);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/associated-public-namespace",
      method = RequestMethod.GET)
  public NamespaceDTO findPublicNamespaceForAssociatedNamespace(@PathVariable String appId,
                                                                @PathVariable String clusterName,
                                                                @PathVariable String namespaceName) {
    Namespace namespace = namespaceService.findPublicNamespaceForAssociatedNamespace(clusterName, namespaceName);
    if (namespace == null) {
      throw new NotFoundException(String.format("public namespace not found. namespace:%s", namespaceName));
    }
    return BeanUtils.transfrom(NamespaceDTO.class, namespace);
  }
  /**
   * cluster -> cluster has not published namespaces?
   */
  @RequestMapping(value = "/apps/{appId}/namespaces/publish_info", method = RequestMethod.GET)
  public Map<String, Boolean> namespacePublishInfo(@PathVariable String appId) {
    return namespaceService.namespacePublishInfo(appId);
  }
}

+ 50 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/NamespaceLockController.java

@ -0,0 +1,50 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.NamespaceLock;
import com.ctrip.framework.apollo.biz.service.NamespaceLockService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.common.dto.NamespaceLockDTO;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class NamespaceLockController {
  @Autowired
  private NamespaceLockService namespaceLockService;
  @Autowired
  private NamespaceService namespaceService;
  @Autowired
  private BizConfig bizConfig;
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/lock", method = RequestMethod.GET)
  public NamespaceLockDTO getNamespaceLockOwner(@PathVariable String appId, @PathVariable String clusterName,
                                                @PathVariable String namespaceName) {
    Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
    if (namespace == null) {
      throw new BadRequestException("namespace not exist.");
    }
    if (bizConfig.isNamespaceLockSwitchOff()) {
      return null;
    }
    NamespaceLock lock = namespaceLockService.findLock(namespace.getId());
    if (lock == null) {
      return null;
    }
    return BeanUtils.transfrom(NamespaceLockDTO.class, lock);
  }
}

+ 179 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ReleaseController.java

@ -0,0 +1,179 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.google.common.base.Splitter;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.message.MessageSender;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.service.NamespaceBranchService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.biz.service.ReleaseService;
import com.ctrip.framework.apollo.biz.utils.ReleaseMessageKeyGenerator;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
import com.ctrip.framework.apollo.common.dto.ReleaseDTO;
import com.ctrip.framework.apollo.common.exception.NotFoundException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@RestController
public class ReleaseController {
  private static final Splitter RELEASES_SPLITTER = Splitter.on(",").omitEmptyStrings()
      .trimResults();
  @Autowired
  private ReleaseService releaseService;
  @Autowired
  private NamespaceService namespaceService;
  @Autowired
  private MessageSender messageSender;
  @Autowired
  private NamespaceBranchService namespaceBranchService;
  @RequestMapping(value = "/releases/{releaseId}", method = RequestMethod.GET)
  public ReleaseDTO get(@PathVariable("releaseId") long releaseId) {
    Release release = releaseService.findOne(releaseId);
    if (release == null) {
      throw new NotFoundException(String.format("release not found for %s", releaseId));
    }
    return BeanUtils.transfrom(ReleaseDTO.class, release);
  }
  @RequestMapping(value = "/releases", method = RequestMethod.GET)
  public List<ReleaseDTO> findReleaseByIds(@RequestParam("releaseIds") String releaseIds) {
    Set<Long> releaseIdSet = RELEASES_SPLITTER.splitToList(releaseIds).stream().map(Long::parseLong)
        .collect(Collectors.toSet());
    List<Release> releases = releaseService.findByReleaseIds(releaseIdSet);
    return BeanUtils.batchTransform(ReleaseDTO.class, releases);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/all", method = RequestMethod.GET)
  public List<ReleaseDTO> findAllReleases(@PathVariable("appId") String appId,
                                          @PathVariable("clusterName") String clusterName,
                                          @PathVariable("namespaceName") String namespaceName,
                                          Pageable page) {
    List<Release> releases = releaseService.findAllReleases(appId, clusterName, namespaceName, page);
    return BeanUtils.batchTransform(ReleaseDTO.class, releases);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/active", method = RequestMethod.GET)
  public List<ReleaseDTO> findActiveReleases(@PathVariable("appId") String appId,
                                             @PathVariable("clusterName") String clusterName,
                                             @PathVariable("namespaceName") String namespaceName,
                                             Pageable page) {
    List<Release> releases = releaseService.findActiveReleases(appId, clusterName, namespaceName, page);
    return BeanUtils.batchTransform(ReleaseDTO.class, releases);
  }
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/latest", method = RequestMethod.GET)
  public ReleaseDTO getLatest(@PathVariable("appId") String appId,
                              @PathVariable("clusterName") String clusterName,
                              @PathVariable("namespaceName") String namespaceName) {
    Release release = releaseService.findLatestActiveRelease(appId, clusterName, namespaceName);
    return BeanUtils.transfrom(ReleaseDTO.class, release);
  }
  @Transactional
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases", method = RequestMethod.POST)
  public ReleaseDTO publish(@PathVariable("appId") String appId,
                            @PathVariable("clusterName") String clusterName,
                            @PathVariable("namespaceName") String namespaceName,
                            @RequestParam("name") String releaseName,
                            @RequestParam(name = "comment", required = false) String releaseComment,
                            @RequestParam("operator") String operator,
                            @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish) {
    Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
    if (namespace == null) {
      throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId,
                                                clusterName, namespaceName));
    }
    Release release = releaseService.publish(namespace, releaseName, releaseComment, operator, isEmergencyPublish);
    //send release message
    Namespace parentNamespace = namespaceService.findParentNamespace(namespace);
    String messageCluster;
    if (parentNamespace != null) {
      messageCluster = parentNamespace.getClusterName();
    } else {
      messageCluster = clusterName;
    }
    messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, messageCluster, namespaceName),
                              Topics.APOLLO_RELEASE_TOPIC);
    return BeanUtils.transfrom(ReleaseDTO.class, release);
  }
  /**
   * merge branch items to master and publish master
   *
   * @return published result
   */
  @Transactional
  @RequestMapping(path = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/updateAndPublish", method = RequestMethod.POST)
  public ReleaseDTO updateAndPublish(@PathVariable("appId") String appId,
                                     @PathVariable("clusterName") String clusterName,
                                     @PathVariable("namespaceName") String namespaceName,
                                     @RequestParam("releaseName") String releaseName,
                                     @RequestParam("branchName") String branchName,
                                     @RequestParam(value = "deleteBranch", defaultValue = "true") boolean deleteBranch,
                                     @RequestParam(name = "releaseComment", required = false) String releaseComment,
                                     @RequestParam(name = "isEmergencyPublish", defaultValue = "false") boolean isEmergencyPublish,
                                     @RequestBody ItemChangeSets changeSets) {
    Namespace namespace = namespaceService.findOne(appId, clusterName, namespaceName);
    if (namespace == null) {
      throw new NotFoundException(String.format("Could not find namespace for %s %s %s", appId,
                                                clusterName, namespaceName));
    }
    Release release = releaseService.mergeBranchChangeSetsAndRelease(namespace, branchName, releaseName,
                                                                     releaseComment, isEmergencyPublish, changeSets);
    if (deleteBranch) {
      namespaceBranchService.deleteBranch(appId, clusterName, namespaceName, branchName,
                                          NamespaceBranchStatus.MERGED, changeSets.getDataChangeLastModifiedBy());
    }
    messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
                              Topics.APOLLO_RELEASE_TOPIC);
    return BeanUtils.transfrom(ReleaseDTO.class, release);
  }
  @Transactional
  @RequestMapping(path = "/releases/{releaseId}/rollback", method = RequestMethod.PUT)
  public void rollback(@PathVariable("releaseId") long releaseId,
                       @RequestParam("operator") String operator) {
    Release release = releaseService.rollback(releaseId, operator);
    String appId = release.getAppId();
    String clusterName = release.getClusterName();
    String namespaceName = release.getNamespaceName();
    //send release message
    messageSender.sendMessage(ReleaseMessageKeyGenerator.generate(appId, clusterName, namespaceName),
                              Topics.APOLLO_RELEASE_TOPIC);
  }
}

+ 97 - 0
apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ReleaseHistoryController.java

@ -0,0 +1,97 @@
package com.ctrip.framework.apollo.adminservice.controller;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import com.ctrip.framework.apollo.biz.service.ReleaseHistoryService;
import com.ctrip.framework.apollo.common.dto.PageDTO;
import com.ctrip.framework.apollo.common.dto.ReleaseHistoryDTO;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@RestController
public class ReleaseHistoryController {
  private Gson gson = new Gson();
  private Type configurationTypeReference = new TypeToken<Map<String, Object>>() {
  }.getType();
  @Autowired
  private ReleaseHistoryService releaseHistoryService;
  @RequestMapping(value = "/apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/releases/histories",
      method = RequestMethod.GET)
  public PageDTO<ReleaseHistoryDTO> findReleaseHistoriesByNamespace(
      @PathVariable String appId, @PathVariable String clusterName,
      @PathVariable String namespaceName,
      Pageable pageable) {
    Page<ReleaseHistory> result = releaseHistoryService.findReleaseHistoriesByNamespace(appId, clusterName,
                                                                                        namespaceName, pageable);
    return transform2PageDTO(result, pageable);
  }
  @RequestMapping(value = "/releases/histories/by_release_id_and_operation", method = RequestMethod.GET)
  public PageDTO<ReleaseHistoryDTO> findReleaseHistoryByReleaseIdAndOperation(
      @RequestParam("releaseId") long releaseId,
      @RequestParam("operation") int operation,
      Pageable pageable) {
    Page<ReleaseHistory> result = releaseHistoryService.findByReleaseIdAndOperation(releaseId, operation, pageable);
    return transform2PageDTO(result, pageable);
  }
  @RequestMapping(value = "/releases/histories/by_previous_release_id_and_operation", method = RequestMethod.GET)
  public PageDTO<ReleaseHistoryDTO> findReleaseHistoryByPreviousReleaseIdAndOperation(
      @RequestParam("previousReleaseId") long previousReleaseId,
      @RequestParam("operation") int operation,
      Pageable pageable) {
    Page<ReleaseHistory> result = releaseHistoryService.findByPreviousReleaseIdAndOperation(previousReleaseId, operation, pageable);
    return transform2PageDTO(result, pageable);
  }
  private PageDTO<ReleaseHistoryDTO> transform2PageDTO(Page<ReleaseHistory> releaseHistoriesPage, Pageable pageable){
    if (!releaseHistoriesPage.hasContent()) {
      return null;
    }
    List<ReleaseHistory> releaseHistories = releaseHistoriesPage.getContent();
    List<ReleaseHistoryDTO> releaseHistoryDTOs = new ArrayList<>(releaseHistories.size());
    for (ReleaseHistory releaseHistory : releaseHistories) {
      releaseHistoryDTOs.add(transformReleaseHistory2DTO(releaseHistory));
    }
    return new PageDTO<>(releaseHistoryDTOs, pageable, releaseHistoriesPage.getTotalElements());
  }
  private ReleaseHistoryDTO transformReleaseHistory2DTO(ReleaseHistory releaseHistory) {
    ReleaseHistoryDTO dto = new ReleaseHistoryDTO();
    BeanUtils.copyProperties(releaseHistory, dto, "operationContext");
    dto.setOperationContext(gson.fromJson(releaseHistory.getOperationContext(),
                                          configurationTypeReference));
    return dto;
  }
}

+ 2 - 0
apollo-adminservice/src/main/resources/META-INF/app.properties

@ -0,0 +1,2 @@
app.id=100003172
jdkVersion=1.8

+ 5 - 0
apollo-adminservice/src/main/resources/adminservice.properties

@ -0,0 +1,5 @@
#Used for apollo-assembly
spring.application.name= apollo-adminservice
ctrip.appid= 100003172
server.port= 8090
logging.file= /opt/logs/100003172/apollo-adminservice.log

+ 31 - 0
apollo-adminservice/src/main/resources/application.yml

@ -0,0 +1,31 @@
spring:
  application:
    name: apollo-adminservice
ctrip:
  appid: 100003172
  
server:
  port: 10062
  
---
spring:
  profiles: dev
  datasource:
    url: jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8
    username: root
    password: root
logging:
  path: c:/data
  file: ${logging.path}/apollo-adminservice.log
---
spring:
  profiles: test
  datasource:
    url: jdbc:mysql://172.19.103.57:3306/ApolloConfigDB?characterEncoding=utf8
    username: root
    password: xmjkzl
logging:
  path: /data
  file: ${logging.path}/apollo-adminservice.log

+ 23 - 0
apollo-adminservice/src/main/resources/bootstrap.yml

@ -0,0 +1,23 @@
eureka:
  instance:
    hostname: ${hostname:localhost}
    preferIpAddress: true
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8080/eureka/
    healthcheck:
      enabled: true
    eurekaServiceUrlPollIntervalSeconds: 60
endpoints:
  health:
    sensitive: false
management:
  security:
    enabled: false
  health:
    status:
      order: DOWN, OUT_OF_SERVICE, UNKNOWN, UP 

+ 23 - 0
apollo-adminservice/src/main/resources/logback.xml

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/defaults.xml" />
	<property name="LOG_FILE"
		value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}apollo-adminservice.log}" />
	<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
	<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
	<root level="INFO">
		<appender-ref ref="FILE" />
		<appender-ref ref="CONSOLE" />
	</root>
	<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
			<fileNamePattern>${LOG_PATH}/apollo-adminservice.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
			<maxFileSize>50MB</maxFileSize>
			<maxHistory>30</maxHistory>
		</rollingPolicy>
		<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
		</encoder>
	</appender>
</configuration>

+ 17 - 0
apollo-adminservice/src/main/scripts/shutdown.sh

@ -0,0 +1,17 @@
#!/bin/bash
SERVICE_NAME=apollo-adminservice
if [[ -z "$JAVA_HOME" && -d /usr/java/latest/ ]]; then
    export JAVA_HOME=/usr/java/latest/
fi
cd `dirname $0`/..
if [[ ! -f $SERVICE_NAME".jar" && -d current ]]; then
    cd current
fi
if [[ -f $SERVICE_NAME".jar" ]]; then
  chmod a+x $SERVICE_NAME".jar"
  ./$SERVICE_NAME".jar" stop
fi

File diff suppressed because it is too large
+ 121 - 0
apollo-adminservice/src/main/scripts/startup.sh


+ 6 - 0
apollo-adminservice/src/test/resources/application.properties

@ -0,0 +1,6 @@
spring.datasource.url = jdbc:h2:mem:~/apolloconfigdb;mode=mysql;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy
spring.jpa.properties.hibernate.show_sql=false
spring.h2.console.enabled = true
spring.h2.console.settings.web-allow-others=true

+ 14 - 0
apollo-adminservice/src/test/resources/application.yml

@ -0,0 +1,14 @@
spring:
  application:
    name: apollo-adminservice
    
server:
  port: ${port:8090}
  
logging:
  level:
    org.springframework.cloud: 'DEBUG'
  file: /opt/logs/${ctrip.appid}/apollo-adminservice.log
ctrip:
  appid: 100003172

+ 20 - 0
apollo-adminservice/src/test/resources/bootstrap.yml

@ -0,0 +1,20 @@
eureka:
  instance:
    hostname: ${hostname:localhost}
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8090/eureka/
    healthcheck:
      enabled: true
      
endpoints:
  health:
    sensitive: false
management:
  security:
    enabled: false
  health:
    status:
      order: DOWN, OUT_OF_SERVICE, UNKNOWN, UP 

+ 8 - 0
apollo-adminservice/src/test/resources/controller/cleanup.sql

@ -0,0 +1,8 @@
DELETE FROM Item;
DELETE FROM Namespace;
DELETE FROM AppNamespace;
DELETE FROM Cluster;
DELETE FROM App;
DELETE FROM NamespaceLock;
DELETE FROM ServerConfig;

+ 7 - 0
apollo-adminservice/src/test/resources/controller/test-itemset.sql

@ -0,0 +1,7 @@
INSERT INTO App (AppId, Name, OwnerName, OwnerEmail) VALUES ('someAppId','someAppName','someOwnerName','someOwnerName@ctrip.com');
INSERT INTO Cluster (AppId, Name) VALUES ('someAppId', 'default');
INSERT INTO AppNamespace (AppId, Name) VALUES ('someAppId', 'application');
INSERT INTO Namespace (AppId, ClusterName, NamespaceName) VALUES ('someAppId', 'default', 'application');

+ 11 - 0
apollo-adminservice/src/test/resources/controller/test-release.sql

@ -0,0 +1,11 @@
INSERT INTO App (AppId, Name, OwnerName, OwnerEmail) VALUES ('someAppId','someAppName','someOwnerName','someOwnerName@ctrip.com');
INSERT INTO Cluster (AppId, Name) VALUES ('someAppId', 'default');
INSERT INTO AppNamespace (AppId, Name) VALUES ('someAppId', 'application');
INSERT INTO Namespace (Id, AppId, ClusterName, NamespaceName) VALUES (100, 'someAppId', 'default', 'application');
INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (100, 'k1', 'v1', 'comment1');
INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (100, 'k2', 'v2', 'comment1');
INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (100, 'k3', 'v3', 'comment1');

+ 34 - 0
apollo-adminservice/src/test/resources/data.sql

@ -0,0 +1,34 @@
INSERT INTO App (AppId, Name, OwnerName, OwnerEmail) VALUES ('100003171','apollo-config-service','刘一鸣','liuym@ctrip.com');
INSERT INTO App (AppId, Name, OwnerName, OwnerEmail) VALUES ('100003172','apollo-admin-service','宋顺','song_s@ctrip.com');
INSERT INTO App (AppId, Name, OwnerName, OwnerEmail) VALUES ('100003173','apollo-portal','张乐','zhanglea@ctrip.com');
INSERT INTO App (AppId, Name, OwnerName, OwnerEmail) VALUES ('fxhermesproducer','fx-hermes-producer','梁锦华','jhliang@ctrip.com');
INSERT INTO Cluster (AppId, Name) VALUES ('100003171', 'default');
INSERT INTO Cluster (AppId, Name) VALUES ('100003171', 'cluster1');
INSERT INTO Cluster (AppId, Name) VALUES ('100003172', 'default');
INSERT INTO Cluster (AppId, Name) VALUES ('100003172', 'cluster2');
INSERT INTO Cluster (AppId, Name) VALUES ('100003173', 'default');
INSERT INTO Cluster (AppId, Name) VALUES ('100003173', 'cluster3');
INSERT INTO Cluster (AppId, Name) VALUES ('fxhermesproducer', 'default');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003171', 'application');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003171', 'fx.apollo.config');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003172', 'application');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003172', 'fx.apollo.admin');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003173', 'application');
INSERT INTO AppNamespace (AppId, Name) VALUES ('100003173', 'fx.apollo.portal');
INSERT INTO AppNamespace (AppID, Name) VALUES ('fxhermesproducer', 'fx.hermes.producer');
INSERT INTO Namespace (Id, AppId, ClusterName, NamespaceName) VALUES (1, '100003171', 'default', 'application');
INSERT INTO Namespace (Id, AppId, ClusterName, NamespaceName) VALUES (5, '100003171', 'cluster1', 'application');
INSERT INTO Namespace (Id, AppId, ClusterName, NamespaceName) VALUES (2, 'fxhermesproducer', 'default', 'fx.hermes.producer');
INSERT INTO Namespace (Id, AppId, ClusterName, NamespaceName) VALUES (3, '100003172', 'default', 'application');
INSERT INTO Namespace (Id, AppId, ClusterName, NamespaceName) VALUES (4, '100003173', 'default', 'application');
INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (1, 'k1', 'v1', 'comment1');
INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (1, 'k2', 'v2', 'comment2');
INSERT INTO Item (NamespaceId, `Key`, Value, Comment) VALUES (2, 'k3', 'v3', 'comment3');
INSERT INTO Item (NamespaceId, `Key`, Value, Comment, LineNum) VALUES (5, 'k1', 'v4', 'comment4',1);
INSERT INTO RELEASE (ReleaseKey, Name, Comment, AppId, ClusterName, NamespaceName, Configurations) VALUES ('TEST-RELEASE-KEY', 'REV1','First Release','100003171', 'default', 'application', '{"k1":"v1"}');

+ 15 - 0
apollo-adminservice/src/test/resources/logback-test.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<charset>utf-8</charset>
			<Pattern>[%p] %c - %m%n</Pattern>
		</encoder>
	</appender>
	<logger name="org.springframework.test" level="OFF" />
	<root level="WARN">
		<appender-ref ref="CONSOLE" />
	</root>
</configuration>

+ 47 - 0
apollo-assembly/pom.xml

@ -0,0 +1,47 @@
<?xml version="1.0"  encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<parent>
		<groupId>com.yihu.jkzl</groupId>
		<artifactId>apollo</artifactId>
		<version>1.1.0-RELEASE</version>
		<relativePath>../pom.xml</relativePath>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>apollo-assembly</artifactId>
	<name>Apollo Assembly</name>
	<properties>
		<github.path>${project.artifactId}</github.path>
	</properties>
	<dependencies>
		<!-- apollo -->
		<dependency>
			<groupId>com.yihu.jkzl</groupId>
			<artifactId>apollo-configservice</artifactId>
		</dependency>
		<dependency>
			<groupId>com.yihu.jkzl</groupId>
			<artifactId>apollo-adminservice</artifactId>
		</dependency>
		<dependency>
			<groupId>com.yihu.jkzl</groupId>
			<artifactId>apollo-portal</artifactId>
		</dependency>
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<configuration>
					<executable>true</executable>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

+ 63 - 0
apollo-assembly/src/main/java/com/ctrip/framework/apollo/assembly/ApolloApplication.java

@ -0,0 +1,63 @@
package com.ctrip.framework.apollo.assembly;
import com.ctrip.framework.apollo.adminservice.AdminServiceApplication;
import com.ctrip.framework.apollo.configservice.ConfigServiceApplication;
import com.ctrip.framework.apollo.portal.PortalApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.system.ApplicationPidFileWriter;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class})
public class ApolloApplication {
  private static final Logger logger = LoggerFactory.getLogger(ApolloApplication.class);
  public static void main(String[] args) throws Exception {
    /**
     * Common
     */
    ConfigurableApplicationContext commonContext =
        new SpringApplicationBuilder(ApolloApplication.class).web(false).run(args);
    commonContext.addApplicationListener(new ApplicationPidFileWriter());
    logger.info(commonContext.getId() + " isActive: " + commonContext.isActive());
    /**
     * ConfigService
     */
    if (commonContext.getEnvironment().containsProperty("configservice")) {
      ConfigurableApplicationContext configContext =
          new SpringApplicationBuilder(ConfigServiceApplication.class).parent(commonContext)
              .sources(RefreshScope.class).run(args);
      logger.info(configContext.getId() + " isActive: " + configContext.isActive());
    }
    /**
     * AdminService
     */
    if (commonContext.getEnvironment().containsProperty("adminservice")) {
      ConfigurableApplicationContext adminContext =
          new SpringApplicationBuilder(AdminServiceApplication.class).parent(commonContext)
              .sources(RefreshScope.class).run(args);
      logger.info(adminContext.getId() + " isActive: " + adminContext.isActive());
    }
    /**
     * Portal
     */
    if (commonContext.getEnvironment().containsProperty("portal")) {
      ConfigurableApplicationContext portalContext =
          new SpringApplicationBuilder(PortalApplication.class).parent(commonContext)
              .sources(RefreshScope.class).run(args);
      logger.info(portalContext.getId() + " isActive: " + portalContext.isActive());
    }
  }
}

+ 2 - 0
apollo-assembly/src/main/resources/META-INF/app.properties

@ -0,0 +1,2 @@
app.id=100003171
jdkVersion=1.8

+ 9 - 0
apollo-assembly/src/main/resources/application.yml

@ -0,0 +1,9 @@
spring:
  profiles:
    active: dev
  datasource:
    url: jdbc:mysql://localhost:3306/ApolloConfigDB?characterEncoding=utf8
    username: root
    password: root
logging:
  file: /opt/logs/100003171/apollo-assembly.log

+ 12 - 0
apollo-assembly/src/main/resources/logback.xml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/defaults.xml" />
	<property name="LOG_FILE"
		value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}apollo-assembly.log}" />
	<include resource="org/springframework/boot/logging/logback/file-appender.xml" />
	<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
	<root level="INFO">
		<appender-ref ref="FILE" />
		<appender-ref ref="CONSOLE" />
	</root>
</configuration>

+ 62 - 0
apollo-assembly/src/test/java/com/ctrip/framework/apollo/assembly/LocalApolloApplication.java

@ -0,0 +1,62 @@
package com.ctrip.framework.apollo.assembly;
import com.ctrip.framework.apollo.adminservice.AdminServiceApplication;
import com.ctrip.framework.apollo.configservice.ConfigServiceApplication;
import com.ctrip.framework.apollo.portal.PortalApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.system.ApplicationPidFileWriter;
import org.springframework.cloud.context.scope.refresh.RefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class})
public class LocalApolloApplication {
  private static final Logger logger = LoggerFactory.getLogger(ApolloApplication.class);
  public static void main(String[] args) throws Exception {
    /**
     * Common
     */
    ConfigurableApplicationContext commonContext =
        new SpringApplicationBuilder(ApolloApplication.class).web(false).run(args);
    commonContext.addApplicationListener(new ApplicationPidFileWriter());
    logger.info(commonContext.getId() + " isActive: " + commonContext.isActive());
    /**
     * ConfigService
     */
    if (commonContext.getEnvironment().containsProperty("configservice")) {
      ConfigurableApplicationContext configContext =
          new SpringApplicationBuilder(ConfigServiceApplication.class).parent(commonContext)
              .sources(RefreshScope.class).run(args);
      logger.info(configContext.getId() + " isActive: " + configContext.isActive());
    }
    /**
     * AdminService
     */
    if (commonContext.getEnvironment().containsProperty("adminservice")) {
      ConfigurableApplicationContext adminContext =
          new SpringApplicationBuilder(AdminServiceApplication.class).parent(commonContext)
              .sources(RefreshScope.class).run(args);
      logger.info(adminContext.getId() + " isActive: " + adminContext.isActive());
    }
    /**
     * Portal
     */
    if (commonContext.getEnvironment().containsProperty("portal")) {
      ConfigurableApplicationContext portalContext =
          new SpringApplicationBuilder(PortalApplication.class).parent(commonContext).run(args);
      logger.info(portalContext.getId() + " isActive: " + portalContext.isActive());
    }
  }
}

+ 6 - 0
apollo-assembly/src/test/resources/application.properties

@ -0,0 +1,6 @@
spring.datasource.url = jdbc:h2:mem:~/apolloconfigdb;mode=mysql;DB_CLOSE_ON_EXIT=FALSE
spring.jpa.hibernate.naming_strategy=org.hibernate.cfg.EJB3NamingStrategy
spring.jpa.properties.hibernate.show_sql=false
spring.h2.console.enabled = true
spring.h2.console.settings.web-allow-others=true
apollo.portal.env= local

+ 3 - 0
apollo-assembly/src/test/resources/application.yml

@ -0,0 +1,3 @@
spring:
  profiles:
    active: local

+ 15 - 0
apollo-assembly/src/test/resources/logback-test.xml

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<charset>utf-8</charset>
			<Pattern>[%p] %c - %m%n</Pattern>
		</encoder>
	</appender>
	<logger name="org.springframework.test" level="OFF" />
	<root level="WARN">
		<appender-ref ref="CONSOLE" />
	</root>
</configuration>

+ 33 - 0
apollo-biz/pom.xml

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<parent>
		<groupId>com.yihu.jkzl</groupId>
		<artifactId>apollo</artifactId>
		<version>1.1.0-RELEASE</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>apollo-biz</artifactId>
	<name>Apollo Biz</name>
	<packaging>jar</packaging>
	<properties>
		<github.path>${project.artifactId}</github.path>
	</properties>
	<dependencies>
		<dependency>
			<groupId>com.yihu.jkzl</groupId>
			<artifactId>apollo-common</artifactId>
		</dependency>
		<!-- eureka -->
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</artifactId>
		</dependency>
		<!-- end of eureka -->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

+ 12 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/ApolloBizConfig.java

@ -0,0 +1,12 @@
package com.ctrip.framework.apollo.biz;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@EnableAutoConfiguration
@Configuration
@ComponentScan(basePackageClasses = ApolloBizConfig.class)
public class ApolloBizConfig {
}

+ 39 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/auth/WebSecurityConfig.java

@ -0,0 +1,39 @@
package com.ctrip.framework.apollo.biz.auth;
import com.ctrip.framework.apollo.common.condition.ConditionalOnMissingProfile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@ConditionalOnMissingProfile("auth")
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.httpBasic();
    http.csrf().disable();
    http.headers().frameOptions().sameOrigin();
  }
  /**
   * Although the authentication below is useless, we may not remove them for backward compatibility.
   * Because if we remove them and the old clients(before 0.9.0) still send the authentication
   * information, the server will return 401, which should cause big problems.
   *
   * We may remove the following once we remove spring security from Apollo.
   */
  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("user").password("").roles("USER").and()
        .withUser("apollobb").password("").roles("USER", "ADMINaa");
  }
}

+ 149 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java

@ -0,0 +1,149 @@
package com.ctrip.framework.apollo.biz.config;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.ctrip.framework.apollo.biz.service.BizDBPropertySource;
import com.ctrip.framework.apollo.common.config.RefreshableConfig;
import com.ctrip.framework.apollo.common.config.RefreshablePropertySource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Component
public class BizConfig extends RefreshableConfig {
  private static final int DEFAULT_ITEM_KEY_LENGTH = 128;
  private static final int DEFAULT_ITEM_VALUE_LENGTH = 20000;
  private static final int DEFAULT_APPNAMESPACE_CACHE_REBUILD_INTERVAL = 60; //60s
  private static final int DEFAULT_GRAY_RELEASE_RULE_SCAN_INTERVAL = 60; //60s
  private static final int DEFAULT_APPNAMESPACE_CACHE_SCAN_INTERVAL = 1; //1s
  private static final int DEFAULT_RELEASE_MESSAGE_CACHE_SCAN_INTERVAL = 1; //1s
  private static final int DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS = 1000; //1000ms
  private static final int DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH = 100;
  private static final int DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH_INTERVAL_IN_MILLI = 100;//100ms
  private Gson gson = new Gson();
  private static final Type namespaceValueLengthOverrideTypeReference =
      new TypeToken<Map<Long, Integer>>() {
      }.getType();
  @Autowired
  private BizDBPropertySource propertySource;
  @Override
  protected List<RefreshablePropertySource> getRefreshablePropertySources() {
    return Collections.singletonList(propertySource);
  }
  public List<String> eurekaServiceUrls() {
    String configuration = getValue("eureka.service.url", "");
    if (Strings.isNullOrEmpty(configuration)) {
      return Collections.emptyList();
    }
    return splitter.splitToList(configuration);
  }
  public int grayReleaseRuleScanInterval() {
    int interval = getIntProperty("apollo.gray-release-rule-scan.interval", DEFAULT_GRAY_RELEASE_RULE_SCAN_INTERVAL);
    return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_GRAY_RELEASE_RULE_SCAN_INTERVAL);
  }
  public int itemKeyLengthLimit() {
    int limit = getIntProperty("item.key.length.limit", DEFAULT_ITEM_KEY_LENGTH);
    return checkInt(limit, 5, Integer.MAX_VALUE, DEFAULT_ITEM_KEY_LENGTH);
  }
  public int itemValueLengthLimit() {
    int limit = getIntProperty("item.value.length.limit", DEFAULT_ITEM_VALUE_LENGTH);
    return checkInt(limit, 5, Integer.MAX_VALUE, DEFAULT_ITEM_VALUE_LENGTH);
  }
  public Map<Long, Integer> namespaceValueLengthLimitOverride() {
    String namespaceValueLengthOverrideString = getValue("namespace.value.length.limit.override");
    Map<Long, Integer> namespaceValueLengthOverride = Maps.newHashMap();
    if (!Strings.isNullOrEmpty(namespaceValueLengthOverrideString)) {
      namespaceValueLengthOverride =
          gson.fromJson(namespaceValueLengthOverrideString, namespaceValueLengthOverrideTypeReference);
    }
    return namespaceValueLengthOverride;
  }
  public boolean isNamespaceLockSwitchOff() {
    return !getBooleanProperty("namespace.lock.switch", false);
  }
  /**
   * ctrip config
   **/
  public String cloggingUrl() {
    return getValue("clogging.server.url");
  }
  public String cloggingPort() {
    return getValue("clogging.server.port");
  }
  public int appNamespaceCacheScanInterval() {
    int interval = getIntProperty("apollo.app-namespace-cache-scan.interval", DEFAULT_APPNAMESPACE_CACHE_SCAN_INTERVAL);
    return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_APPNAMESPACE_CACHE_SCAN_INTERVAL);
  }
  public TimeUnit appNamespaceCacheScanIntervalTimeUnit() {
    return TimeUnit.SECONDS;
  }
  public int appNamespaceCacheRebuildInterval() {
    int interval = getIntProperty("apollo.app-namespace-cache-rebuild.interval", DEFAULT_APPNAMESPACE_CACHE_REBUILD_INTERVAL);
    return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_APPNAMESPACE_CACHE_REBUILD_INTERVAL);
  }
  public TimeUnit appNamespaceCacheRebuildIntervalTimeUnit() {
    return TimeUnit.SECONDS;
  }
  public int releaseMessageCacheScanInterval() {
    int interval = getIntProperty("apollo.release-message-cache-scan.interval", DEFAULT_RELEASE_MESSAGE_CACHE_SCAN_INTERVAL);
    return checkInt(interval, 1, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_CACHE_SCAN_INTERVAL);
  }
  public TimeUnit releaseMessageCacheScanIntervalTimeUnit() {
    return TimeUnit.SECONDS;
  }
  public int releaseMessageScanIntervalInMilli() {
    int interval = getIntProperty("apollo.message-scan.interval", DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS);
    return checkInt(interval, 100, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_SCAN_INTERVAL_IN_MS);
  }
  public int releaseMessageNotificationBatch() {
    int batch = getIntProperty("apollo.release-message.notification.batch", DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH);
    return checkInt(batch, 1, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH);
  }
  public int releaseMessageNotificationBatchIntervalInMilli() {
    int interval = getIntProperty("apollo.release-message.notification.batch.interval", DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH_INTERVAL_IN_MILLI);
    return checkInt(interval, 10, Integer.MAX_VALUE, DEFAULT_RELEASE_MESSAGE_NOTIFICATION_BATCH_INTERVAL_IN_MILLI);
  }
  public boolean isConfigServiceCacheEnabled() {
    return getBooleanProperty("config-service.cache.enabled", false);
  }
  int checkInt(int value, int min, int max, int defaultValue) {
    if (value >= min && value <= max) {
      return value;
    }
    return defaultValue;
  }
}

+ 28 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/customize/BizLoggingCustomizer.java

@ -0,0 +1,28 @@
package com.ctrip.framework.apollo.biz.customize;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.common.customize.LoggingCustomizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Component
@Profile("ctrip")
public class BizLoggingCustomizer extends LoggingCustomizer{
  @Autowired
  private BizConfig bizConfig;
  @Override
  protected String cloggingUrl() {
    return bizConfig.cloggingUrl();
  }
  @Override
  protected String cloggingPort() {
    return bizConfig.cloggingPort();
  }
}

+ 4 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/customize/package-info.java

@ -0,0 +1,4 @@
/**
 * 携程内部的日志系统,第三方公司可删除
 */
package com.ctrip.framework.apollo.biz.customize;

+ 70 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Audit.java

@ -0,0 +1,70 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "Audit")
@SQLDelete(sql = "Update Audit set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Audit extends BaseEntity {
  public enum OP {
    INSERT, UPDATE, DELETE
  }
  @Column(name = "EntityName", nullable = false)
  private String entityName;
  @Column(name = "EntityId")
  private Long entityId;
  @Column(name = "OpName", nullable = false)
  private String opName;
  @Column(name = "Comment")
  private String comment;
  public String getComment() {
    return comment;
  }
  public Long getEntityId() {
    return entityId;
  }
  public String getEntityName() {
    return entityName;
  }
  public String getOpName() {
    return opName;
  }
  public void setComment(String comment) {
    this.comment = comment;
  }
  public void setEntityId(Long entityId) {
    this.entityId = entityId;
  }
  public void setEntityName(String entityName) {
    this.entityName = entityName;
  }
  public void setOpName(String opName) {
    this.opName = opName;
  }
  public String toString() {
    return toStringHelper().add("entityName", entityName).add("entityId", entityId)
        .add("opName", opName).add("comment", comment).toString();
  }
}

+ 71 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Cluster.java

@ -0,0 +1,71 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@Entity
@Table(name = "Cluster")
@SQLDelete(sql = "Update Cluster set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Cluster extends BaseEntity implements Comparable<Cluster> {
  @Column(name = "Name", nullable = false)
  private String name;
  @Column(name = "AppId", nullable = false)
  private String appId;
  @Column(name = "ParentClusterId", nullable = false)
  private long parentClusterId;
  public String getAppId() {
    return appId;
  }
  public String getName() {
    return name;
  }
  public void setAppId(String appId) {
    this.appId = appId;
  }
  public void setName(String name) {
    this.name = name;
  }
  public long getParentClusterId() {
    return parentClusterId;
  }
  public void setParentClusterId(long parentClusterId) {
    this.parentClusterId = parentClusterId;
  }
  public String toString() {
    return toStringHelper().add("name", name).add("appId", appId)
        .add("parentClusterId", parentClusterId).toString();
  }
  @Override
  public int compareTo(Cluster o) {
    if (o == null || getId() > o.getId()) {
      return 1;
    }
    if (getId() == o.getId()) {
      return 0;
    }
    return -1;
  }
}

+ 80 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Commit.java

@ -0,0 +1,80 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.Table;
@Entity
@Table(name = "Commit")
@SQLDelete(sql = "Update Commit set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Commit extends BaseEntity {
  @Lob
  @Column(name = "ChangeSets", nullable = false)
  private String changeSets;
  @Column(name = "AppId", nullable = false)
  private String appId;
  @Column(name = "ClusterName", nullable = false)
  private String clusterName;
  @Column(name = "NamespaceName", nullable = false)
  private String namespaceName;
  @Column(name = "Comment")
  private String comment;
  public String getChangeSets() {
    return changeSets;
  }
  public void setChangeSets(String changeSets) {
    this.changeSets = changeSets;
  }
  public String getAppId() {
    return appId;
  }
  public void setAppId(String appId) {
    this.appId = appId;
  }
  public String getClusterName() {
    return clusterName;
  }
  public void setClusterName(String clusterName) {
    this.clusterName = clusterName;
  }
  public String getNamespaceName() {
    return namespaceName;
  }
  public void setNamespaceName(String namespaceName) {
    this.namespaceName = namespaceName;
  }
  public String getComment() {
    return comment;
  }
  public void setComment(String comment) {
    this.comment = comment;
  }
  @Override
  public String toString() {
    return toStringHelper().add("changeSets", changeSets).add("appId", appId).add("clusterName", clusterName)
        .add("namespaceName", namespaceName).add("comment", comment).toString();
  }
}

+ 94 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/GrayReleaseRule.java

@ -0,0 +1,94 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "GrayReleaseRule")
@SQLDelete(sql = "Update GrayReleaseRule set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class GrayReleaseRule extends BaseEntity{
  @Column(name = "appId", nullable = false)
  private String appId;
  @Column(name = "ClusterName", nullable = false)
  private String clusterName;
  @Column(name = "NamespaceName", nullable = false)
  private String namespaceName;
  @Column(name = "BranchName", nullable = false)
  private String branchName;
  @Column(name = "Rules")
  private String rules;
  @Column(name = "releaseId", nullable = false)
  private Long releaseId;
  @Column(name = "BranchStatus", nullable = false)
  private int branchStatus;
  public String getAppId() {
    return appId;
  }
  public void setAppId(String appId) {
    this.appId = appId;
  }
  public String getClusterName() {
    return clusterName;
  }
  public void setClusterName(String clusterName) {
    this.clusterName = clusterName;
  }
  public String getNamespaceName() {
    return namespaceName;
  }
  public void setNamespaceName(String namespaceName) {
    this.namespaceName = namespaceName;
  }
  public String getBranchName() {
    return branchName;
  }
  public void setBranchName(String branchName) {
    this.branchName = branchName;
  }
  public String getRules() {
    return rules;
  }
  public void setRules(String rules) {
    this.rules = rules;
  }
  public Long getReleaseId() {
    return releaseId;
  }
  public void setReleaseId(Long releaseId) {
    this.releaseId = releaseId;
  }
  public int getBranchStatus() {
    return branchStatus;
  }
  public void setBranchStatus(int branchStatus) {
    this.branchStatus = branchStatus;
  }
}

+ 122 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Instance.java

@ -0,0 +1,122 @@
package com.ctrip.framework.apollo.biz.entity;
import com.google.common.base.MoreObjects;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.Table;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@Entity
@Table(name = "Instance")
public class Instance {
  @Id
  @GeneratedValue
  @Column(name = "Id")
  private long id;
  @Column(name = "AppId", nullable = false)
  private String appId;
  @Column(name = "ClusterName", nullable = false)
  private String clusterName;
  @Column(name = "DataCenter", nullable = false)
  private String dataCenter;
  @Column(name = "Ip", nullable = false)
  private String ip;
  @Column(name = "DataChange_CreatedTime", nullable = false)
  private Date dataChangeCreatedTime;
  @Column(name = "DataChange_LastTime")
  private Date dataChangeLastModifiedTime;
  @PrePersist
  protected void prePersist() {
    if (this.dataChangeCreatedTime == null) {
      dataChangeCreatedTime = new Date();
    }
    if (this.dataChangeLastModifiedTime == null) {
      dataChangeLastModifiedTime = dataChangeCreatedTime;
    }
  }
  public long getId() {
    return id;
  }
  public void setId(long id) {
    this.id = id;
  }
  public String getAppId() {
    return appId;
  }
  public void setAppId(String appId) {
    this.appId = appId;
  }
  public String getClusterName() {
    return clusterName;
  }
  public void setClusterName(String clusterName) {
    this.clusterName = clusterName;
  }
  public String getDataCenter() {
    return dataCenter;
  }
  public void setDataCenter(String dataCenter) {
    this.dataCenter = dataCenter;
  }
  public String getIp() {
    return ip;
  }
  public void setIp(String ip) {
    this.ip = ip;
  }
  public Date getDataChangeCreatedTime() {
    return dataChangeCreatedTime;
  }
  public void setDataChangeCreatedTime(Date dataChangeCreatedTime) {
    this.dataChangeCreatedTime = dataChangeCreatedTime;
  }
  public Date getDataChangeLastModifiedTime() {
    return dataChangeLastModifiedTime;
  }
  public void setDataChangeLastModifiedTime(Date dataChangeLastModifiedTime) {
    this.dataChangeLastModifiedTime = dataChangeLastModifiedTime;
  }
  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .omitNullValues()
        .add("id", id)
        .add("appId", appId)
        .add("clusterName", clusterName)
        .add("dataCenter", dataCenter)
        .add("ip", ip)
        .add("dataChangeCreatedTime", dataChangeCreatedTime)
        .add("dataChangeLastModifiedTime", dataChangeLastModifiedTime)
        .toString();
  }
}

+ 150 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/InstanceConfig.java

@ -0,0 +1,150 @@
package com.ctrip.framework.apollo.biz.entity;
import com.google.common.base.MoreObjects;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@Entity
@Table(name = "InstanceConfig")
public class InstanceConfig {
  @Id
  @GeneratedValue
  @Column(name = "Id")
  private long id;
  @Column(name = "InstanceId")
  private long instanceId;
  @Column(name = "ConfigAppId", nullable = false)
  private String configAppId;
  @Column(name = "ConfigClusterName", nullable = false)
  private String configClusterName;
  @Column(name = "ConfigNamespaceName", nullable = false)
  private String configNamespaceName;
  @Column(name = "ReleaseKey", nullable = false)
  private String releaseKey;
  @Column(name = "ReleaseDeliveryTime", nullable = false)
  private Date releaseDeliveryTime;
  @Column(name = "DataChange_CreatedTime", nullable = false)
  private Date dataChangeCreatedTime;
  @Column(name = "DataChange_LastTime")
  private Date dataChangeLastModifiedTime;
  @PrePersist
  protected void prePersist() {
    if (this.dataChangeCreatedTime == null) {
      dataChangeCreatedTime = new Date();
    }
    if (this.dataChangeLastModifiedTime == null) {
      dataChangeLastModifiedTime = dataChangeCreatedTime;
    }
  }
  @PreUpdate
  protected void preUpdate() {
    this.dataChangeLastModifiedTime = new Date();
  }
  public long getId() {
    return id;
  }
  public void setId(long id) {
    this.id = id;
  }
  public long getInstanceId() {
    return instanceId;
  }
  public void setInstanceId(long instanceId) {
    this.instanceId = instanceId;
  }
  public String getConfigAppId() {
    return configAppId;
  }
  public void setConfigAppId(String configAppId) {
    this.configAppId = configAppId;
  }
  public String getConfigNamespaceName() {
    return configNamespaceName;
  }
  public void setConfigNamespaceName(String configNamespaceName) {
    this.configNamespaceName = configNamespaceName;
  }
  public String getReleaseKey() {
    return releaseKey;
  }
  public void setReleaseKey(String releaseKey) {
    this.releaseKey = releaseKey;
  }
  public Date getDataChangeCreatedTime() {
    return dataChangeCreatedTime;
  }
  public void setDataChangeCreatedTime(Date dataChangeCreatedTime) {
    this.dataChangeCreatedTime = dataChangeCreatedTime;
  }
  public Date getDataChangeLastModifiedTime() {
    return dataChangeLastModifiedTime;
  }
  public void setDataChangeLastModifiedTime(Date dataChangeLastModifiedTime) {
    this.dataChangeLastModifiedTime = dataChangeLastModifiedTime;
  }
  public String getConfigClusterName() {
    return configClusterName;
  }
  public void setConfigClusterName(String configClusterName) {
    this.configClusterName = configClusterName;
  }
  public Date getReleaseDeliveryTime() {
    return releaseDeliveryTime;
  }
  public void setReleaseDeliveryTime(Date releaseDeliveryTime) {
    this.releaseDeliveryTime = releaseDeliveryTime;
  }
  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .omitNullValues()
        .add("id", id)
        .add("configAppId", configAppId)
        .add("configClusterName", configClusterName)
        .add("configNamespaceName", configNamespaceName)
        .add("releaseKey", releaseKey)
        .add("dataChangeCreatedTime", dataChangeCreatedTime)
        .add("dataChangeLastModifiedTime", dataChangeLastModifiedTime)
        .toString();
  }
}

+ 79 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Item.java

@ -0,0 +1,79 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.Table;
@Entity
@Table(name = "Item")
@SQLDelete(sql = "Update Item set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Item extends BaseEntity {
  @Column(name = "NamespaceId", nullable = false)
  private long namespaceId;
  @Column(name = "key", nullable = false)
  private String key;
  @Column(name = "value")
  @Lob
  private String value;
  @Column(name = "comment")
  private String comment;
  @Column(name = "LineNum")
  private Integer lineNum;
  public String getComment() {
    return comment;
  }
  public String getKey() {
    return key;
  }
  public long getNamespaceId() {
    return namespaceId;
  }
  public String getValue() {
    return value;
  }
  public void setComment(String comment) {
    this.comment = comment;
  }
  public void setKey(String key) {
    this.key = key;
  }
  public void setNamespaceId(long namespaceId) {
    this.namespaceId = namespaceId;
  }
  public void setValue(String value) {
    this.value = value;
  }
  public Integer getLineNum() {
    return lineNum;
  }
  public void setLineNum(Integer lineNum) {
    this.lineNum = lineNum;
  }
  public String toString() {
    return toStringHelper().add("namespaceId", namespaceId).add("key", key).add("value", value)
        .add("lineNum", lineNum).add("comment", comment).toString();
  }
}

+ 65 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Namespace.java

@ -0,0 +1,65 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "Namespace")
@SQLDelete(sql = "Update Namespace set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Namespace extends BaseEntity {
  @Column(name = "appId", nullable = false)
  private String appId;
  @Column(name = "ClusterName", nullable = false)
  private String clusterName;
  @Column(name = "NamespaceName", nullable = false)
  private String namespaceName;
  public Namespace(){
  }
  public Namespace(String appId, String clusterName, String namespaceName) {
    this.appId = appId;
    this.clusterName = clusterName;
    this.namespaceName = namespaceName;
  }
  public String getAppId() {
    return appId;
  }
  public String getClusterName() {
    return clusterName;
  }
  public String getNamespaceName() {
    return namespaceName;
  }
  public void setAppId(String appId) {
    this.appId = appId;
  }
  public void setClusterName(String clusterName) {
    this.clusterName = clusterName;
  }
  public void setNamespaceName(String namespaceName) {
    this.namespaceName = namespaceName;
  }
  public String toString() {
    return toStringHelper().add("appId", appId).add("clusterName", clusterName)
        .add("namespaceName", namespaceName).toString();
  }
}

+ 26 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/NamespaceLock.java

@ -0,0 +1,26 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "NamespaceLock")
@Where(clause = "isDeleted = 0")
public class NamespaceLock extends BaseEntity{
  @Column(name = "NamespaceId")
  private long namespaceId;
  public long getNamespaceId() {
    return namespaceId;
  }
  public void setNamespaceId(long namespaceId) {
    this.namespaceId = namespaceId;
  }
}

+ 55 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Privilege.java

@ -0,0 +1,55 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name = "Privilege")
@SQLDelete(sql = "Update Privilege set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Privilege extends BaseEntity {
  @Column(name = "Name", nullable = false)
  private String name;
  @Column(name = "PrivilType", nullable = false)
  private String privilType;
  @Column(name = "NamespaceId")
  private long namespaceId;
  public String getName() {
    return name;
  }
  public long getNamespaceId() {
    return namespaceId;
  }
  public String getPrivilType() {
    return privilType;
  }
  public void setName(String name) {
    this.name = name;
  }
  public void setNamespaceId(long namespaceId) {
    this.namespaceId = namespaceId;
  }
  public void setPrivilType(String privilType) {
    this.privilType = privilType;
  }
  public String toString() {
    return toStringHelper().add("namespaceId", namespaceId).add("privilType", privilType)
        .add("name", name).toString();
  }
}

+ 115 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Release.java

@ -0,0 +1,115 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;
import javax.persistence.Table;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@Entity
@Table(name = "Release")
@SQLDelete(sql = "Update Release set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class Release extends BaseEntity {
  @Column(name = "ReleaseKey", nullable = false)
  private String releaseKey;
  @Column(name = "Name", nullable = false)
  private String name;
  @Column(name = "AppId", nullable = false)
  private String appId;
  @Column(name = "ClusterName", nullable = false)
  private String clusterName;
  @Column(name = "NamespaceName", nullable = false)
  private String namespaceName;
  @Column(name = "Configurations", nullable = false)
  @Lob
  private String configurations;
  @Column(name = "Comment", nullable = false)
  private String comment;
  @Column(name = "IsAbandoned", columnDefinition = "Bit default '0'")
  private boolean isAbandoned;
  public String getReleaseKey() {
    return releaseKey;
  }
  public String getAppId() {
    return appId;
  }
  public String getClusterName() {
    return clusterName;
  }
  public String getComment() {
    return comment;
  }
  public String getConfigurations() {
    return configurations;
  }
  public String getNamespaceName() {
    return namespaceName;
  }
  public String getName() {
    return name;
  }
  public void setReleaseKey(String releaseKey) {
    this.releaseKey = releaseKey;
  }
  public void setAppId(String appId) {
    this.appId = appId;
  }
  public void setClusterName(String clusterName) {
    this.clusterName = clusterName;
  }
  public void setComment(String comment) {
    this.comment = comment;
  }
  public void setConfigurations(String configurations) {
    this.configurations = configurations;
  }
  public void setNamespaceName(String namespaceName) {
    this.namespaceName = namespaceName;
  }
  public void setName(String name) {
    this.name = name;
  }
  public boolean isAbandoned() {
    return isAbandoned;
  }
  public void setAbandoned(boolean abandoned) {
    isAbandoned = abandoned;
  }
  public String toString() {
    return toStringHelper().add("name", name).add("appId", appId).add("clusterName", clusterName)
        .add("namespaceName", namespaceName).add("configurations", configurations)
        .add("comment", comment).add("isAbandoned", isAbandoned).toString();
  }
}

+ 114 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ReleaseHistory.java

@ -0,0 +1,114 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@Entity
@Table(name = "ReleaseHistory")
@SQLDelete(sql = "Update ReleaseHistory set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class ReleaseHistory extends BaseEntity {
  @Column(name = "AppId", nullable = false)
  private String appId;
  @Column(name = "ClusterName", nullable = false)
  private String clusterName;
  @Column(name = "NamespaceName", nullable = false)
  private String namespaceName;
  @Column(name = "BranchName", nullable = false)
  private String branchName;
  @Column(name = "ReleaseId")
  private long releaseId;
  @Column(name = "PreviousReleaseId")
  private long previousReleaseId;
  @Column(name = "Operation")
  private int operation;
  @Column(name = "OperationContext", nullable = false)
  private String operationContext;
  public String getAppId() {
    return appId;
  }
  public void setAppId(String appId) {
    this.appId = appId;
  }
  public String getClusterName() {
    return clusterName;
  }
  public void setClusterName(String clusterName) {
    this.clusterName = clusterName;
  }
  public String getNamespaceName() {
    return namespaceName;
  }
  public void setNamespaceName(String namespaceName) {
    this.namespaceName = namespaceName;
  }
  public String getBranchName() {
    return branchName;
  }
  public void setBranchName(String branchName) {
    this.branchName = branchName;
  }
  public long getReleaseId() {
    return releaseId;
  }
  public void setReleaseId(long releaseId) {
    this.releaseId = releaseId;
  }
  public long getPreviousReleaseId() {
    return previousReleaseId;
  }
  public void setPreviousReleaseId(long previousReleaseId) {
    this.previousReleaseId = previousReleaseId;
  }
  public int getOperation() {
    return operation;
  }
  public void setOperation(int operation) {
    this.operation = operation;
  }
  public String getOperationContext() {
    return operationContext;
  }
  public void setOperationContext(String operationContext) {
    this.operationContext = operationContext;
  }
  public String toString() {
    return toStringHelper().add("appId", appId).add("clusterName", clusterName)
        .add("namespaceName", namespaceName).add("branchName", branchName)
        .add("releaseId", releaseId).add("previousReleaseId", previousReleaseId)
        .add("operation", operation).toString();
  }
}

+ 70 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ReleaseMessage.java

@ -0,0 +1,70 @@
package com.ctrip.framework.apollo.biz.entity;
import com.google.common.base.MoreObjects;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.Table;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@Entity
@Table(name = "ReleaseMessage")
public class ReleaseMessage {
  @Id
  @GeneratedValue
  @Column(name = "Id")
  private long id;
  @Column(name = "Message", nullable = false)
  private String message;
  @Column(name = "DataChange_LastTime")
  private Date dataChangeLastModifiedTime;
  @PrePersist
  protected void prePersist() {
    if (this.dataChangeLastModifiedTime == null) {
      dataChangeLastModifiedTime = new Date();
    }
  }
  public ReleaseMessage() {
  }
  public ReleaseMessage(String message) {
    this.message = message;
  }
  public long getId() {
    return id;
  }
  public void setId(long id) {
    this.id = id;
  }
  public String getMessage() {
    return message;
  }
  public void setMessage(String message) {
    this.message = message;
  }
  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .omitNullValues()
        .add("id", id)
        .add("message", message)
        .add("dataChangeLastModifiedTime", dataChangeLastModifiedTime)
        .toString();
  }
}

+ 67 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ServerConfig.java

@ -0,0 +1,67 @@
package com.ctrip.framework.apollo.biz.entity;
import com.ctrip.framework.apollo.common.entity.BaseEntity;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@Entity
@Table(name = "ServerConfig")
@SQLDelete(sql = "Update ServerConfig set isDeleted = 1 where id = ?")
@Where(clause = "isDeleted = 0")
public class ServerConfig extends BaseEntity {
  @Column(name = "Key", nullable = false)
  private String key;
  @Column(name = "Cluster", nullable = false)
  private String cluster;
  @Column(name = "Value", nullable = false)
  private String value;
  @Column(name = "Comment", nullable = false)
  private String comment;
  public String getKey() {
    return key;
  }
  public void setKey(String key) {
    this.key = key;
  }
  public String getValue() {
    return value;
  }
  public void setValue(String value) {
    this.value = value;
  }
  public String getComment() {
    return comment;
  }
  public void setComment(String comment) {
    this.comment = comment;
  }
  public String getCluster() {
    return cluster;
  }
  public void setCluster(String cluster) {
    this.cluster = cluster;
  }
  public String toString() {
    return toStringHelper().add("key", key).add("value", value).add("comment", comment).toString();
  }
}

+ 33 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/eureka/ApolloEurekaClientConfig.java

@ -0,0 +1,33 @@
package com.ctrip.framework.apollo.biz.eureka;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
@Component
@Primary
public class ApolloEurekaClientConfig extends EurekaClientConfigBean {
  @Autowired
  private BizConfig bizConfig;
  /**
   * Assert only one zone: defaultZone, but multiple environments.
   */
  public List<String> getEurekaServerServiceUrls(String myZone) {
    List<String> urls = bizConfig.eurekaServiceUrls();
    return CollectionUtils.isEmpty(urls) ? super.getEurekaServerServiceUrls(myZone) : urls;
  }
  @Override
  public boolean equals(Object o) {
    return super.equals(o);
  }
}

+ 70 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/grayReleaseRule/GrayReleaseRuleCache.java

@ -0,0 +1,70 @@
package com.ctrip.framework.apollo.biz.grayReleaseRule;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import java.util.Set;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public class GrayReleaseRuleCache {
  private long ruleId;
  private String branchName;
  private String namespaceName;
  private long releaseId;
  private long loadVersion;
  private int branchStatus;
  private Set<GrayReleaseRuleItemDTO> ruleItems;
  public GrayReleaseRuleCache(long ruleId, String branchName, String namespaceName, long
      releaseId, int branchStatus, long loadVersion, Set<GrayReleaseRuleItemDTO> ruleItems) {
    this.ruleId = ruleId;
    this.branchName = branchName;
    this.namespaceName = namespaceName;
    this.releaseId = releaseId;
    this.branchStatus = branchStatus;
    this.loadVersion = loadVersion;
    this.ruleItems = ruleItems;
  }
  public long getRuleId() {
    return ruleId;
  }
  public Set<GrayReleaseRuleItemDTO> getRuleItems() {
    return ruleItems;
  }
  public String getBranchName() {
    return branchName;
  }
  public int getBranchStatus() {
    return branchStatus;
  }
  public long getReleaseId() {
    return releaseId;
  }
  public long getLoadVersion() {
    return loadVersion;
  }
  public void setLoadVersion(long loadVersion) {
    this.loadVersion = loadVersion;
  }
  public String getNamespaceName() {
    return namespaceName;
  }
  public boolean matches(String clientAppId, String clientIp) {
    for (GrayReleaseRuleItemDTO ruleItem : ruleItems) {
      if (ruleItem.matches(clientAppId, clientIp)) {
        return true;
      }
    }
    return false;
  }
}

+ 273 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/grayReleaseRule/GrayReleaseRulesHolder.java

@ -0,0 +1,273 @@
package com.ctrip.framework.apollo.biz.grayReleaseRule;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.message.ReleaseMessageListener;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.biz.repository.GrayReleaseRuleRepository;
import com.ctrip.framework.apollo.common.constants.NamespaceBranchStatus;
import com.ctrip.framework.apollo.common.dto.GrayReleaseRuleItemDTO;
import com.ctrip.framework.apollo.common.utils.GrayReleaseRuleItemTransformer;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public class GrayReleaseRulesHolder implements ReleaseMessageListener, InitializingBean {
  private static final Logger logger = LoggerFactory.getLogger(GrayReleaseRulesHolder.class);
  private static final Joiner STRING_JOINER = Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR);
  private static final Splitter STRING_SPLITTER =
      Splitter.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).omitEmptyStrings();
  @Autowired
  private GrayReleaseRuleRepository grayReleaseRuleRepository;
  @Autowired
  private BizConfig bizConfig;
  private int databaseScanInterval;
  private ScheduledExecutorService executorService;
  //store configAppId+configCluster+configNamespace -> GrayReleaseRuleCache map
  private Multimap<String, GrayReleaseRuleCache> grayReleaseRuleCache;
  //store clientAppId+clientNamespace+ip -> ruleId map
  private Multimap<String, Long> reversedGrayReleaseRuleCache;
  //an auto increment version to indicate the age of rules
  private AtomicLong loadVersion;
  public GrayReleaseRulesHolder() {
    loadVersion = new AtomicLong();
    grayReleaseRuleCache = Multimaps.synchronizedSetMultimap(HashMultimap.create());
    reversedGrayReleaseRuleCache = Multimaps.synchronizedSetMultimap(HashMultimap.create());
    executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory
        .create("GrayReleaseRulesHolder", true));
  }
  @Override
  public void afterPropertiesSet() throws Exception {
    populateDataBaseInterval();
    //force sync load for the first time
    periodicScanRules();
    executorService.scheduleWithFixedDelay(this::periodicScanRules,
        getDatabaseScanIntervalSecond(), getDatabaseScanIntervalSecond(), getDatabaseScanTimeUnit()
    );
  }
  @Override
  public void handleMessage(ReleaseMessage message, String channel) {
    logger.info("message received - channel: {}, message: {}", channel, message);
    String releaseMessage = message.getMessage();
    if (!Topics.APOLLO_RELEASE_TOPIC.equals(channel) || Strings.isNullOrEmpty(releaseMessage)) {
      return;
    }
    List<String> keys = STRING_SPLITTER.splitToList(releaseMessage);
    //message should be appId+cluster+namespace
    if (keys.size() != 3) {
      logger.error("message format invalid - {}", releaseMessage);
      return;
    }
    String appId = keys.get(0);
    String cluster = keys.get(1);
    String namespace = keys.get(2);
    List<GrayReleaseRule> rules = grayReleaseRuleRepository
        .findByAppIdAndClusterNameAndNamespaceName(appId, cluster, namespace);
    mergeGrayReleaseRules(rules);
  }
  private void periodicScanRules() {
    Transaction transaction = Tracer.newTransaction("Apollo.GrayReleaseRulesScanner",
        "scanGrayReleaseRules");
    try {
      loadVersion.incrementAndGet();
      scanGrayReleaseRules();
      transaction.setStatus(Transaction.SUCCESS);
    } catch (Throwable ex) {
      transaction.setStatus(ex);
      logger.error("Scan gray release rule failed", ex);
    } finally {
      transaction.complete();
    }
  }
  public Long findReleaseIdFromGrayReleaseRule(String clientAppId, String clientIp, String
      configAppId, String configCluster, String configNamespaceName) {
    String key = assembleGrayReleaseRuleKey(configAppId, configCluster, configNamespaceName);
    if (!grayReleaseRuleCache.containsKey(key)) {
      return null;
    }
    //create a new list to avoid ConcurrentModificationException
    List<GrayReleaseRuleCache> rules = Lists.newArrayList(grayReleaseRuleCache.get(key));
    for (GrayReleaseRuleCache rule : rules) {
      //check branch status
      if (rule.getBranchStatus() != NamespaceBranchStatus.ACTIVE) {
        continue;
      }
      if (rule.matches(clientAppId, clientIp)) {
        return rule.getReleaseId();
      }
    }
    return null;
  }
  /**
   * Check whether there are gray release rules for the clientAppId, clientIp, namespace
   * combination. Please note that even there are gray release rules, it doesn't mean it will always
   * load gray releases. Because gray release rules actually apply to one more dimension - cluster.
   */
  public boolean hasGrayReleaseRule(String clientAppId, String clientIp, String namespaceName) {
    return reversedGrayReleaseRuleCache.containsKey(assembleReversedGrayReleaseRuleKey(clientAppId,
        namespaceName, clientIp)) || reversedGrayReleaseRuleCache.containsKey
        (assembleReversedGrayReleaseRuleKey(clientAppId, namespaceName, GrayReleaseRuleItemDTO
            .ALL_IP));
  }
  private void scanGrayReleaseRules() {
    long maxIdScanned = 0;
    boolean hasMore = true;
    while (hasMore && !Thread.currentThread().isInterrupted()) {
      List<GrayReleaseRule> grayReleaseRules = grayReleaseRuleRepository
          .findFirst500ByIdGreaterThanOrderByIdAsc(maxIdScanned);
      if (CollectionUtils.isEmpty(grayReleaseRules)) {
        break;
      }
      mergeGrayReleaseRules(grayReleaseRules);
      int rulesScanned = grayReleaseRules.size();
      maxIdScanned = grayReleaseRules.get(rulesScanned - 1).getId();
      //batch is 500
      hasMore = rulesScanned == 500;
    }
  }
  private void mergeGrayReleaseRules(List<GrayReleaseRule> grayReleaseRules) {
    if (CollectionUtils.isEmpty(grayReleaseRules)) {
      return;
    }
    for (GrayReleaseRule grayReleaseRule : grayReleaseRules) {
      if (grayReleaseRule.getReleaseId() == null || grayReleaseRule.getReleaseId() == 0) {
        //filter rules with no release id, i.e. never released
        continue;
      }
      String key = assembleGrayReleaseRuleKey(grayReleaseRule.getAppId(), grayReleaseRule
          .getClusterName(), grayReleaseRule.getNamespaceName());
      //create a new list to avoid ConcurrentModificationException
      List<GrayReleaseRuleCache> rules = Lists.newArrayList(grayReleaseRuleCache.get(key));
      GrayReleaseRuleCache oldRule = null;
      for (GrayReleaseRuleCache ruleCache : rules) {
        if (ruleCache.getBranchName().equals(grayReleaseRule.getBranchName())) {
          oldRule = ruleCache;
          break;
        }
      }
      //if old rule is null and new rule's branch status is not active, ignore
      if (oldRule == null && grayReleaseRule.getBranchStatus() != NamespaceBranchStatus.ACTIVE) {
        continue;
      }
      //use id comparison to avoid synchronization
      if (oldRule == null || grayReleaseRule.getId() > oldRule.getRuleId()) {
        addCache(key, transformRuleToRuleCache(grayReleaseRule));
        if (oldRule != null) {
          removeCache(key, oldRule);
        }
      } else {
        if (oldRule.getBranchStatus() == NamespaceBranchStatus.ACTIVE) {
          //update load version
          oldRule.setLoadVersion(loadVersion.get());
        } else if ((loadVersion.get() - oldRule.getLoadVersion()) > 1) {
          //remove outdated inactive branch rule after 2 update cycles
          removeCache(key, oldRule);
        }
      }
    }
  }
  private void addCache(String key, GrayReleaseRuleCache ruleCache) {
    if (ruleCache.getBranchStatus() == NamespaceBranchStatus.ACTIVE) {
      for (GrayReleaseRuleItemDTO ruleItemDTO : ruleCache.getRuleItems()) {
        for (String clientIp : ruleItemDTO.getClientIpList()) {
          reversedGrayReleaseRuleCache.put(assembleReversedGrayReleaseRuleKey(ruleItemDTO
              .getClientAppId(), ruleCache.getNamespaceName(), clientIp), ruleCache.getRuleId());
        }
      }
    }
    grayReleaseRuleCache.put(key, ruleCache);
  }
  private void removeCache(String key, GrayReleaseRuleCache ruleCache) {
    grayReleaseRuleCache.remove(key, ruleCache);
    for (GrayReleaseRuleItemDTO ruleItemDTO : ruleCache.getRuleItems()) {
      for (String clientIp : ruleItemDTO.getClientIpList()) {
        reversedGrayReleaseRuleCache.remove(assembleReversedGrayReleaseRuleKey(ruleItemDTO
            .getClientAppId(), ruleCache.getNamespaceName(), clientIp), ruleCache.getRuleId());
      }
    }
  }
  private GrayReleaseRuleCache transformRuleToRuleCache(GrayReleaseRule grayReleaseRule) {
    Set<GrayReleaseRuleItemDTO> ruleItems;
    try {
      ruleItems = GrayReleaseRuleItemTransformer.batchTransformFromJSON(grayReleaseRule.getRules());
    } catch (Throwable ex) {
      ruleItems = Sets.newHashSet();
      Tracer.logError(ex);
      logger.error("parse rule for gray release rule {} failed", grayReleaseRule.getId(), ex);
    }
    GrayReleaseRuleCache ruleCache = new GrayReleaseRuleCache(grayReleaseRule.getId(),
        grayReleaseRule.getBranchName(), grayReleaseRule.getNamespaceName(), grayReleaseRule
        .getReleaseId(), grayReleaseRule.getBranchStatus(), loadVersion.get(), ruleItems);
    return ruleCache;
  }
  private void populateDataBaseInterval() {
    databaseScanInterval = bizConfig.grayReleaseRuleScanInterval();
  }
  private int getDatabaseScanIntervalSecond() {
    return databaseScanInterval;
  }
  private TimeUnit getDatabaseScanTimeUnit() {
    return TimeUnit.SECONDS;
  }
  private String assembleGrayReleaseRuleKey(String configAppId, String configCluster, String
      configNamespaceName) {
    return STRING_JOINER.join(configAppId, configCluster, configNamespaceName);
  }
  private String assembleReversedGrayReleaseRuleKey(String clientAppId, String
      clientNamespaceName, String clientIp) {
    return STRING_JOINER.join(clientAppId, clientNamespaceName, clientIp);
  }
}

+ 110 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/DatabaseMessageSender.java

@ -0,0 +1,110 @@
package com.ctrip.framework.apollo.biz.message;
import com.google.common.collect.Queues;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.repository.ReleaseMessageRepository;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PostConstruct;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
@Component
public class DatabaseMessageSender implements MessageSender {
  private static final Logger logger = LoggerFactory.getLogger(DatabaseMessageSender.class);
  private static final int CLEAN_QUEUE_MAX_SIZE = 100;
  private BlockingQueue<Long> toClean = Queues.newLinkedBlockingQueue(CLEAN_QUEUE_MAX_SIZE);
  private final ExecutorService cleanExecutorService;
  private final AtomicBoolean cleanStopped;
  @Autowired
  private ReleaseMessageRepository releaseMessageRepository;
  public DatabaseMessageSender() {
    cleanExecutorService = Executors.newSingleThreadExecutor(ApolloThreadFactory.create("DatabaseMessageSender", true));
    cleanStopped = new AtomicBoolean(false);
  }
  @Override
  @Transactional
  public void sendMessage(String message, String channel) {
    logger.info("Sending message {} to channel {}", message, channel);
    if (!Objects.equals(channel, Topics.APOLLO_RELEASE_TOPIC)) {
      logger.warn("Channel {} not supported by DatabaseMessageSender!");
      return;
    }
    Tracer.logEvent("Apollo.AdminService.ReleaseMessage", message);
    Transaction transaction = Tracer.newTransaction("Apollo.AdminService", "sendMessage");
    try {
      ReleaseMessage newMessage = releaseMessageRepository.save(new ReleaseMessage(message));
      toClean.offer(newMessage.getId());
      transaction.setStatus(Transaction.SUCCESS);
    } catch (Throwable ex) {
      logger.error("Sending message to database failed", ex);
      transaction.setStatus(ex);
      throw ex;
    } finally {
      transaction.complete();
    }
  }
  @PostConstruct
  private void initialize() {
    cleanExecutorService.submit(() -> {
      while (!cleanStopped.get() && !Thread.currentThread().isInterrupted()) {
        try {
          Long rm = toClean.poll(1, TimeUnit.SECONDS);
          if (rm != null) {
            cleanMessage(rm);
          } else {
            TimeUnit.SECONDS.sleep(5);
          }
        } catch (Throwable ex) {
          Tracer.logError(ex);
        }
      }
    });
  }
  private void cleanMessage(Long id) {
    boolean hasMore = true;
    //double check in case the release message is rolled back
    ReleaseMessage releaseMessage = releaseMessageRepository.findOne(id);
    if (releaseMessage == null) {
      return;
    }
    while (hasMore && !Thread.currentThread().isInterrupted()) {
      List<ReleaseMessage> messages = releaseMessageRepository.findFirst100ByMessageAndIdLessThanOrderByIdAsc(
          releaseMessage.getMessage(), releaseMessage.getId());
      releaseMessageRepository.delete(messages);
      hasMore = messages.size() == 100;
      messages.forEach(toRemove -> Tracer.logEvent(
          String.format("ReleaseMessage.Clean.%s", toRemove.getMessage()), String.valueOf(toRemove.getId())));
    }
  }
  void stopClean() {
    cleanStopped.set(true);
  }
}

+ 8 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/MessageSender.java

@ -0,0 +1,8 @@
package com.ctrip.framework.apollo.biz.message;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public interface MessageSender {
  void sendMessage(String message, String channel);
}

+ 10 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/ReleaseMessageListener.java

@ -0,0 +1,10 @@
package com.ctrip.framework.apollo.biz.message;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public interface ReleaseMessageListener {
  void handleMessage(ReleaseMessage message, String channel);
}

+ 124 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/ReleaseMessageScanner.java

@ -0,0 +1,124 @@
package com.ctrip.framework.apollo.biz.message;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import com.ctrip.framework.apollo.biz.config.BizConfig;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.repository.ReleaseMessageRepository;
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.ctrip.framework.apollo.tracer.spi.Transaction;
import com.google.common.collect.Lists;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public class ReleaseMessageScanner implements InitializingBean {
  private static final Logger logger = LoggerFactory.getLogger(ReleaseMessageScanner.class);
  @Autowired
  private BizConfig bizConfig;
  @Autowired
  private ReleaseMessageRepository releaseMessageRepository;
  private int databaseScanInterval;
  private List<ReleaseMessageListener> listeners;
  private ScheduledExecutorService executorService;
  private long maxIdScanned;
  public ReleaseMessageScanner() {
    listeners = Lists.newCopyOnWriteArrayList();
    executorService = Executors.newScheduledThreadPool(1, ApolloThreadFactory
        .create("ReleaseMessageScanner", true));
  }
  @Override
  public void afterPropertiesSet() throws Exception {
    databaseScanInterval = bizConfig.releaseMessageScanIntervalInMilli();
    maxIdScanned = loadLargestMessageId();
    executorService.scheduleWithFixedDelay((Runnable) () -> {
      Transaction transaction = Tracer.newTransaction("Apollo.ReleaseMessageScanner", "scanMessage");
      try {
        scanMessages();
        transaction.setStatus(Transaction.SUCCESS);
      } catch (Throwable ex) {
        transaction.setStatus(ex);
        logger.error("Scan and send message failed", ex);
      } finally {
        transaction.complete();
      }
    }, databaseScanInterval, databaseScanInterval, TimeUnit.MILLISECONDS);
  }
  /**
   * add message listeners for release message
   * @param listener
   */
  public void addMessageListener(ReleaseMessageListener listener) {
    if (!listeners.contains(listener)) {
      listeners.add(listener);
    }
  }
  /**
   * Scan messages, continue scanning until there is no more messages
   */
  private void scanMessages() {
    boolean hasMoreMessages = true;
    while (hasMoreMessages && !Thread.currentThread().isInterrupted()) {
      hasMoreMessages = scanAndSendMessages();
    }
  }
  /**
   * scan messages and send
   *
   * @return whether there are more messages
   */
  private boolean scanAndSendMessages() {
    //current batch is 500
    List<ReleaseMessage> releaseMessages =
        releaseMessageRepository.findFirst500ByIdGreaterThanOrderByIdAsc(maxIdScanned);
    if (CollectionUtils.isEmpty(releaseMessages)) {
      return false;
    }
    fireMessageScanned(releaseMessages);
    int messageScanned = releaseMessages.size();
    maxIdScanned = releaseMessages.get(messageScanned - 1).getId();
    return messageScanned == 500;
  }
  /**
   * find largest message id as the current start point
   * @return current largest message id
   */
  private long loadLargestMessageId() {
    ReleaseMessage releaseMessage = releaseMessageRepository.findTopByOrderByIdDesc();
    return releaseMessage == null ? 0 : releaseMessage.getId();
  }
  /**
   * Notify listeners with messages loaded
   * @param messages
   */
  private void fireMessageScanned(List<ReleaseMessage> messages) {
    for (ReleaseMessage message : messages) {
      for (ReleaseMessageListener listener : listeners) {
        try {
          listener.handleMessage(message, Topics.APOLLO_RELEASE_TOPIC);
        } catch (Throwable ex) {
          Tracer.logError(ex);
          logger.error("Failed to invoke message listener {}", listener.getClass(), ex);
        }
      }
    }
  }
}

+ 8 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/message/Topics.java

@ -0,0 +1,8 @@
package com.ctrip.framework.apollo.biz.message;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public class Topics {
  public static final String APOLLO_RELEASE_TOPIC = "apollo-release";
}

+ 36 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/AppNamespaceRepository.java

@ -0,0 +1,36 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
import java.util.Set;
public interface AppNamespaceRepository extends PagingAndSortingRepository<AppNamespace, Long>{
  AppNamespace findByAppIdAndName(String appId, String namespaceName);
  List<AppNamespace> findByAppIdAndNameIn(String appId, Set<String> namespaceNames);
  AppNamespace findByNameAndIsPublicTrue(String namespaceName);
  List<AppNamespace> findByNameInAndIsPublicTrue(Set<String> namespaceNames);
  List<AppNamespace> findByAppIdAndIsPublic(String appId, boolean isPublic);
  List<AppNamespace> findByAppId(String appId);
  List<AppNamespace> findFirst500ByIdGreaterThanOrderByIdAsc(long id);
  @Modifying
  @Query("UPDATE AppNamespace SET IsDeleted=1,DataChange_LastModifiedBy = ?2 WHERE AppId=?1")
  int batchDeleteByAppId(String appId, String operator);
  @Modifying
  @Query("UPDATE AppNamespace SET IsDeleted=1,DataChange_LastModifiedBy = ?3 WHERE AppId=?1 and Name = ?2")
  int delete(String appId, String namespaceName, String operator);
}

+ 17 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/AppRepository.java

@ -0,0 +1,17 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.common.entity.App;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface AppRepository extends PagingAndSortingRepository<App, Long> {
  @Query("SELECT a from App a WHERE a.name LIKE %:name%")
  List<App> findByName(@Param("name") String name);
  App findByAppId(String appId);
}

+ 19 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/AuditRepository.java

@ -0,0 +1,19 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Audit;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface AuditRepository extends PagingAndSortingRepository<Audit, Long> {
  @Query("SELECT a from Audit a WHERE a.dataChangeCreatedBy = :owner")
  List<Audit> findByOwner(@Param("owner") String owner);
  @Query("SELECT a from Audit a WHERE a.dataChangeCreatedBy = :owner AND a.entityName =:entity AND a.opName = :op")
  List<Audit> findAudits(@Param("owner") String owner, @Param("entity") String entity,
      @Param("op") String op);
}

+ 19 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ClusterRepository.java

@ -0,0 +1,19 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Cluster;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
public interface ClusterRepository extends PagingAndSortingRepository<Cluster, Long> {
  List<Cluster> findByAppIdAndParentClusterId(String appId, Long parentClusterId);
  List<Cluster> findByAppId(String appId);
  Cluster findByAppIdAndName(String appId, String name);
  List<Cluster> findByParentClusterId(Long parentClusterId);
}

+ 21 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/CommitRepository.java

@ -0,0 +1,21 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Commit;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
public interface CommitRepository extends PagingAndSortingRepository<Commit, Long> {
  List<Commit> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String clusterName,
                                                                      String namespaceName, Pageable pageable);
  @Modifying
  @Query("update Commit set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
  int batchDelete(String appId, String clusterName, String namespaceName, String operator);
}

+ 20 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/GrayReleaseRuleRepository.java

@ -0,0 +1,20 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.GrayReleaseRule;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
public interface GrayReleaseRuleRepository extends PagingAndSortingRepository<GrayReleaseRule, Long> {
  GrayReleaseRule findTopByAppIdAndClusterNameAndNamespaceNameAndBranchNameOrderByIdDesc(String appId, String clusterName,
                                                                                         String namespaceName, String branchName);
  List<GrayReleaseRule> findByAppIdAndClusterNameAndNamespaceName(String appId,
                                                               String clusterName, String namespaceName);
  List<GrayReleaseRule> findFirst500ByIdGreaterThanOrderByIdAsc(Long id);
}

+ 48 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/InstanceConfigRepository.java

@ -0,0 +1,48 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import java.util.Date;
import java.util.List;
import java.util.Set;
public interface InstanceConfigRepository extends PagingAndSortingRepository<InstanceConfig, Long> {
  InstanceConfig findByInstanceIdAndConfigAppIdAndConfigNamespaceName(long instanceId, String
      configAppId, String configNamespaceName);
  Page<InstanceConfig> findByReleaseKeyAndDataChangeLastModifiedTimeAfter(String releaseKey, Date
      validDate, Pageable pageable);
  Page<InstanceConfig> findByConfigAppIdAndConfigClusterNameAndConfigNamespaceNameAndDataChangeLastModifiedTimeAfter(
      String appId, String clusterName, String namespaceName, Date validDate, Pageable pageable);
  List<InstanceConfig> findByConfigAppIdAndConfigClusterNameAndConfigNamespaceNameAndDataChangeLastModifiedTimeAfterAndReleaseKeyNotIn(
      String appId, String clusterName, String namespaceName, Date validDate, Set<String> releaseKey);
  @Modifying
  @Query("delete from InstanceConfig  where ConfigAppId=?1 and ConfigClusterName=?2 and ConfigNamespaceName = ?3")
  int batchDelete(String appId, String clusterName, String namespaceName);
  @Query(
      value = "select b.Id from `InstanceConfig` a inner join `Instance` b on b.Id =" +
          " a.`InstanceId` where a.`ConfigAppId` = :configAppId and a.`ConfigClusterName` = " +
          ":clusterName and a.`ConfigNamespaceName` = :namespaceName and a.`DataChange_LastTime` " +
          "> :validDate and b.`AppId` = :instanceAppId and ?#{#pageable.pageSize} > 0",
      countQuery = "select count(1) from `InstanceConfig` a inner join `Instance` b on b.id =" +
          " a.`InstanceId` where a.`ConfigAppId` = :configAppId and a.`ConfigClusterName` = " +
          ":clusterName and a.`ConfigNamespaceName` = :namespaceName and a.`DataChange_LastTime` " +
          "> :validDate and b.`AppId` = :instanceAppId",
      nativeQuery = true)
  Page<Object[]> findInstanceIdsByNamespaceAndInstanceAppId(
      @Param("instanceAppId") String instanceAppId, @Param("configAppId") String configAppId,
      @Param("clusterName") String clusterName, @Param("namespaceName") String namespaceName,
      @Param("validDate") Date validDate, Pageable pageable);
}

+ 9 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/InstanceRepository.java

@ -0,0 +1,9 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Instance;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface InstanceRepository extends PagingAndSortingRepository<Instance, Long> {
  Instance findByAppIdAndClusterNameAndDataCenterAndIp(String appId, String clusterName, String dataCenter, String ip);
}

+ 28 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java

@ -0,0 +1,28 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Item;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.Date;
import java.util.List;
public interface ItemRepository extends PagingAndSortingRepository<Item, Long> {
  Item findByNamespaceIdAndKey(Long namespaceId, String key);
  List<Item> findByNamespaceIdOrderByLineNumAsc(Long namespaceId);
  List<Item> findByNamespaceId(Long namespaceId);
  List<Item> findByNamespaceIdAndDataChangeLastModifiedTimeGreaterThan(Long namespaceId, Date date);
  Item findFirst1ByNamespaceIdOrderByLineNumDesc(Long namespaceId);
  @Modifying
  @Query("update Item set isdeleted=1,DataChange_LastModifiedBy = ?2 where namespaceId = ?1")
  int deleteByNamespaceId(long namespaceId, String operator);
}

+ 13 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceLockRepository.java

@ -0,0 +1,13 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.NamespaceLock;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface NamespaceLockRepository extends PagingAndSortingRepository<NamespaceLock, Long> {
  NamespaceLock findByNamespaceId(Long namespaceId);
  Long deleteByNamespaceId(Long namespaceId);
}

+ 28 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java

@ -0,0 +1,28 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
public interface NamespaceRepository extends PagingAndSortingRepository<Namespace, Long> {
  List<Namespace> findByAppIdAndClusterNameOrderByIdAsc(String appId, String clusterName);
  Namespace findByAppIdAndClusterNameAndNamespaceName(String appId, String clusterName, String namespaceName);
  @Modifying
  @Query("update Namespace set isdeleted=1,DataChange_LastModifiedBy = ?3 where appId=?1 and clusterName=?2")
  int batchDelete(String appId, String clusterName, String operator);
  List<Namespace> findByAppIdAndNamespaceNameOrderByIdAsc(String appId, String namespaceName);
  List<Namespace> findByNamespaceName(String namespaceName, Pageable page);
  int countByNamespaceNameAndAppIdNot(String namespaceName, String appId);
}

+ 16 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/PrivilegeRepository.java

@ -0,0 +1,16 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Privilege;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
public interface PrivilegeRepository extends PagingAndSortingRepository<Privilege, Long> {
  List<Privilege> findByNamespaceId(long namespaceId);
  List<Privilege> findByNamespaceIdAndPrivilType(long namespaceId, String privilType);
  Privilege findByNamespaceIdAndNameAndPrivilType(long namespaceId, String name, String privilType);
}

+ 26 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseHistoryRepository.java

@ -0,0 +1,26 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public interface ReleaseHistoryRepository extends PagingAndSortingRepository<ReleaseHistory, Long> {
  Page<ReleaseHistory> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String
      clusterName, String namespaceName, Pageable pageable);
  Page<ReleaseHistory> findByReleaseIdAndOperationOrderByIdDesc(long releaseId, int operation, Pageable pageable);
  Page<ReleaseHistory> findByPreviousReleaseIdAndOperationOrderByIdDesc(long previousReleaseId, int operation, Pageable pageable);
  @Modifying
  @Query("update ReleaseHistory set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
  int batchDelete(String appId, String clusterName, String namespaceName, String operator);
}

+ 26 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseMessageRepository.java

@ -0,0 +1,26 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import java.util.Collection;
import java.util.List;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public interface ReleaseMessageRepository extends PagingAndSortingRepository<ReleaseMessage, Long> {
  List<ReleaseMessage> findFirst500ByIdGreaterThanOrderByIdAsc(Long id);
  ReleaseMessage findTopByOrderByIdDesc();
  ReleaseMessage findTopByMessageInOrderByIdDesc(Collection<String> messages);
  List<ReleaseMessage> findFirst100ByMessageAndIdLessThanOrderByIdAsc(String message, Long id);
  @Query("select message, max(id) as id from ReleaseMessage where message in :messages group by message")
  List<Object[]> findLatestReleaseMessagesGroupByMessages(@Param("messages") Collection<String> messages);
}

+ 38 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseRepository.java

@ -0,0 +1,38 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Release;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Set;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public interface ReleaseRepository extends PagingAndSortingRepository<Release, Long> {
  Release findFirstByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(@Param("appId") String appId, @Param("clusterName") String clusterName,
                                                                                         @Param("namespaceName") String namespaceName);
  Release findByIdAndIsAbandonedFalse(long id);
  List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page);
  List<Release> findByAppIdAndClusterNameAndNamespaceNameAndIsAbandonedFalseOrderByIdDesc(String appId, String clusterName, String namespaceName, Pageable page);
  List<Release> findByReleaseKeyIn(Set<String> releaseKey);
  List<Release> findByIdIn(Set<Long> releaseIds);
  @Modifying
  @Query("update Release set isdeleted=1,DataChange_LastModifiedBy = ?4 where appId=?1 and clusterName=?2 and namespaceName = ?3")
  int batchDelete(String appId, String clusterName, String namespaceName, String operator);
  // For release history conversion program, need to delete after conversion it done
  List<Release> findByAppIdAndClusterNameAndNamespaceNameOrderByIdAsc(String appId, String clusterName, String namespaceName);
}

+ 12 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ServerConfigRepository.java

@ -0,0 +1,12 @@
package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.ServerConfig;
import org.springframework.data.repository.PagingAndSortingRepository;
/**
 * @author Jason Song(song_s@ctrip.com)
 */
public interface ServerConfigRepository extends PagingAndSortingRepository<ServerConfig, Long> {
  ServerConfig findTopByKeyAndCluster(String key, String cluster);
}

+ 0 - 0
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AdminService.java


Some files were not shown because too many files changed in this diff