├── .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 |
--------------------------------------------------------------------------------