Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redis cache support #10057

Merged
merged 12 commits into from Sep 22, 2019
21 changes: 21 additions & 0 deletions generators/common/templates/README.md.ejs
Expand Up @@ -66,6 +66,27 @@ You will only need to run this command when dependencies change in [package.json

We use <%= clientPackageManager %> scripts and [Webpack][] as our build system.

<%_ if(['hazelcast', 'memcached', 'redis'].includes(cacheProvider)) { _%>
If you are using <%= cacheProvider %> as a cache, you will have to launch a cache server.
To start your cache server, run:
```
docker-compose -f src/main/docker/<%= cacheProvider==='hazelcast'?'hazelcast-management-center':cacheProvider%>.yml up -d
```
<%_ if (cacheProvider === 'redis') { _%>

The cache can also be disabled by adding to the application yaml:
```
spring:
cache:
type: none
```
See [here](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html#boot-features-caching-provider-none) for details.

**WARNING**: If you using second level hibernate cache and disabling the spring cache, you have to disable the second level hibernate cache as well since they are using
the same CacheManager.
<%_ } _%>
<%_ } _%>

Run the following commands in two separate terminals to create a blissful development experience where your browser
auto-refreshes when files change on your hard drive.
<% if (buildTool === 'maven') { %>
Expand Down
2 changes: 1 addition & 1 deletion generators/entity-server/files.js
Expand Up @@ -268,7 +268,7 @@ function writeFiles() {
}
this.addChangelogToLiquibase(`${this.changelogDate}_added_entity_${this.entityClass}`);

if (['ehcache', 'infinispan'].includes(this.cacheProvider) && this.enableHibernateCache) {
if (['ehcache', 'infinispan', 'redis'].includes(this.cacheProvider) && this.enableHibernateCache) {
this.addEntityToCache(
this.asEntity(this.entityClass),
this.relationships,
Expand Down
1 change: 1 addition & 0 deletions generators/generator-base-docker.js
Expand Up @@ -81,6 +81,7 @@ module.exports = class extends BaseGenerator {
this.DOCKER_MONGODB = constants.DOCKER_MONGODB;
this.DOCKER_COUCHBASE = constants.DOCKER_COUCHBASE;
this.DOCKER_MEMCACHED = constants.DOCKER_MEMCACHED;
this.DOCKER_REDIS = constants.DOCKER_REDIS;
this.DOCKER_ELASTICSEARCH = constants.DOCKER_ELASTICSEARCH;
this.DOCKER_PROMETHEUS_OPERATOR = constants.DOCKER_PROMETHEUS_OPERATOR;
this.DOCKER_GRAFANA_WATCHER = constants.DOCKER_GRAFANA_WATCHER;
Expand Down
2 changes: 2 additions & 0 deletions generators/generator-constants.js
Expand Up @@ -29,6 +29,7 @@ const DOCKER_CASSANDRA = 'cassandra:3.11.4';
const DOCKER_MSSQL = 'microsoft/mssql-server-linux:latest';
const DOCKER_HAZELCAST_MANAGEMENT_CENTER = 'hazelcast/management-center:3.12.1';
const DOCKER_MEMCACHED = 'memcached:1.5.16-alpine';
const DOCKER_REDIS = 'redis:5.0.5';
const DOCKER_KEYCLOAK = 'jboss/keycloak:6.0.1';
const DOCKER_ELASTICSEARCH = 'docker.elastic.co/elasticsearch/elasticsearch:6.4.3'; // The version should be coerent with the one from spring-data-elasticsearch project
const DOCKER_KAFKA = 'confluentinc/cp-kafka:5.2.2';
Expand Down Expand Up @@ -277,6 +278,7 @@ const constants = {
DOCKER_MSSQL,
DOCKER_HAZELCAST_MANAGEMENT_CENTER,
DOCKER_MEMCACHED,
DOCKER_REDIS,
DOCKER_ELASTICSEARCH,
DOCKER_KEYCLOAK,
DOCKER_KAFKA,
Expand Down
7 changes: 6 additions & 1 deletion generators/server/files.js
Expand Up @@ -105,6 +105,11 @@ const serverFiles = {
path: DOCKER_DIR,
templates: ['memcached.yml']
},
{
condition: generator => generator.cacheProvider === 'redis',
path: DOCKER_DIR,
templates: ['redis.yml']
},
{
condition: generator => generator.searchEngine === 'elasticsearch',
path: DOCKER_DIR,
Expand Down Expand Up @@ -831,7 +836,7 @@ const serverFiles = {
},
{
condition: generator =>
['ehcache', 'hazelcast', 'infinispan', 'memcached'].includes(generator.cacheProvider) ||
['ehcache', 'hazelcast', 'infinispan', 'memcached', 'redis'].includes(generator.cacheProvider) ||
generator.applicationType === 'gateway',
path: SERVER_MAIN_SRC_DIR,
templates: [
Expand Down
3 changes: 2 additions & 1 deletion generators/server/index.js
Expand Up @@ -100,6 +100,7 @@ module.exports = class extends BaseBlueprintGenerator {
this.DOCKER_MSSQL = constants.DOCKER_MSSQL;
this.DOCKER_HAZELCAST_MANAGEMENT_CENTER = constants.DOCKER_HAZELCAST_MANAGEMENT_CENTER;
this.DOCKER_MEMCACHED = constants.DOCKER_MEMCACHED;
this.DOCKER_REDIS = constants.DOCKER_REDIS;
this.DOCKER_CASSANDRA = constants.DOCKER_CASSANDRA;
this.DOCKER_ELASTICSEARCH = constants.DOCKER_ELASTICSEARCH;
this.DOCKER_KEYCLOAK = constants.DOCKER_KEYCLOAK;
Expand Down Expand Up @@ -366,7 +367,7 @@ module.exports = class extends BaseBlueprintGenerator {
this.lowercaseBaseName = this.baseName.toLowerCase();
this.humanizedBaseName = _.startCase(this.baseName);
this.mainClass = this.getMainClassName();
this.cacheManagerIsAvailable = ['ehcache', 'hazelcast', 'infinispan', 'memcached'].includes(this.cacheProvider);
this.cacheManagerIsAvailable = ['ehcache', 'hazelcast', 'infinispan', 'memcached', 'redis'].includes(this.cacheProvider);
this.pkType = this.getPkType(this.databaseType);

this.packageFolder = this.packageName.replace(/\./g, '/');
Expand Down
5 changes: 5 additions & 0 deletions generators/server/needle-api/needle-server-cache.js
Expand Up @@ -53,6 +53,11 @@ module.exports = class extends needleServer {
cacheManager.getCache(${entry}).getAdvancedCache(), this,
ConfigurationAdapter.create()));`;

this._doAddBlockContentToFile(cachePath, needle, content, errorMessage);
} else if (cacheProvider === 'redis') {
const needle = 'jhipster-needle-redis-add-entry';
const content = `createCache(cm, ${entry});`;

this._doAddBlockContentToFile(cachePath, needle, content, errorMessage);
}
}
Expand Down
4 changes: 4 additions & 0 deletions generators/server/prompts.js
Expand Up @@ -240,6 +240,10 @@ function askForServerSideOpts(meta) {
name:
'Yes, with Memcached (distributed cache) - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!'
},
{
value: 'redis',
name: 'Yes, with the Redis implementation (single server)'
},
{
value: 'no',
name: 'No - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!'
Expand Down
8 changes: 7 additions & 1 deletion generators/server/templates/build.gradle.ejs
Expand Up @@ -309,7 +309,13 @@ dependencies {
implementation "com.google.code.simple-spring-memcached:xmemcached-provider"
implementation "com.googlecode.xmemcached:xmemcached"
<%_ } _%>
<%_ if (['ehcache', 'hazelcast', 'infinispan'].includes(cacheProvider) || applicationType === 'gateway') { _%>
<%_ if (cacheProvider === 'redis') { _%>
implementation "org.redisson:redisson"
<%_ if (enableHibernateCache) { _%>
implementation "org.hibernate:hibernate-jcache"
<%_ } _%>
<%_ } _%>
<%_ if (['ehcache', 'hazelcast', 'infinispan', 'redis'].includes(cacheProvider) || applicationType === 'gateway') { _%>
implementation "javax.cache:cache-api"
<%_ } _%>
<%_ if (databaseType === 'sql') { _%>
Expand Down
12 changes: 12 additions & 0 deletions generators/server/templates/pom.xml.ejs
Expand Up @@ -281,6 +281,18 @@
<scope>test</scope>
<!-- parent POM declares this dependency in default (compile) scope -->
</dependency>
<%_ if (cacheProvider === 'redis' && enableHibernateCache) { _%>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jcache</artifactId>
</dependency>
<%_ } _%>
<%_ if (cacheProvider === 'redis') { _%>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
<%_ } _%>
<%_ if (!reactive) { _%>
<dependency>
<groupId>io.springfox</groupId>
Expand Down
9 changes: 9 additions & 0 deletions generators/server/templates/src/main/docker/app.yml.ejs
Expand Up @@ -58,6 +58,9 @@ services:
<%_ if (cacheProvider === 'memcached') { _%>
- JHIPSTER_CACHE_MEMCACHED_SERVERS=<%= baseName.toLowerCase() %>-memcached:11211
<%_ } _%>
<%_ if (cacheProvider === 'redis') { _%>
- JHIPSTER_CACHE_REDIS_SERVER=redis://<%= baseName.toLowerCase() %>-redis:6379
<%_ } _%>
<%_ if (authenticationType === 'oauth2') { _%>
- SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER_URI=http://keycloak:9080/auth/realms/jhipster
<%_ if (applicationType === 'gateway' || applicationType === 'monolith') { _%>
Expand Down Expand Up @@ -149,6 +152,12 @@ services:
file: memcached.yml
service: <%= baseName.toLowerCase() %>-memcached
<%_ } _%>
<%_ if (cacheProvider === 'redis') { _%>
<%= baseName.toLowerCase() %>-redis:
extends:
file: redis.yml
service: <%= baseName.toLowerCase() %>-redis
<%_ } _%>
<%_ if (searchEngine === 'elasticsearch') { _%>
<%= baseName.toLowerCase() %>-elasticsearch:
extends:
Expand Down
20 changes: 20 additions & 0 deletions generators/server/templates/src/main/docker/redis.yml.ejs
@@ -0,0 +1,20 @@
<%#
Copyright 2013-2019 the original author or authors from the JHipster project.
This file is part of the JHipster project, see https://www.jhipster.tech/
for more information.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-%>
version: '2'
services:
<%= baseName.toLowerCase() %>-redis:
image: <%= DOCKER_REDIS %>
ports:
- "6379:6379"
Expand Up @@ -132,6 +132,26 @@ import com.google.code.ssm.providers.xmemcached.MemcacheClientFactoryImpl;
import com.google.code.ssm.spring.SSMCache;
import com.google.code.ssm.spring.SSMCacheManager;

import io.github.jhipster.config.JHipsterProperties;
<%_ } _%>
<%_ if (cacheProvider === 'redis') { _%>
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.redisson.jcache.configuration.RedissonConfiguration;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
<%_ if (enableHibernateCache) { _%>
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.hibernate.cache.jcache.ConfigSettings;
<%_ } _%>

import java.util.concurrent.TimeUnit;

import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;

import io.github.jhipster.config.JHipsterProperties;
<%_ } _%>

Expand Down Expand Up @@ -706,4 +726,53 @@ public class CacheConfiguration<% if (cacheProvider === 'hazelcast') { %> implem
return defaultCache;
}
<%_ } _%>

<%_ if (cacheProvider === 'redis') { _%>
private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;

public CacheConfiguration(JHipsterProperties jHipsterProperties) {
MutableConfiguration<Object, Object> jcacheConfig = new MutableConfiguration<>();
Config config = new Config();
config.useSingleServer().setAddress(jHipsterProperties.getCache().getRedis().getServer());
jcacheConfig.setStatisticsEnabled(true);
jcacheConfig.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, jHipsterProperties.getCache().getRedis().getExpiration())));
jcacheConfiguration = RedissonConfiguration.fromInstance(Redisson.create(config), jcacheConfig);
}


<%_ if (enableHibernateCache) { _%>
@Bean
public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(javax.cache.CacheManager cm) {
return hibernateProperties -> hibernateProperties.put(ConfigSettings.CACHE_MANAGER, cm);
}
<%_ } _%>

@Bean
public JCacheManagerCustomizer cacheManagerCustomizer() {
return cm -> {
<%_ if (!skipUserManagement || (authenticationType === 'oauth2' && databaseType !== 'no')) { _%>
createCache(cm, <%=packageName%>.repository<% if (reactive) { %>.reactive<% } %>.UserRepository.USERS_BY_LOGIN_CACHE);
createCache(cm, <%=packageName%>.repository<% if (reactive) { %>.reactive<% } %>.UserRepository.USERS_BY_EMAIL_CACHE);
<%_ if (enableHibernateCache) { _%>
createCache(cm, <%=packageName%>.domain.<%= asEntity('User') %>.class.getName());
createCache(cm, <%=packageName%>.domain.Authority.class.getName());
createCache(cm, <%=packageName%>.domain.<%= asEntity('User') %>.class.getName() + ".authorities");
<%_ if (authenticationType === 'session') { _%>
createCache(cm, <%=packageName%>.domain.PersistentToken.class.getName());
createCache(cm, <%=packageName%>.domain.<%= asEntity('User') %>.class.getName() + ".persistentTokens");
<%_ } _%>
<%_ } _%>
<%_ } _%>
// jhipster-needle-redis-add-entry
};
}

private void createCache(javax.cache.CacheManager cm, String cacheName) {
javax.cache.Cache<Object, Object> cache = cm.getCache(cacheName);
if (cache != null) {
cm.destroyCache(cacheName);
}
cm.createCache(cacheName, jcacheConfiguration);
}
<%_ } _%>
}
Expand Up @@ -303,6 +303,10 @@ jhipster:
# If you want to use Memcached, you must also enable it in CacheConfiguration
enabled: false
<%_ } _%>
<%_ if (cacheProvider === 'redis') { _%>
redis: # Redis configuration
expiration: 3600 # By default objects stay 1 hour (in seconds) in the cache
<%_ } _%>
<%_ } _%>
<%_ if (applicationType !== 'microservice') { _%>
# CORS is only enabled by default with the "dev" profile, so BrowserSync can access the API
Expand Down
Expand Up @@ -299,6 +299,11 @@ jhipster:
expiration: 300
use-binary-protocol: true
<%_ } _%>
<%_ if (cacheProvider === 'redis') { _%>
redis: # Redis configuration
expiration: 3600 # By default objects stay 1 hour (in seconds) in the cache
server: redis://localhost:6379
<%_ } _%>
<%_ } _%>
<%_ if (authenticationType === 'jwt') { _%>
security:
Expand Down
Expand Up @@ -65,6 +65,9 @@
<logger name="com.google.code.ssm" level="WARN"/>
<logger name="com.google.code.yanf4j" level="WARN"/>
<%_ } _%>
<%_ if (cacheProvider === 'redis') { _%>
<logger name="org.redisson" level="WARN" />
<%_ } _%>
<%_ if (applicationType === 'microservice' || applicationType === 'gateway' || serviceDiscoveryType === 'eureka') { _%>
<logger name="com.netflix" level="WARN"/>
<logger name="com.netflix.discovery" level="INFO"/>
Expand Down
37 changes: 37 additions & 0 deletions test/app.spec.js
Expand Up @@ -820,6 +820,43 @@ describe('JHipster generator', () => {
});
});

describe('Redis', () => {
before(done => {
helpers
.run(path.join(__dirname, '../generators/app'))
.withOptions({ 'from-cli': true, skipInstall: true, skipChecks: true })
.withPrompts({
baseName: 'jhipster',
packageName: 'com.mycompany.myapp',
packageFolder: 'com/mycompany/myapp',
clientFramework: 'angularX',
serviceDiscoveryType: false,
authenticationType: 'jwt',
cacheProvider: 'redis',
enableHibernateCache: true,
databaseType: 'sql',
devDatabaseType: 'h2Memory',
prodDatabaseType: 'mysql',
enableTranslation: true,
nativeLanguage: 'en',
languages: ['fr'],
buildTool: 'maven',
rememberMeKey: '5c37379956bd1242f5636c8cb322c2966ad81277',
serverSideOptions: []
})
.on('end', done);
});
it('creates expected files with "Redis"', () => {
assert.file(expectedFiles.common);
assert.file(expectedFiles.server);
assert.file(expectedFiles.userManagementServer);
assert.file(expectedFiles.client);
assert.file(expectedFiles.redis);
assert.file(expectedFiles.mysql);
assert.file(expectedFiles.hibernateTimeZoneConfig);
});
});

describe('Messaging with Kafka configuration', () => {
before(done => {
helpers
Expand Down
2 changes: 2 additions & 0 deletions test/utils/expected-files.js
Expand Up @@ -245,6 +245,8 @@ const expectedFiles = {

memcached: [`${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, `${DOCKER_DIR}memcached.yml`],

redis: [`${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/config/CacheConfiguration.java`, `${DOCKER_DIR}redis.yml`],

gatling: [`${TEST_DIR}gatling/conf/gatling.conf`],

i18nJson: [
Expand Down