├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .settings ├── .jsdtscope ├── org.eclipse.wst.jsdt.core.prefs ├── org.eclipse.wst.jsdt.ui.superType.container ├── org.eclipse.wst.jsdt.ui.superType.name └── org.eclipse.wst.validation.prefs ├── .travis.yml ├── .yo-rc.json ├── LICENSE ├── README.md ├── generators └── app │ ├── index.js │ └── templates │ └── src │ └── main │ ├── java │ └── package │ │ ├── config │ │ ├── _FF4jConfiguration.java │ │ ├── _SecurityCsrfRequestMatcher.java │ │ └── ff4j │ │ │ ├── _JHipsterAuthorizationManager.java │ │ │ ├── _JHipsterEhCacheCacheManager.java │ │ │ ├── _JHipsterEventRepository.java │ │ │ └── _JHipsterHazelcastCacheManager.java │ │ ├── service │ │ └── _MySampleFF4jService.java │ │ └── web │ │ └── rest │ │ └── _MySampleFF4jResource.java │ └── resources │ ├── config │ └── liquibase │ │ └── changelog │ │ └── _ff4jTables.xml │ └── ff4j.xml ├── gulpfile.js ├── package.json ├── test ├── jhipster-jdl.jh ├── templates │ └── default │ │ └── .yo-rc.json └── test-app.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [{package,bower}.json] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | generators/**/templates 3 | node_modules 4 | travis 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true 5 | }, 6 | "extends": ["airbnb-base"], 7 | "rules": { 8 | "indent": [2, 4], 9 | "linebreak-style": 0, 10 | "eol-last": 2, 11 | "quotes": [2, "single"], 12 | "semi": [2, "always"], 13 | "eqeqeq": [2, "smart"], 14 | "no-use-before-define": [2, "nofunc"], 15 | "no-unused-vars": [2, {"vars": "local", "args": "none"}], 16 | "no-multi-str": 2, 17 | "no-irregular-whitespace": 2, 18 | "comma-dangle": "off", 19 | "max-len": "off", 20 | "func-names": "off", 21 | "class-methods-use-this": "off", 22 | "no-underscore-dangle": "off", 23 | "no-plusplus": "off", 24 | 25 | "no-multi-assign": "off", 26 | "no-param-reassign": "off", 27 | "no-shadow": "off" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # All text files should have the "lf" (Unix) line endings 2 | * text eol=lf 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.java text 7 | *.js text 8 | *.css text 9 | *.html text 10 | 11 | # Denote all files that are truly binary and should not be modified. 12 | *.png binary 13 | *.jpg binary 14 | *.jar binary 15 | *.pdf binary 16 | *.eot binary 17 | *.ttf binary 18 | *.gzip binary 19 | *.gz binary 20 | *.ai binary 21 | *.eps binary 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .idea 4 | *.iml 5 | *.ipr 6 | *.iws 7 | atlassian-ide-plugin.xml 8 | /.project 9 | test/temp/ 10 | *debug.log* 11 | .vscode 12 | -------------------------------------------------------------------------------- /.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.wst.jsdt.core.compiler.codegen.inlineJsrBytecode=disabled 3 | org.eclipse.wst.jsdt.core.compiler.codegen.targetPlatform=1.2 4 | org.eclipse.wst.jsdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.wst.jsdt.core.compiler.compliance=1.4 6 | org.eclipse.wst.jsdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.wst.jsdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.wst.jsdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.wst.jsdt.core.compiler.problem.assertIdentifier=warning 10 | org.eclipse.wst.jsdt.core.compiler.source=1.3 11 | org.eclipse.wst.jsdt.core.compiler.source.type=module 12 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.JRE_CONTAINER -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Global -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.validation.prefs: -------------------------------------------------------------------------------- 1 | DELEGATES_PREFERENCE=delegateValidatorList 2 | USER_BUILD_PREFERENCE=enabledBuildValidatorListorg.eclipse.wst.wsi.ui.internal.WSIMessageValidator; 3 | USER_MANUAL_PREFERENCE=enabledManualValidatorListorg.eclipse.wst.wsi.ui.internal.WSIMessageValidator; 4 | USER_PREFERENCE=overrideGlobalPreferencestruedisableAllValidationtrueversion1.2.700.v201508251749 5 | eclipse.preferences.version=1 6 | override=true 7 | suspend=true 8 | vf.version=3 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | language: node_js 4 | node_js: 5 | - "6.10.0" 6 | before_install: 7 | - npm install -g npm 8 | - npm install -g yarn 9 | install: 10 | - yarn install 11 | - yarn link 12 | script: 13 | - gulp eslint 14 | - gulp test 15 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-jhipster-module": { 3 | "promptValues": { 4 | "githubName": "clun", 5 | "authorName": "Cedrick Lunven", 6 | "authorEmail": "cedrick.lunven@gmail.com", 7 | "authorUrl": "ff4j.org" 8 | } 9 | }, 10 | "generator-jhipster": { 11 | "promptValues": { 12 | "packageName": "fr.clun.demo", 13 | "nativeLanguage": "en" 14 | }, 15 | "jhipsterVersion": "4.5.3", 16 | "baseName": "appcache", 17 | "packageName": "fr.clun.demo", 18 | "packageFolder": "fr/clun/demo", 19 | "serverPort": "8080", 20 | "authenticationType": "session", 21 | "hibernateCache": "ehcache", 22 | "clusteredHttpSession": false, 23 | "websocket": false, 24 | "databaseType": "sql", 25 | "devDatabaseType": "h2Disk", 26 | "prodDatabaseType": "mysql", 27 | "searchEngine": false, 28 | "messageBroker": false, 29 | "serviceDiscoveryType": false, 30 | "buildTool": "maven", 31 | "enableSocialSignIn": false, 32 | "rememberMeKey": "67f1bd2a34cbd20c4384da270aecceb13e36328b", 33 | "clientFramework": "angular2", 34 | "useSass": false, 35 | "clientPackageManager": "yarn", 36 | "applicationType": "monolith", 37 | "testFrameworks": [], 38 | "jhiPrefix": "jhi", 39 | "otherModules": [ 40 | { 41 | "name": "generator-jhipster-ff4j", 42 | "version": "1.6.6" 43 | } 44 | ], 45 | "enableTranslation": true, 46 | "nativeLanguage": "en", 47 | "languages": [ 48 | "en" 49 | ] 50 | } 51 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-jhipster-ff4j 2 | 3 | JHipster module, A Jhipster module to integrate FeatureToggle capability in your application using FF4j (Feature Flipping for Java) 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![Dependency Status][daviddm-image]][daviddm-url] 7 | 8 | | JHIPSTER | + | FF4J | 9 | | ------------- |:-------------:| -----:| 10 | | | | | 11 | 12 | ## Prerequisites 13 | 14 | As this is a [JHipster](http://jhipster.github.io/) module, we expect you have JHipster and its related tools already installed: 15 | 16 | - [Installing JHipster](https://jhipster.github.io/installation.html) 17 | 18 | ## Installation 19 | 20 | ### With Yarn 21 | 22 | To install this module: 23 | 24 | ```bash 25 | yarn global add generator-jhipster-ff4j 26 | ``` 27 | 28 | To update this module: 29 | 30 | ```bash 31 | yarn global upgrade generator-jhipster-ff4j 32 | ``` 33 | 34 | ### With NPM 35 | 36 | To install this module: 37 | 38 | ```bash 39 | npm install -g generator-jhipster-ff4j 40 | ``` 41 | 42 | To update this module: 43 | 44 | ```bash 45 | npm update -g generator-jhipster-ff4j 46 | ``` 47 | 48 | ## Usage 49 | 50 | Run the module on a JHipster generated application: 51 | 52 | ```bash 53 | yo jhipster-ff4j 54 | ``` 55 | 56 | If you want don't want to answer each question you can use 57 | 58 | ```bash 59 | yo jhipster-ff4j default 60 | ``` 61 | This will enable auditing for all available entities (only ones created by the jhipster:entity generator) and add the audit log page under admin 62 | 63 | ## License 64 | 65 | Apache-2.0 66 | 67 | 68 | [npm-image]: https://img.shields.io/npm/v/generator-jhipster-ff4j.svg 69 | [npm-url]: https://npmjs.org/package/generator-jhipster-ff4j 70 | [travis-image]: https://travis-ci.org/clun/generator-jhipster-ff4j.svg?branch=master 71 | [travis-url]: https://travis-ci.org/clun/generator-jhipster-ff4j 72 | [daviddm-image]: https://david-dm.org/clun/generator-jhipster-ff4j.svg?theme=shields.io 73 | [daviddm-url]: https://david-dm.org/clun/generator-jhipster-module 74 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------------- 2 | // --- FF4j : Feature Flipping for Java 3 | // -------------------------------------------------------------------------------------------- 4 | 5 | const chalk = require('chalk'); 6 | const generator = require('yeoman-generator'); 7 | const packagejs = require('../../package.json'); 8 | 9 | // Stores JHipster variables 10 | const jhipsterVar = { moduleName: 'ff4j' }; 11 | 12 | // Stores JHipster functions 13 | const jhipsterFunc = {}; 14 | 15 | // Stores JHipster functions 16 | const jhipsterUtils = {}; 17 | 18 | // Constants 19 | const TPL = 'template'; 20 | const FF4J_VERSION = '1.6.6-SNAPSHOT'; 21 | 22 | // Functions available 23 | module.exports = generator.extend( { 24 | 25 | // ---------------------------------------------------- 26 | // Use compose Yeoman capability to maje jhipterVar available (like DB) 27 | // ---------------------------------------------------- 28 | initializing: { 29 | compose() { 30 | this.composeWith('jhipster:modules', { 31 | jhipsterVar, 32 | jhipsterFunc, 33 | jhipsterUtils}, 34 | this.options.testmode ? { local: require.resolve('generator-jhipster/generators/modules') } : null 35 | ); 36 | }, 37 | displayLogo() { 38 | // Have Yeoman greet the user. 39 | this.log(`Welcome to the ${chalk.bold.yellow('JHipster ff4j')} generator! ${chalk.yellow(`v${packagejs.version}\n`)}`); 40 | } 41 | }, 42 | 43 | // ---------------------------------------------------- 44 | // Ask questions to user to set up the generator 45 | // ---------------------------------------------------- 46 | prompting() { 47 | const done = this.async(); 48 | const prompts = [ 49 | { type: 'confirm', 50 | name: 'ff4jInstall', 51 | message: 'Do you want to install FF4j ?', 52 | default: false }, 53 | { 54 | type: 'list', 55 | name: 'ff4jFeatureStore', 56 | message: 'Which database would you like to use to store *Features* ?', 57 | choices: [ 58 | {name: 'Reuse current (' + jhipsterVar.databaseType+ ')', value: jhipsterVar.databaseType}, 59 | {name: 'SQL', value: 'sql'}, 60 | {name: 'Cassandra', value: 'cassandra'}, 61 | {name: 'MongoDB', value: 'mongodb'}, 62 | {name: 'ElasticSearch', value: 'elastic'}, 63 | {name: 'Redis', value: 'redis'}, 64 | {name: 'Consul', value: 'consul'}, 65 | ], 66 | default: jhipsterVar.databaseType }, 67 | { 68 | type: 'list', 69 | name: 'ff4jPropertyStore', 70 | message: 'Which database would you like to use to store *Properties* ?', 71 | choices: [ 72 | {name: 'Reuse current (' + jhipsterVar.databaseType+ ')', value: jhipsterVar.databaseType}, 73 | {name: 'SQL', value: 'sql'}, 74 | {name: 'Cassandra', value: 'cassandra'}, 75 | {name: 'MongoDB', value: 'mongodb'}, 76 | {name: 'ElasticSearch', value: 'elastic'}, 77 | {name: 'Redis', value: 'redis'}, 78 | {name: 'Consul', value: 'consul'}, 79 | ], 80 | default: jhipsterVar.databaseType }, 81 | { 82 | type: 'list', 83 | name: 'ff4jEventRepository', 84 | message: 'Which database would you like to use to store *AuditTrail* ?', 85 | choices: [ 86 | {name: 'Reuse current (' + jhipsterVar.databaseType+ ')', value: jhipsterVar.databaseType}, 87 | {name: 'SQL', value: 'sql'}, 88 | {name: 'Cassandra', value: 'cassandra'}, 89 | {name: 'MongoDB', value: 'mongodb'}, 90 | {name: 'ElasticSearch', value: 'elastic'}, 91 | {name: 'Redis', value: 'redis'}, 92 | ], 93 | default: jhipsterVar.databaseType }, 94 | { 95 | type: 'list', 96 | name: 'ff4jCache', 97 | message: 'Do you want to enable Caching ?', 98 | choices: [ 99 | {name: 'No.', value: 'no'}, 100 | {name: 'Reuse current (' + jhipsterVar.hibernateCache + ')', value: jhipsterVar.hibernateCache}, 101 | {name: 'Yes with EhCache', value: 'ehcache'}, 102 | {name: 'Yes with HazelCast', value: 'hazelcast'}, 103 | {name: 'Yes with Redis', value: 'redis'} 104 | ], 105 | default: 'no' }, 106 | { 107 | type: 'confirm', 108 | name: 'ff4jSample', 109 | message: 'Do you want to install the sample illustrating ff4j?', 110 | default: true 111 | } 112 | 113 | ]; 114 | 115 | // After prompting, put variables in the context 116 | this.prompt(prompts).then((props) => { 117 | this.props = props; 118 | done(); 119 | }); 120 | }, 121 | 122 | // ----------------------------------------- 123 | // Copy Files to be used 124 | // ----------------------------------------- 125 | writing() { 126 | 127 | // Abort if requested 128 | this.ff4jInstall = this.props.ff4jInstall; 129 | if (!this.ff4jInstall) { 130 | this.log(`\n${chalk.bold.green('[jhipster-ff4j]')} - Aborted`); 131 | return; 132 | } 133 | 134 | this.template = function (source, destination) { 135 | this.fs.copyTpl( 136 | this.templatePath(source), 137 | this.destinationPath(destination), 138 | this 139 | ); 140 | }; 141 | 142 | this.copyFiles = function (files) { 143 | files.forEach( function(file) { 144 | jhipsterFunc.copyTemplate(file.from, file.to, 145 | file.type? file.type: TPL, this, file.interpolate? { 'interpolate': file.interpolate } : undefined); 146 | }, this); 147 | }; 148 | 149 | this.isDbRequired = function(dbName) { 150 | return this.ff4jFeatureStore === dbName || 151 | this.ff4jEventRepository === dbName || 152 | this.ff4jPropertyStore === dbName || 153 | this.ff4jCache === dbName; 154 | }; 155 | 156 | // Extract core information 157 | this.baseName = jhipsterVar.baseName; 158 | this.packageName = jhipsterVar.packageName; 159 | this.angularAppName = jhipsterVar.angularAppName; 160 | 161 | // Core config 162 | this.clientFramework = jhipsterVar.clientFramework; 163 | this.clientPackageManager = jhipsterVar.clientPackageManager; 164 | this.enableTranslation = jhipsterVar.enableTranslation; 165 | 166 | // DataBase (used to setup ff4j as well) 167 | this.databaseType = jhipsterVar.databaseType; 168 | this.devDatabaseType = jhipsterVar.devDatabaseType; 169 | this.prodDatabaseType = jhipsterVar.prodDatabaseType; 170 | this.authenticationType = jhipsterVar.authenticationType; 171 | 172 | // Path Config 173 | const javaDir = jhipsterVar.javaDir; 174 | const resourceDir = jhipsterVar.resourceDir; 175 | const webappDir = jhipsterVar.webappDir; 176 | this.javaDir = jhipsterVar.javaDir; 177 | this.resourceDir = jhipsterVar.resourceDir; 178 | this.javaTemplateDir = 'src/main/java/package'; 179 | this.message = this.props.message; 180 | 181 | // Custom Parameters 182 | this.ff4jFeatureStore = this.props.ff4jFeatureStore; 183 | this.ff4jEventRepository = this.props.ff4jEventRepository; 184 | this.ff4jPropertyStore = this.props.ff4jPropertyStore; 185 | this.ff4jCache = this.props.ff4jCache; 186 | this.ff4jSample = this.props.ff4jSample; 187 | 188 | this.log(`\n${chalk.bold.green('[jhipster-ff4j]')} - Starting...`); 189 | 190 | // Update Dependencies 191 | if (jhipsterVar.buildTool === 'maven') { 192 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-core', FF4J_VERSION); 193 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-core] (maven)`); 194 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-web', FF4J_VERSION); 195 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-web] (maven)`); 196 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-spring-services', FF4J_VERSION); 197 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-spring-services] (maven)`); 198 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-spring-boot-web-api', FF4J_VERSION); 199 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-spring-boot-web-api] (maven)`); 200 | 201 | if (this.isDbRequired('sql')) { 202 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-springjdbc', FF4J_VERSION); 203 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-springjdbc] (maven)`); 204 | // As not provided by Jhispter, need to declare a driver and a datasource 205 | if (this.databaseType != 'sql') { 206 | jhipsterFunc.addMavenDependency('com.h2database', 'h2', '1.4.195'); 207 | jhipsterFunc.addMavenDependency('com.zaxxer', 'HikariCP', '2.6.0'); 208 | } 209 | } 210 | if (this.isDbRequired('cassandra')) { 211 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-cassandra', FF4J_VERSION); 212 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-cassandra] (maven)`); 213 | } 214 | if (this.isDbRequired('mongodb')) { 215 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-mongodb-v3', FF4J_VERSION); 216 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-mongodb-v3] (maven)`); 217 | } 218 | if (this.isDbRequired('elastic')) { 219 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-elastic', FF4J_VERSION); 220 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-elastic] (maven)`); 221 | } 222 | if (this.isDbRequired('hbase')) { 223 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-hbase', FF4J_VERSION); 224 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-hbase] (maven)`); 225 | } 226 | if (this.isDbRequired('neo4j')) { 227 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-neo4j', FF4J_VERSION); 228 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-neo4j] (maven)`); 229 | } 230 | if (this.isDbRequired('redis')) { 231 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-redis', FF4J_VERSION); 232 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-redis] (maven)`); 233 | } 234 | if (this.isDbRequired('consul')) { 235 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-consul', FF4J_VERSION); 236 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-consul] (maven)`); 237 | } 238 | if (this.isDbRequired('ehcache') || this.isDbRequired('terracotta')) { 239 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-ehcache', FF4J_VERSION); 240 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-ehcache] (maven)`); 241 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-jcache', FF4J_VERSION); 242 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-jcache] (maven)`); 243 | } 244 | if (this.isDbRequired('ignite')) { 245 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-ignite', FF4J_VERSION); 246 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-ignite] (maven)`); 247 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-jcache', FF4J_VERSION); 248 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-jcache] (maven)`); 249 | } 250 | if (this.isDbRequired('hazelcast')) { 251 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-hazelcast', FF4J_VERSION); 252 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-hazelcast] (maven)`); 253 | jhipsterFunc.addMavenDependency('org.ff4j', 'ff4j-store-jcache', FF4J_VERSION); 254 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-jcache] (maven)`); 255 | } 256 | 257 | } else if (jhipsterVar.buildTool === 'gradle') { 258 | 259 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-core', FF4J_VERSION); 260 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-core] (gradle)`); 261 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-web', FF4J_VERSION); 262 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-web] (gradle)`); 263 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-spring-services', FF4J_VERSION); 264 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-spring-services] (gradle)`); 265 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-spring-boot-web-api', FF4J_VERSION); 266 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-spring-boot-web-api] (gradle)`); 267 | if (this.isDbRequired('sql')) { 268 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-springjdbc', FF4J_VERSION); 269 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-springjdbc] (gradle)`); 270 | // As not provided by Jhispter, need to declare a driver and a datasource 271 | if (this.databaseType != 'sql') { 272 | jhipsterFunc.addGradleDependency('com.h2database', 'h2', '1.4.195'); 273 | jhipsterFunc.addGradleDependency('com.zaxxer', 'HikariCP', '2.6.0'); 274 | } 275 | } 276 | if (this.isDbRequired('cassandra')) { 277 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-cassandra', FF4J_VERSION); 278 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-cassandra] (gradle)`); 279 | } 280 | if (this.isDbRequired('mongodb')) { 281 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-mongodb-v3', FF4J_VERSION); 282 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-mongodb-v3] (gradle)`); 283 | } 284 | if (this.isDbRequired('elastic')) { 285 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-elastic', FF4J_VERSION); 286 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-elastic] (gradle)`); 287 | } 288 | if (this.isDbRequired('hbase')) { 289 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-hbase', FF4J_VERSION); 290 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-hbase] (gradle)`); 291 | } 292 | if (this.isDbRequired('neo4j')) { 293 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-neo4j', FF4J_VERSION); 294 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-neo4j] (gradle)`); 295 | } 296 | if (this.isDbRequired('redis')) { 297 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-redis', FF4J_VERSION); 298 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-redis] (gradle)`); 299 | } 300 | if (this.isDbRequired('consul')) { 301 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-consul', FF4J_VERSION); 302 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-consul] (gradle)`); 303 | } 304 | if (this.isDbRequired('ehcache') || this.isDbRequired('terracotta')) { 305 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-ehcache', FF4J_VERSION); 306 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-ehcache] (gradle)`); 307 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-jcache', FF4J_VERSION); 308 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-jcache] (gradle)`); 309 | } 310 | if (this.isDbRequired('ignite')) { 311 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-ignite', FF4J_VERSION); 312 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-ignite] (gradle)`); 313 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-jcache', FF4J_VERSION); 314 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-jcache] (gradle)`); 315 | } 316 | if (this.isDbRequired('hazelcast')) { 317 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-hazelcast', FF4J_VERSION); 318 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-hazelcast] (gradle)`); 319 | jhipsterFunc.addGradleDependency('org.ff4j', 'ff4j-store-jcache', FF4J_VERSION); 320 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Add dependency [ff4j-store-jcache] (gradle)`); 321 | } 322 | } 323 | 324 | // Copy Files (Java) 325 | files = [ 326 | { from: this.javaTemplateDir + '/config/_FF4jConfiguration.java', 327 | to: this.javaDir + 'config/FF4jConfiguration.java'}, 328 | { from: this.javaTemplateDir + '/config/ff4j/_JHipsterAuthorizationManager.java', 329 | to: this.javaDir + 'config/ff4j/JHipsterAuthorizationManager.java'}, 330 | { from: this.javaTemplateDir + '/config/ff4j/_JHipsterEventRepository.java', 331 | to: this.javaDir + 'config/ff4j/JHipsterEventRepository.java'}, 332 | { from: this.javaTemplateDir + '/config/_SecurityCsrfRequestMatcher.java', 333 | to: this.javaDir + 'config/SecurityCsrfRequestMatcher.java'}, 334 | { from: 'src/main/resources/config/liquibase/changelog/_ff4jTables.xml', 335 | to: this.resourceDir + 'config/liquibase/changelog/00000000000001_added_ff4jTables.xml', 336 | interpolate: this.interpolateRegex } 337 | ]; 338 | 339 | if (this.ff4jCache === 'hazelcast') { 340 | files.push({ from: this.javaTemplateDir + '/config/ff4j/_JHipsterHazelcastCacheManager.java', 341 | to: this.javaDir + 'config/ff4j/JHipsterHazelcastCacheManager.java'}); 342 | } 343 | if (this.ff4jCache === 'ehcache') { 344 | files.push({ from: this.javaTemplateDir + '/config/ff4j/_JHipsterEhCacheCacheManager.java', 345 | to: this.javaDir + 'config/ff4j/JHipsterEhCacheCacheManager.java'}); 346 | } 347 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Import Java files`); 348 | this.copyFiles(files); 349 | 350 | // Liquibase (add Tables in the DB) 351 | jhipsterFunc.addChangelogToLiquibase('00000000000001_added_ff4jTables'); 352 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Import Liquibase changelog files`); 353 | 354 | // Add link in WebPack 355 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Update Webpack Configuration`); 356 | jhipsterFunc.rewriteFile('webpack/webpack.dev.js', 'jhipster-needle-add-entity-to-webpack', "'/ff4j-web-console',"); 357 | 358 | // Add Reference the Admin menu 359 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Update Admin Menu`); 360 | if (this.clientFramework === 'angular1') { 361 | jhipsterFunc.rewriteFile( 362 | 'src/main/webapp/app/layouts/navbar/navbar.html', 363 | 'jhipster-needle-add-element-to-admin-menu', 364 | 365 | '
  • \n'+ 366 | ' \n' + 367 | ' \n '+ 368 | 'Feature Toggle\n'+ 369 | '
  • \n'); 370 | } else { 371 | jhipsterFunc.rewriteFile( 372 | 'src/main/webapp/app/layouts/navbar/navbar.component.html', 373 | 'jhipster-needle-add-element-to-admin-menu', 374 | 375 | '
  • \n'+ 376 | '  \n' + 377 | ' Feature Toggle\n' + 378 | ' \n
  • '); 379 | } 380 | 381 | // Update CSRF to allow request 382 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Update security configuration (csrf, url)`); 383 | jhipsterFunc.replaceContent(this.javaDir + 'config/SecurityConfiguration.java', 384 | '.csrf()', 385 | '.csrf().requireCsrfProtectionMatcher(new SecurityCsrfRequestMatcher())'); 386 | 387 | // Secured access to the servlet 388 | if (this.authenticationType === 'jwt' || this.authenticationType === 'oauth2') { 389 | // TODO : to change, cannot override the filters (jwt, oauth2) to authorize the console 390 | jhipsterFunc.replaceContent(this.javaDir + 'config/SecurityConfiguration.java', 391 | ' .authorizeRequests()', 392 | ' .authorizeRequests()\n .antMatchers("/ff4j-web-console/**").permitAll()'); 393 | } else { 394 | jhipsterFunc.replaceContent(this.javaDir + 'config/SecurityConfiguration.java', 395 | ' .authorizeRequests()', 396 | ' .authorizeRequests()\n .antMatchers("/ff4j-web-console/**").hasAuthority('+ this.packageName + '.security.AuthoritiesConstants.ADMIN)'); 397 | } 398 | 399 | // Update application.yml based on configuration of FF4J 400 | let ff4jConfig = '\n# ===================================================================\n'; 401 | ff4jConfig+='# FF4j specific properties\n'; 402 | ff4jConfig+='# ===================================================================\n'; 403 | ff4jConfig+='ff4j:\n'; 404 | ff4jConfig+=' core:\n'; 405 | ff4jConfig+=' autocreate: true\n'; 406 | ff4jConfig+=' audit:\n'; 407 | ff4jConfig+=' enabled: true\n'; 408 | ff4jConfig+=' log2jhispter: true\n'; 409 | // Add default settings in Properties if the DB is not the same as current APP 410 | if (this.isDbRequired('elastic')) { 411 | ff4jConfig+=' elastic:\n'; 412 | ff4jConfig+=' hostName: localhost\n'; 413 | ff4jConfig+=' port: 9200\n'; 414 | ff4jConfig+=' index: ff4j\n'; 415 | } 416 | if (this.isDbRequired('hbase')) { 417 | ff4jConfig+=' hbase:\n'; 418 | ff4jConfig+=' hostname: localhost\n'; 419 | ff4jConfig+=' port: 2181\n'; 420 | } 421 | if (this.isDbRequired('neo4j')) { 422 | ff4jConfig+=' hbase:\n'; 423 | ff4jConfig+=' hostname: localhost\n'; 424 | ff4jConfig+=' port: 7474\n'; 425 | } 426 | if (this.isDbRequired('redis')) { 427 | ff4jConfig+=' redis:\n'; 428 | ff4jConfig+=' hostname: localhost\n'; 429 | ff4jConfig+=' port: 6379\n'; 430 | ff4jConfig+=' password:\n'; 431 | } 432 | if (this.isDbRequired('consul')) { 433 | ff4jConfig+=' consul:\n'; 434 | ff4jConfig+=' hostname: localhost\n'; 435 | ff4jConfig+=' port: 8500\n'; 436 | } 437 | // Expect to find a Cluster, if not use this default info 438 | if (this.isDbRequired('cassandra') && this.databaseType != 'cassandra') { 439 | ff4jConfig+=' cassandra:\n'; 440 | ff4jConfig+=' hostname: localhost\n'; 441 | ff4jConfig+=' port: 9042\n'; 442 | } 443 | // Expect to find a MongoClient, if not use this default info 444 | if (this.isDbRequired('mongodb') && this.databaseType != 'mongodb') { 445 | ff4jConfig+=' mongodb:\n'; 446 | ff4jConfig+=' hostname: localhost\n'; 447 | ff4jConfig+=' port: 27017\n'; 448 | } 449 | if (this.isDbRequired('sql') && this.databaseType != 'sql') { 450 | ff4jConfig+=' sql:\n'; 451 | ff4jConfig+=' dataSourceClassName: com.zaxxer.hikari.HikariDataSource\n'; 452 | ff4jConfig+=' dataSourceUrl: jdbc:h2:file:./target/h2db/db/ff4j;DB_CLOSE_DELAY=-1\n'; 453 | ff4jConfig+=' username: ff4j\n'; 454 | ff4jConfig+=' password: ff4j\n'; 455 | } 456 | 457 | jhipsterFunc.rewriteFile('src/main/resources/config/application.yml', 'application:', ff4jConfig); 458 | 459 | // Install the FF4J sample to illustrate working with FF4J 460 | if (this.ff4jSample) { 461 | this.log(`${chalk.bold.green('[jhipster-ff4j]')} - Install MySampleFF4j (will populate db at startup)`); 462 | files = [ 463 | { from: this.javaTemplateDir + '/service/_MySampleFF4jService.java', 464 | to: this.javaDir + 'service/MySampleFF4jService.java'}, 465 | 466 | { from: this.javaTemplateDir + '/web/rest/_MySampleFF4jResource.java', 467 | to: this.javaDir + 'web/rest/MySampleFF4jResource.java'}, 468 | 469 | { from: 'src/main/resources/ff4j.xml', to: this.resourceDir + 'ff4j.xml'}, 470 | ]; 471 | this.copyFiles(files); 472 | } 473 | }, 474 | 475 | install() { 476 | this.log(`install()`); 477 | let logMsg = 478 | `To install your dependencies manually, run: ${chalk.yellow.bold(`${this.clientPackageManager} install`)}`; 479 | 480 | if (this.clientFramework === 'angular1') { 481 | logMsg = 482 | `To install your dependencies manually, run: ${chalk.yellow.bold(`${this.clientPackageManager} install & bower install`)}`; 483 | } 484 | const injectDependenciesAndConstants = (err) => { 485 | if (err) { 486 | this.warning('Install of dependencies failed!'); 487 | this.log(logMsg); 488 | } else if (this.clientFramework === 'angular1') { 489 | this.spawnCommand('gulp', ['install']); 490 | } 491 | }; 492 | const installConfig = { 493 | bower: this.clientFramework === 'angular1', 494 | npm: this.clientPackageManager !== 'yarn', 495 | yarn: this.clientPackageManager === 'yarn', 496 | callback: injectDependenciesAndConstants 497 | }; 498 | this.installDependencies(installConfig); 499 | }, 500 | 501 | end() { 502 | if (this.ff4jInstall) { 503 | this.log('End of ff4j generator'); 504 | } 505 | } 506 | }); 507 | 508 | 509 | -------------------------------------------------------------------------------- /generators/app/templates/src/main/java/package/config/_FF4jConfiguration.java: -------------------------------------------------------------------------------- 1 | package <%=packageName%>.config; 2 | 3 | /* 4 | * #%L 5 | * %% 6 | * Copyright (C) 2013 - 2017 FF4J 7 | * %% 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * #L% 20 | */ 21 | import org.ff4j.FF4j; 22 | import org.ff4j.web.FF4jDispatcherServlet; 23 | 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.beans.factory.annotation.Value; 28 | import org.springframework.boot.autoconfigure.AutoConfigureBefore; 29 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 30 | import org.springframework.boot.web.support.SpringBootServletInitializer; 31 | import org.springframework.context.annotation.Bean; 32 | import org.springframework.context.annotation.ComponentScan; 33 | import org.springframework.context.annotation.Configuration; 34 | import org.springframework.boot.actuate.audit.AuditEventRepository; 35 | import io.github.jhipster.config.JHipsterProperties; 36 | 37 | <%_ if (databaseType === 'sql' || databaseType === 'mongodb') { _%> 38 | import <%=packageName%>.config.DatabaseConfiguration;<% } %> 39 | 40 | import <%=packageName%>.config.WebConfigurer; 41 | import <%=packageName%>.service.UserService; 42 | import <%=packageName%>.config.ff4j.JHipsterAuthorizationManager; 43 | import <%=packageName%>.config.ff4j.JHipsterEventRepository; 44 | 45 | <%_ if (ff4jFeatureStore === 'sql') { _%> 46 | import org.ff4j.springjdbc.store.FeatureStoreSpringJdbc;<% } %> 47 | <%_ if (ff4jPropertyStore === 'sql') { _%> 48 | import org.ff4j.springjdbc.store.PropertyStoreSpringJdbc;<% } %> 49 | <%_ if (ff4jEventRepository === 'sql') { _%> 50 | import org.ff4j.springjdbc.store.EventRepositorySpringJdbc;<% } %> 51 | <%_ if (ff4jFeatureStore === 'sql' || ff4jEventRepository === 'sql' || ff4jPropertyStore ==='sql') { _%> 52 | import com.zaxxer.hikari.HikariDataSource;<% } %> 53 | <%_ if (ff4jFeatureStore === 'mongodb') { _%> 54 | import org.ff4j.mongo.store.FeatureStoreMongo;<% } %> 55 | <%_ if (ff4jPropertyStore === 'mongodb') { _%> 56 | import org.ff4j.mongo.store.PropertyStoreMongo;<% } %> 57 | <%_ if (ff4jEventRepository === 'mongodb') { _%> 58 | import org.ff4j.mongo.store.EventRepositoryMongo;<% } %> 59 | <%_ if (ff4jFeatureStore === 'mongodb' || ff4jEventRepository === 'mongodb' || ff4jPropertyStore ==='mongodb') { _%> 60 | import com.mongodb.MongoClient;<% } %> 61 | <%_ if (ff4jFeatureStore === 'cassandra') { _%> 62 | import org.ff4j.cassandra.store.FeatureStoreCassandra;<% } %> 63 | <%_ if (ff4jPropertyStore === 'cassandra') { _%> 64 | import org.ff4j.cassandra.store.PropertyStoreCassandra;<% } %> 65 | <%_ if (ff4jEventRepository === 'cassandra') { _%> 66 | import org.ff4j.cassandra.store.EventRepositoryCassandra;<% } %> 67 | <%_ if (ff4jFeatureStore === 'cassandra' || ff4jEventRepository === 'cassandra' || ff4jPropertyStore ==='cassandra') { _%> 68 | import com.datastax.driver.core.Cluster; 69 | import org.ff4j.cassandra.CassandraConnection;<% } %> 70 | <%_ if (ff4jFeatureStore === 'redis') { _%> 71 | import org.ff4j.store.FeatureStoreRedis;<% } %> 72 | <%_ if (ff4jPropertyStore === 'redis') { _%> 73 | import org.ff4j.store.PropertyStoreRedis;<% } %> 74 | <%_ if (ff4jEventRepository === 'redis') { _%> 75 | import org.ff4j.store.EventRepositoryRedis;<% } %> 76 | <%_ if (ff4jCache === 'redis') { _%> 77 | import org.ff4j.cache.FF4jCacheManagerRedis;<% } %> 78 | <%_ if (ff4jFeatureStore === 'redis' || ff4jEventRepository === 'redis' || ff4jPropertyStore ==='redis'|| ff4jCache ==='redis') { _%> 79 | import org.ff4j.utils.Util; 80 | import org.ff4j.redis.RedisConnection;<% } %> 81 | <%_ if (ff4jFeatureStore === 'consul') { _%> 82 | import org.ff4j.consul.store.FeatureStoreConsul;<% } %> 83 | <%_ if (ff4jPropertyStore === 'consul') { _%> 84 | import org.ff4j.consul.store.PropertyStoreConsul;<% } %> 85 | <%_ if (ff4jFeatureStore === 'consul' || ff4jPropertyStore ==='consul') { _%> 86 | import com.orbitz.consul.Consul; 87 | import com.google.common.net.HostAndPort; 88 | import org.ff4j.consul.ConsulConnection;<% } %> 89 | <%_ if (ff4jFeatureStore === 'elastic') { _%> 90 | import org.ff4j.elastic.store.FeatureStoreElastic;<% } %> 91 | <%_ if (ff4jPropertyStore === 'elastic') { _%> 92 | import org.ff4j.elastic.store.PropertyStoreElasti;<% } %> 93 | <%_ if (ff4jEventRepository === 'elastic') { _%> 94 | import org.ff4j.elastic.store.EventRepositoryElastic;<% } %> 95 | <%_ if (ff4jFeatureStore === 'elastic' || ff4jEventRepository === 'elastic' || ff4jPropertyStore ==='elastic') { _%> 96 | import java.net.MalformedURLException; 97 | import java.net.URL; 98 | import org.ff4j.elastic.ElasticConnection; 99 | import org.ff4j.elastic.ElasticConnectionMode; 100 | import org.ff4j.exception.FeatureAccessException;<% } %> 101 | <%_ if (ff4jCache != 'no') { _%> 102 | import org.ff4j.cache.FF4JCacheManager; 103 | import org.ff4j.cache.FF4jCacheProxy; 104 | import org.ff4j.cache.FF4jJCacheManager;<% } %> 105 | <%_ if (ff4jCache === 'ehcache') { _%> 106 | import <%=packageName%>.config.ff4j.JHipsterEhCacheCacheManager;<% } %> 107 | <%_ if (ff4jCache === 'hazelcast') { _%> 108 | import com.hazelcast.core.HazelcastInstance; 109 | import <%=packageName%>config.ff4j.JHipsterEhCacheCacheManager; 110 | import <%=packageName%>.config.ff4j.JHipsterHazelcastCacheManager;<% } %> 111 | 112 | /** 113 | * Configuration of FF4J (ff4j.org) to work with JHipster 114 | * 115 | * @author Clunven (@clunven) 116 | */ 117 | @Configuration 118 | @ComponentScan(basePackages={"org.ff4j.spring.boot.web.api.resources", "org.ff4j.services", "org.ff4j.aop"}) 119 | @AutoConfigureBefore(value = { WebConfigurer.class<%_ if (databaseType === 'sql' || databaseType === 'mongodb') { _%>, DatabaseConfiguration.class <% } %>}) 120 | public class FF4jConfiguration extends SpringBootServletInitializer { 121 | 122 | /** Default URL. */ 123 | public static final String FF4J_WEBCONSOLE_URL = "ff4j-web-console"; 124 | 125 | /** logging. */ 126 | private final Logger log = LoggerFactory.getLogger(FF4jConfiguration.class); 127 | 128 | /** User services of Jhispter to be used in FF4j. */ 129 | private final UserService userServices; 130 | 131 | /** User services of Jhispter to be used in FF4j. */ 132 | private final AuditEventRepository auditServices; 133 | 134 | /** User services of Jhispter to be used in FF4j. */ 135 | private final JHipsterProperties jHipsterConfig; 136 | 137 | @Value("${ff4j.core.autocreate}") 138 | private boolean enableAutoCreate = false; 139 | 140 | @Value("${ff4j.audit.enabled}") 141 | private boolean enableAudit = true; 142 | 143 | @Value("${ff4j.audit.log2jhispter}") 144 | private boolean log2Jhipster = true; 145 | 146 | /** 147 | * Inject JHipster Settings. 148 | * 149 | * @param jHipsterProperties 150 | * settings 151 | */ 152 | public FF4jConfiguration(JHipsterProperties jHipsterProperties, UserService user, AuditEventRepository audit) { 153 | this.userServices = user; 154 | this.auditServices = audit; 155 | this.jHipsterConfig = jHipsterProperties; 156 | log.info("Configuration Jhipster:" + jHipsterConfig.toString()); 157 | } 158 | 159 | @Bean 160 | public FF4j getFF4j() { 161 | FF4j ff4j = new FF4j(); 162 | <%_ if (ff4jFeatureStore === 'sql') { _%> 163 | ff4j.setFeatureStore(new FeatureStoreSpringJdbc(hikariDataSource)); 164 | log.info("Features are stored in RDBMS."); 165 | <% } else if (ff4jFeatureStore === 'elastic') { _%> 166 | ff4j.setFeatureStore( new FeatureStoreElastic(getElasticConnection())); 167 | log.info("Features are stored in ElasticSearch."); 168 | <% } else if (ff4jFeatureStore === 'redis') { _%> 169 | ff4j.setFeatureStore(new FeatureStoreRedis(getRedisConnection())); 170 | log.info("Features are stored in Redis."); 171 | <% } else if (ff4jFeatureStore === 'consul') { _%> 172 | ff4j.setFeatureStore(new FeatureStoreConsul(getConsulConnection())); 173 | log.info("Features are stored in Consul."); 174 | <% } else if (ff4jFeatureStore === 'mongodb') { _%> 175 | log.info("Features are stored in MongoDB dbName=[" + mongoDatabaseName + "]"); 176 | ff4j.setFeatureStore(new FeatureStoreMongo(mongoClient, mongoDatabaseName)); 177 | <% } else if (ff4jFeatureStore === 'cassandra') { _%> 178 | ff4j.setFeatureStore(new FeatureStoreCassandra(getCassandraConnection())); 179 | log.info("Features are store in Cassandra."); 180 | <%_ } _%> 181 | <%_ if (ff4jPropertyStore === 'sql') { _%> 182 | ff4j.setPropertiesStore(new PropertyStoreSpringJdbc(hikariDataSource)); 183 | log.info("Properties are stored in RDBMS."); 184 | <% } else if (ff4jPropertyStore === 'elastic') { _%> 185 | ff4j.setPropertiesStore(new PropertyStoreElastic(getElasticConnection())); 186 | log.info("Properties are stored in ElasticSearch."); 187 | <% } else if (ff4jPropertyStore === 'redis') { _%> 188 | ff4j.setPropertiesStore(new PropertyStoreRedis(getRedisConnection())); 189 | log.info("Properties are stored in Redis."); 190 | <% } else if (ff4jPropertyStore === 'consul') { _%> 191 | ff4j.setPropertiesStore(new PropertyStoreConsul(getConsulConnection())); 192 | log.info("Properties are stored in Consul."); 193 | <% } else if (ff4jPropertyStore === 'mongodb') { _%> 194 | ff4j.setPropertiesStore(new PropertyStoreMongo(mongoClient, mongoDatabaseName)); 195 | log.info("Properties are stored in MongoDB."); 196 | <% } else if (ff4jPropertyStore === 'cassandra') { _%> 197 | ff4j.setPropertiesStore(new PropertyStoreCassandra(getCassandraConnection())); 198 | log.info("Properties are store in Cassandra."); 199 | <%_ } _%> 200 | <%_ if (ff4jEventRepository === 'sql') { _%> 201 | ff4j.setEventRepository(new EventRepositorySpringJdbc(hikariDataSource)); 202 | log.info("AuditEvents are stored in RDBMS."); 203 | <% } else if (ff4jEventRepository === 'elastic') { _%> 204 | ff4j.setEventRepository(new EventRepositoryElastic(getElasticConnection())); 205 | log.info("AuditEvents are stored in ElasticSearch."); 206 | <% } else if (ff4jEventRepository === 'redis') { _%> 207 | ff4j.setEventRepository(new EventRepositoryRedis(getRedisConnection())); 208 | log.info("AuditEvents are stored in Redis."); 209 | <% } else if (ff4jPropertyStore === 'mongodb') { _%> 210 | ff4j.setEventRepository(new EventRepositoryMongo(mongoClient, mongoDatabaseName)); 211 | log.info("AuditEvents are stored in MongoDB."); 212 | <% } else if (ff4jEventRepository === 'cassandra') { _%> 213 | ff4j.setEventRepository(new EventRepositoryCassandra(getCassandraConnection())); 214 | log.info("AuditEvents are store in Cassandra."); 215 | <%_ } _%> 216 | <%_ if (ff4jCache === 'redis') { _%> 217 | FF4JCacheManager ff4jCache = new FF4jCacheManagerRedis(getRedisConnection());<%_ } _%> 218 | <%_ if (ff4jCache === 'ehcache') { _%> 219 | FF4JCacheManager ff4jCache = new JHipsterEhCacheCacheManager();<%_ } _%> 220 | <%_ if (ff4jCache === 'hazelcast') { _%> 221 | FF4JCacheManager ff4jCache = new JHipsterHazelcastCacheManager(hazelcastInstance);<%_ } _%> 222 | <%_ if (ff4jCache != 'no') { _%> 223 | FF4jCacheProxy cacheProxy = new FF4jCacheProxy(ff4j.getFeatureStore(), ff4j.getPropertiesStore(), ff4jCache); 224 | ff4j.setFeatureStore(cacheProxy); 225 | ff4j.setPropertiesStore(cacheProxy);<%_ } _%> 226 | if (log2Jhipster) { 227 | ff4j.setEventRepository(new JHipsterEventRepository(ff4j.getEventRepository(), auditServices)); 228 | } 229 | ff4j.audit(enableAudit); 230 | ff4j.autoCreate(enableAutoCreate); 231 | ff4j.setAuthorizationsManager(new JHipsterAuthorizationManager(userServices)); 232 | return ff4j; 233 | } 234 | 235 | <%_ if (ff4jCache === 'hazelcast') { _%> 236 | private HazelcastInstance hazelcastInstance; 237 | 238 | @Autowired(required = false) 239 | public void setHazelcastInstance(HazelcastInstance hazelcastInstance) { 240 | this.hazelcastInstance = hazelcastInstance; 241 | }<%_ } _%> 242 | <%_ if (ff4jFeatureStore === 'sql' || ff4jEventRepository === 'sql' || ff4jPropertyStore ==='sql') { _%> 243 | private HikariDataSource hikariDataSource; 244 | 245 | @Autowired(required = false) 246 | public void setHikariDataSource(HikariDataSource hikariDataSource) { 247 | this.hikariDataSource = hikariDataSource; 248 | }<% } %> 249 | <%_ if (ff4jFeatureStore === 'cassandra' || ff4jPropertyStore ==='cassandra' || ff4jEventRepository === 'cassandra') { _%> 250 | private Cluster cluster; 251 | 252 | @Autowired(required = false) 253 | public void setClusterCassandra(Cluster cluster) { 254 | this.cluster = cluster; 255 | } 256 | 257 | @Bean 258 | public CassandraConnection getCassandraConnection() { 259 | return new CassandraConnection(cluster); 260 | }<% } %> 261 | <%_ if (ff4jFeatureStore === 'mongodb' || ff4jPropertyStore ==='mongodb' || ff4jEventRepository === 'mongodb') { _%> 262 | @Value("${spring.data.mongodb.database}") 263 | private String mongoDatabaseName = "appmongo"; 264 | 265 | @Autowired 266 | private MongoClient mongoClient;<% } %> 267 | <%_ if (ff4jFeatureStore === 'elastic' || ff4jEventRepository === 'elastic' || ff4jPropertyStore ==='elastic') { _%> 268 | @Value("${ff4j.elastic.index}") 269 | private String elasticIndexName; 270 | 271 | @Value("${ff4j.elastic.hostName}") 272 | private String elasticHostName; 273 | 274 | @Value("${ff4j.elastic.port}") 275 | private Integer elasticPort; 276 | 277 | @Bean 278 | public ElasticConnection getElasticConnection() { 279 | URL urlElastic; 280 | try { 281 | urlElastic = new URL("http://" + elasticHostName + ":" + elasticPort); 282 | return new ElasticConnection(ElasticConnectionMode.JEST_CLIENT, elasticIndexName, urlElastic); 283 | } catch (MalformedURLException e) { 284 | throw new FeatureAccessException("Cannot access elastic", e); 285 | } 286 | } 287 | <% } %> 288 | <%_ if (ff4jFeatureStore === 'redis' || ff4jEventRepository === 'redis' || ff4jPropertyStore ==='redis' || ff4jCache === 'redis') { _%> 289 | 290 | @Value("${ff4j.redis.hostname}") 291 | private String redisHostName; 292 | 293 | @Value("${ff4j.redis.port}") 294 | private Integer redisPort; 295 | 296 | @Value("${ff4j.redis.password}") 297 | private String redisPassword; 298 | 299 | @Bean 300 | public RedisConnection getRedisConnection() { 301 | if (Util.hasLength(redisPassword)) { 302 | return new RedisConnection(redisHostName, redisPort, redisPassword); 303 | } 304 | return new RedisConnection(redisHostName, redisPort); 305 | } 306 | <% } %> 307 | 308 | <%_ if (ff4jFeatureStore === 'consul' || ff4jPropertyStore ==='consul') { _%> 309 | // --------- Consul --------- 310 | 311 | @Value("${ff4j.consul.hostname}") 312 | private String consulHost; 313 | 314 | @Value("${ff4j.consul.port}") 315 | private Integer consulPort; 316 | 317 | @Bean 318 | public ConsulConnection getConsulConnection() { 319 | return new ConsulConnection( 320 | Consul.builder().withHostAndPort( 321 | HostAndPort.fromParts(consulHost, consulPort)).build()); 322 | }<% } %> 323 | 324 | @Bean 325 | public ServletRegistrationBean ff4jDispatcherServletRegistrationBean(FF4jDispatcherServlet ff4jDispatcherServlet) { 326 | return new ServletRegistrationBean(ff4jDispatcherServlet, "/" + FF4J_WEBCONSOLE_URL + "/*"); 327 | } 328 | 329 | @Bean 330 | public FF4jDispatcherServlet getFF4jDispatcherServlet(FF4j ff4j) { 331 | FF4jDispatcherServlet ff4jConsoleServlet = new FF4jDispatcherServlet(); 332 | ff4jConsoleServlet.setFf4j(ff4j); 333 | return ff4jConsoleServlet; 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /generators/app/templates/src/main/java/package/config/_SecurityCsrfRequestMatcher.java: -------------------------------------------------------------------------------- 1 | package <%=packageName%>.config; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | 7 | import org.springframework.security.web.util.matcher.RequestMatcher; 8 | 9 | /** 10 | * This matcher is introduced by FF4j to allow the servlet to perform 11 | * some POST requests (features creation, update...) without disabling 12 | * the useful CSRF filter. 13 | * 14 | * @see jhipster-ff4j 15 | * @author clunven (@clunven) 16 | */ 17 | public class SecurityCsrfRequestMatcher implements RequestMatcher { 18 | 19 | /** Standard HTTP METHODS which do not require CSRF. */ 20 | private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$"); 21 | 22 | @Override 23 | public boolean matches(HttpServletRequest request) { 24 | if (allowedMethods.matcher(request.getMethod()).matches()) { 25 | return false; 26 | } 27 | return !request.getRequestURI().startsWith("/ff4j-web-console/"); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /generators/app/templates/src/main/java/package/config/ff4j/_JHipsterAuthorizationManager.java: -------------------------------------------------------------------------------- 1 | package <%=packageName%>.config.ff4j; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.stream.Collectors; 6 | 7 | import <%=packageName%>.security.SecurityUtils; 8 | import <%=packageName%>.service.UserService; 9 | 10 | import org.ff4j.security.AbstractAuthorizationManager; 11 | import org.springframework.security.core.Authentication; 12 | import org.springframework.security.core.GrantedAuthority; 13 | import org.springframework.security.core.context.SecurityContext; 14 | import org.springframework.security.core.context.SecurityContextHolder; 15 | import org.springframework.stereotype.Service; 16 | 17 | /** 18 | * Declare an authorization manager based on JHipster security. 19 | * 20 | * @author Clunven (@clunven) 21 | */ 22 | @Service 23 | public class JHipsterAuthorizationManager extends AbstractAuthorizationManager { 24 | 25 | /** Reference to userService. */ 26 | private UserService userService; 27 | 28 | /** 29 | * Injection through constructor. 30 | * 31 | * @param us 32 | * user service. 33 | */ 34 | public JHipsterAuthorizationManager(UserService us) { 35 | this.userService = us; 36 | } 37 | 38 | /** {@inheritDoc} */ 39 | @Override 40 | public String getCurrentUserName() { 41 | return SecurityUtils.getCurrentUserLogin(); 42 | } 43 | 44 | /** {@inheritDoc} */ 45 | @Override 46 | public Set getCurrentUserPermissions() { 47 | SecurityContext securityContext = SecurityContextHolder.getContext(); 48 | Authentication authentication = securityContext.getAuthentication(); 49 | if (authentication != null) { 50 | return authentication.getAuthorities() 51 | .stream() 52 | .map(GrantedAuthority::getAuthority) 53 | .collect(Collectors.toSet()); 54 | } 55 | return new HashSet<>(); 56 | } 57 | 58 | /** {@inheritDoc} */ 59 | @Override 60 | public Set listAllPermissions() { 61 | <%_ if (databaseType === 'sql' || databaseType === 'mongodb') { _%> 62 | return new HashSet<>(userService.getAuthorities());<% } %> 63 | <%_ if (databaseType === 'cassandra') { _%> 64 | return userService.getUserWithAuthorities().getAuthorities();<% } %> 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /generators/app/templates/src/main/java/package/config/ff4j/_JHipsterEhCacheCacheManager.java: -------------------------------------------------------------------------------- 1 | package <%=packageName%>.config.ff4j; 2 | 3 | import org.ehcache.jsr107.EhcacheCachingProvider; 4 | import org.ff4j.cache.FF4jJCacheManager; 5 | 6 | /** 7 | * Inherit HazelcastInstance to work with same caching cluster. 8 | * 9 | * @author clunven (@clunven) 10 | */ 11 | public class JHipsterEhCacheCacheManager extends FF4jJCacheManager { 12 | 13 | /** 14 | * Initialization of HazelCast with default config. 15 | * 16 | * @param hazelCastConfig 17 | */ 18 | public JHipsterEhCacheCacheManager() { 19 | super(EhcacheCachingProvider.class.getName()); 20 | } 21 | 22 | /** {@inheritDoc} */ 23 | @Override 24 | public String getCacheProviderName() { 25 | return "ehcache"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /generators/app/templates/src/main/java/package/config/ff4j/_JHipsterEventRepository.java: -------------------------------------------------------------------------------- 1 | package <%=packageName%>.config.ff4j; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import org.ff4j.audit.Event; 8 | import org.ff4j.audit.EventConstants; 9 | import org.ff4j.audit.EventQueryDefinition; 10 | import org.ff4j.audit.EventSeries; 11 | import org.ff4j.audit.MutableHitCount; 12 | import org.ff4j.audit.chart.TimeSeriesChart; 13 | import org.ff4j.audit.repository.AbstractEventRepository; 14 | import org.ff4j.audit.repository.EventRepository; 15 | import org.springframework.boot.actuate.audit.AuditEvent; 16 | import org.springframework.boot.actuate.audit.AuditEventRepository; 17 | 18 | import <%=packageName%>.security.SecurityUtils; 19 | 20 | /** 21 | * Log FF4j operations into Jhipster auditing mechanism. 22 | * 23 | * @author clunven (@clunven) 24 | */ 25 | public class JHipsterEventRepository extends AbstractEventRepository { 26 | 27 | /** Leveragin on current repository. */ 28 | private final EventRepository eventRepository; 29 | 30 | /** User services of Jhispter to be used in FF4j. */ 31 | private final AuditEventRepository auditRepository; 32 | 33 | /** 34 | * Default constructor. 35 | * 36 | * @param ev 37 | * current envent repository 38 | * @param audi 39 | * current audit service 40 | */ 41 | public JHipsterEventRepository(EventRepository ev, AuditEventRepository audi) { 42 | this.eventRepository = ev; 43 | this.auditRepository = audi; 44 | } 45 | 46 | /** 47 | * Extract information in ff4j audit object to JHispter. 48 | * 49 | * @param e 50 | * target event 51 | * @return 52 | * audit event 53 | */ 54 | public static AuditEvent mapEvent2AuditEvent(Event e) { 55 | String principal = "ANONYMOUS"; 56 | if (e.getUser() != null) { 57 | principal = e.getUser(); 58 | } else if (SecurityUtils.getCurrentUserLogin() != null ) { 59 | principal = SecurityUtils.getCurrentUserLogin(); 60 | } 61 | String type = e.getAction().toUpperCase() + " " + e.getType().toUpperCase() + " " + e.getName(); 62 | Map < String, Object> extraKeys = new HashMap<>(); 63 | extraKeys.put(EventConstants.ATTRIBUTE_HOST, e.getHostName()); 64 | extraKeys.put(EventConstants.ATTRIBUTE_ID, e.getUuid()); 65 | extraKeys.put(EventConstants.ATTRIBUTE_NAME, e.getName()); 66 | extraKeys.put(EventConstants.ATTRIBUTE_DURATION, e.getDuration()); 67 | extraKeys.put(EventConstants.ATTRIBUTE_SOURCE, e.getSource()); 68 | extraKeys.put(EventConstants.ATTRIBUTE_TIME, e.getTimestamp()); 69 | extraKeys.putAll(e.getCustomKeys()); 70 | return new AuditEvent(principal, type, extraKeys); 71 | } 72 | 73 | /** {@inheritDoc} */ 74 | @Override 75 | public boolean saveEvent(Event e) { 76 | auditRepository.add(mapEvent2AuditEvent(e)); 77 | return eventRepository.saveEvent(e); 78 | } 79 | 80 | /** {@inheritDoc} */ 81 | @Override 82 | public Event getEventByUUID(String uuid, Long timestamp) { 83 | return eventRepository.getEventByUUID(uuid, timestamp); 84 | } 85 | 86 | /** {@inheritDoc} */ 87 | @Override 88 | public Map getFeatureUsageHitCount(EventQueryDefinition query) { 89 | return eventRepository.getFeatureUsageHitCount(query); 90 | } 91 | 92 | /** {@inheritDoc} */ 93 | @Override 94 | public TimeSeriesChart getFeatureUsageHistory(EventQueryDefinition query, TimeUnit tu) { 95 | return eventRepository.getFeatureUsageHistory(query, tu); 96 | } 97 | 98 | /** {@inheritDoc} */ 99 | @Override 100 | public EventSeries searchFeatureUsageEvents(EventQueryDefinition query) { 101 | return eventRepository.searchFeatureUsageEvents(query); 102 | } 103 | 104 | /** {@inheritDoc} */ 105 | @Override 106 | public void purgeFeatureUsage(EventQueryDefinition query) { 107 | eventRepository.purgeFeatureUsage(query); 108 | } 109 | 110 | /** {@inheritDoc} */ 111 | @Override 112 | public Map getHostHitCount(EventQueryDefinition query) { 113 | return eventRepository.getHostHitCount(query); 114 | } 115 | 116 | /** {@inheritDoc} */ 117 | @Override 118 | public Map getUserHitCount(EventQueryDefinition query) { 119 | return eventRepository.getUserHitCount(query); 120 | } 121 | 122 | /** {@inheritDoc} */ 123 | @Override 124 | public Map getSourceHitCount(EventQueryDefinition query) { 125 | return eventRepository.getSourceHitCount(query); 126 | } 127 | 128 | /** {@inheritDoc} */ 129 | @Override 130 | public EventSeries getAuditTrail(EventQueryDefinition query) { 131 | return eventRepository.getAuditTrail(query); 132 | } 133 | 134 | /** {@inheritDoc} */ 135 | @Override 136 | public void purgeAuditTrail(EventQueryDefinition query) { 137 | eventRepository.getAuditTrail(query); 138 | } 139 | 140 | /** {@inheritDoc} */ 141 | @Override 142 | public void createSchema() { 143 | eventRepository.createSchema(); 144 | } 145 | 146 | } -------------------------------------------------------------------------------- /generators/app/templates/src/main/java/package/config/ff4j/_JHipsterHazelcastCacheManager.java: -------------------------------------------------------------------------------- 1 | package <%=packageName%>.config.ff4j; 2 | 3 | import javax.cache.spi.CachingProvider; 4 | import com.hazelcast.core.HazelcastInstance; 5 | import com.hazelcast.cache.impl.HazelcastServerCachingProvider; 6 | import org.ff4j.hazelcast.CacheManagerHazelCast; 7 | 8 | /** 9 | * Inherit HazelcastInstance to work with same caching cluster. 10 | * 11 | * @author clunven (@clunven) 12 | */ 13 | public class JHipsterHazelcastCacheManager extends CacheManagerHazelCast { 14 | 15 | /** Instance of HazelCast provided by JHIPSTER. */ 16 | private HazelcastInstance hazelcastInstance; 17 | 18 | /** 19 | * Initialization of HazelCast with default config. 20 | * 21 | * @param hazelCastConfig 22 | */ 23 | public JHipsterHazelcastCacheManager(HazelcastInstance hazelcastInstance) { 24 | super(); 25 | } 26 | 27 | /** {@inheritDoc} */ 28 | @Override 29 | public CachingProvider initCachingProvider(String className) { 30 | setCachingProvider(HazelcastServerCachingProvider.createCachingProvider(hazelcastInstance)); 31 | return getCachingProvider(); 32 | } 33 | 34 | /** {@inheritDoc} */ 35 | @Override 36 | public String getCacheProviderName() { 37 | return "hazelcast"; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /generators/app/templates/src/main/java/package/service/_MySampleFF4jService.java: -------------------------------------------------------------------------------- 1 | package <%=packageName%>.service; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | import javax.annotation.PostConstruct; 7 | 8 | import org.ff4j.FF4j; 9 | import org.ff4j.conf.XmlConfig; 10 | import org.ff4j.conf.XmlParser; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Service; 16 | 17 | 18 | /** 19 | * This sample is provided for you to understand how to use the FF4J Framework 20 | * 21 | * @author Clunven 22 | */ 23 | @Service 24 | public class MySampleFF4jService { 25 | 26 | /** yet another logger. */ 27 | private final Logger log = LoggerFactory.getLogger(MySampleFF4jService.class); 28 | 29 | /** 30 | * The main class to work with FF4j is well, suprisingly ff4j. 31 | * It's a bean and can be injected anywhere. 32 | */ 33 | @Autowired 34 | private FF4j ff4j; 35 | 36 | /** 37 | * Sample method.. 38 | */ 39 | public Double getPrice(double amount) { 40 | double coef = 1; 41 | if (ff4j.check("ttc")) { 42 | if (ff4j.getPropertiesStore().existProperty("tvaRate")) { 43 | coef += (Double) ff4j.getProperty("tvaRate").getValue() / 100; 44 | } 45 | } 46 | return amount * coef; 47 | } 48 | 49 | @PostConstruct 50 | public void initSampleData() { 51 | log.warn("Initialize sample data"); 52 | ff4j.createSchema(); 53 | 54 | try(InputStream xmlFile = getClass().getClassLoader().getResourceAsStream("ff4j.xml")) { 55 | XmlConfig xmlConf = new XmlParser().parseConfigurationFile(xmlFile); 56 | ff4j.getFeatureStore().importFeatures(xmlConf.getFeatures().values()); 57 | ff4j.getPropertiesStore().importProperties(xmlConf.getProperties().values()); 58 | } catch (IOException e) { 59 | log.warn("Cannot initialize DB with sample data", e); 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /generators/app/templates/src/main/java/package/web/rest/_MySampleFF4jResource.java: -------------------------------------------------------------------------------- 1 | package <%=packageName%>.web.rest; 2 | 3 | import <%=packageName%>.service.MySampleFF4jService; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | /** 12 | * REST controller for playing with FF4J. 13 | */ 14 | @RestController 15 | @RequestMapping("/api/sampleff4j") 16 | public class MySampleFF4jResource { 17 | 18 | @Autowired 19 | private MySampleFF4jService sampleService; 20 | 21 | @GetMapping("/price") 22 | public Double getPrice(@RequestParam(value = "amount") Double amount) { 23 | return sampleService.getPrice(amount); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /generators/app/templates/src/main/resources/config/liquibase/changelog/_ff4jTables.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Create table for ff4j from generator jhispter 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Create table for ff4j from generator jhispter 42 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Create table for ff4j from generator jhispter 56 | 57 | 58 | 59 | 60 | 61 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Create table for ff4j from generator jhispter 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Create table for ff4j from generator jhispter 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /generators/app/templates/src/main/resources/ff4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 0 45 | 1 46 | 2 47 | 3 48 | 49 | 50 | 51 | 52 | AMER 53 | SSSS 54 | EAST 55 | EAST 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const bumper = require('gulp-bump'); 3 | const eslint = require('gulp-eslint'); 4 | const git = require('gulp-git'); 5 | const shell = require('gulp-shell'); 6 | const fs = require('fs'); 7 | const sequence = require('gulp-sequence'); 8 | const path = require('path'); 9 | const mocha = require('gulp-mocha'); 10 | const istanbul = require('gulp-istanbul'); 11 | const nsp = require('gulp-nsp'); 12 | const plumber = require('gulp-plumber'); 13 | 14 | gulp.task('eslint', () => gulp.src(['gulpfile.js', 'generators/app/index.js', 'test/*.js']) 15 | // .pipe(plumber({errorHandler: handleErrors})) 16 | .pipe(eslint()) 17 | .pipe(eslint.format()) 18 | .pipe(eslint.failOnError()) 19 | ); 20 | 21 | gulp.task('nsp', (cb) => { 22 | nsp({ package: path.resolve('package.json') }, cb); 23 | }); 24 | 25 | gulp.task('pre-test', () => gulp.src('generators/app/index.js') 26 | .pipe(istanbul({ 27 | includeUntested: true 28 | })) 29 | .pipe(istanbul.hookRequire()) 30 | ); 31 | 32 | gulp.task('test', ['pre-test'], (cb) => { 33 | let mochaErr; 34 | 35 | gulp.src('test/*.js') 36 | .pipe(plumber()) 37 | .pipe(mocha({ reporter: 'spec' })) 38 | .on('error', (err) => { 39 | mochaErr = err; 40 | }) 41 | .pipe(istanbul.writeReports()) 42 | .on('end', () => { 43 | cb(mochaErr); 44 | }); 45 | }); 46 | 47 | gulp.task('bump-patch', bump('patch')); 48 | gulp.task('bump-minor', bump('minor')); 49 | gulp.task('bump-major', bump('major')); 50 | 51 | gulp.task('git-commit', () => { 52 | const v = `update to version ${version()}`; 53 | gulp.src(['./generators/**/*', './README.md', './package.json', './gulpfile.js', './.travis.yml', './travis/**/*']) 54 | .pipe(git.add()) 55 | .pipe(git.commit(v)); 56 | }); 57 | 58 | gulp.task('git-push', (cb) => { 59 | const v = version(); 60 | git.push('origin', 'master', (err) => { 61 | if (err) return cb(err); 62 | git.tag(v, v, (err) => { 63 | if (err) return cb(err); 64 | git.push('origin', 'master', { 65 | args: '--tags' 66 | }, cb); 67 | return true; 68 | }); 69 | return true; 70 | }); 71 | }); 72 | 73 | gulp.task('npm', shell.task([ 74 | 'npm publish' 75 | ])); 76 | 77 | function bump(level) { 78 | return function () { 79 | return gulp.src(['./package.json']) 80 | .pipe(bumper({ 81 | type: level 82 | })) 83 | .pipe(gulp.dest('./')); 84 | }; 85 | } 86 | 87 | function version() { 88 | return JSON.parse(fs.readFileSync('package.json', 'utf8')).version; 89 | } 90 | 91 | gulp.task('prepublish', ['nsp']); 92 | gulp.task('default', ['static', 'test']); 93 | gulp.task('deploy-patch', sequence('test', 'bump-patch', 'git-commit', 'git-push', 'npm')); 94 | gulp.task('deploy-minor', sequence('test', 'bump-minor', 'git-commit', 'git-push', 'npm')); 95 | gulp.task('deploy-major', sequence('test', 'bump-major', 'git-commit', 'git-push', 'npm')); 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-jhipster-ff4j", 3 | "version": "1.6.7", 4 | "description": "A Jhipster module to integrate FeatureToggle capability in your application using FF4j (Feature Flipping for Java)", 5 | "keywords": [ 6 | "yeoman-generator", 7 | "jhipster-module", 8 | "ff4j", 9 | "Feature Toggle" 10 | ], 11 | "homepage": "https://github.com/clun/generator-jhipster-ff4j", 12 | "author": { 13 | "name": "Cedrick Lunven", 14 | "email": "cedrick.lunven@gmail.com", 15 | "url": "ff4j.org" 16 | }, 17 | "files": [ 18 | "generators" 19 | ], 20 | "main": "generators/app/index.js", 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/clun/generator-jhipster-ff4j.git" 24 | }, 25 | "dependencies": { 26 | "yeoman-generator": "1.1.1", 27 | "chalk": "1.1.3", 28 | "mkdirp": "0.5.1", 29 | "generator-jhipster": ">=4.1.0" 30 | }, 31 | "devDependencies": { 32 | "eslint": "3.17.1", 33 | "eslint-config-airbnb-base": "11.1.1", 34 | "eslint-plugin-import": "2.2.0", 35 | "fs-extra": "2.1.2", 36 | "gulp": "3.9.1", 37 | "gulp-bump": "2.7.0", 38 | "gulp-eslint": "3.0.1", 39 | "gulp-exclude-gitignore": "1.1.1", 40 | "gulp-git": "2.1.0", 41 | "gulp-istanbul": "1.1.1", 42 | "gulp-mocha": "3.0.1", 43 | "gulp-nsp": "2.4.2", 44 | "gulp-plumber": "1.1.0", 45 | "gulp-rename": "1.2.2", 46 | "gulp-sequence": "0.4.6", 47 | "gulp-shell": "0.6.3", 48 | "mocha": "3.2.0", 49 | "yeoman-assert": "3.0.0", 50 | "yeoman-test": "1.6.0" 51 | }, 52 | "scripts": { 53 | "test": "mocha test/*" 54 | }, 55 | "license": "Apache-2.0", 56 | "bugs": { 57 | "url": "https://github.com/clun/generator-jhipster-ff4j/issues" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/jhipster-jdl.jh: -------------------------------------------------------------------------------- 1 | 2 | // --- Features --- 3 | 4 | entity FF4jFeature { 5 | uid String required, 6 | description String, 7 | enable Boolean, 8 | group String, 9 | } 10 | 11 | // --- Properties --- 12 | 13 | entity FF4jProperty { 14 | uid String required, 15 | description String, 16 | className String required, 17 | currentValue String required 18 | } 19 | 20 | // --- Audit --- 21 | 22 | enum EventSource { JAVAAPI, WEB, CLI, JMX } 23 | enum EventType { FEATURES, PROPERTIES, ADMINISTRATION } 24 | enum EventAction { TOGGLE_ON, TOGGLE_OFF, CREATE, DELETE, UPDATE, HIT } 25 | 26 | entity FF4jEvent { 27 | uid String required, 28 | time ZonedDateTime required, 29 | duration Integer, 30 | name String, 31 | value String, 32 | type EventType, 33 | source EventSource, 34 | action EventAction, 35 | hostname String, 36 | user String 37 | } 38 | 39 | // Set pagination options 40 | paginate FF4jFeature, FF4jProperty with infinite-scroll 41 | paginate FF4jEvent with pagination 42 | 43 | dto * with mapstruct 44 | 45 | // Set service options to all except few 46 | service all with serviceImpl except Employee, Job 47 | 48 | // Set an angular suffix 49 | angularSuffix * with mySuffix 50 | -------------------------------------------------------------------------------- /test/templates/default/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-jhipster": { 3 | "baseName": "sampleMysql", 4 | "packageName": "com.mycompany.myapp", 5 | "packageFolder": "com/mycompany/myapp", 6 | "authenticationType": "session", 7 | "hibernateCache": "ehcache", 8 | "clusteredHttpSession": "no", 9 | "websocket": "no", 10 | "databaseType": "sql", 11 | "devDatabaseType": "h2Disk", 12 | "prodDatabaseType": "mysql", 13 | "searchEngine": "no", 14 | "useSass": false, 15 | "buildTool": "maven", 16 | "frontendBuilder": "grunt", 17 | "enableTranslation": true, 18 | "enableSocialSignIn": false, 19 | "rememberMeKey": "2bb60a80889aa6e6767e9ccd8714982681152aa5", 20 | "testFrameworks": [ 21 | "gatling" 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/test-app.js: -------------------------------------------------------------------------------- 1 | /* global describe, beforeEach, it*/ 2 | 3 | const path = require('path'); 4 | const fse = require('fs-extra'); 5 | const assert = require('yeoman-assert'); 6 | const helpers = require('yeoman-test'); 7 | 8 | const deps = [ 9 | [helpers.createDummyGenerator(), 'jhipster:modules'] 10 | ]; 11 | 12 | describe('JHipster generator ff4j', () => { 13 | describe('simple test', () => { 14 | beforeEach((done) => { 15 | helpers 16 | .run(path.join(__dirname, '../generators/app')) 17 | .inTmpDir((dir) => { 18 | fse.copySync(path.join(__dirname, '../test/templates/default'), dir); 19 | }) 20 | .withOptions({ 21 | testmode: true 22 | }) 23 | .withPrompts({ 24 | message: 'simple message to say hello' 25 | }) 26 | .withGenerators(deps) 27 | .on('end', done); 28 | }); 29 | 30 | it('generate dummy.txt file', () => { 31 | assert.file([ 32 | 'dummy.txt' 33 | ]); 34 | }); 35 | }); 36 | }); 37 | --------------------------------------------------------------------------------