Skip to content

Commit

Permalink
Add caffeine cache implementation (#117)
Browse files Browse the repository at this point in the history
* Add Quarkus cache dependency

* Add cache UserService.java

* Follow JHipster cache implementation for User entity

* Enable Hibernate second cache level for user

* Enable Hibernate second level cache for Authority

* Improve code style

* Provide Quarkus cache each time we choose a cache

* Add cache dependency in Gradle

* Override cacheManagerIsAvailable property with Quarkus caches

* Make integration-tests more consistent

* Enable User methods caching when enableHibernateCache

* Add gradle tests

* Remove unused file

* Add Hibernate cache support in Entity (with ReadOnly option too)

* Add application.properties for tests purpose

* Move generation context into api and builder class to avoid duplicate code

* Move server test generation context into api builder

* Move client test generation context into api builder

* Move common test generation context into api builder

* Add default cache properties for User and Authority entities

* Add needle api for hibernate second cache entity and relationships

* Remove hints feature

* add cache default properties
  • Loading branch information
avdev4j committed Dec 2, 2020
1 parent bcbe7f9 commit 54195e4
Show file tree
Hide file tree
Showing 33 changed files with 856 additions and 468 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/smoke-test-imperative.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ jobs:
node_version: [12.16.2]
os: [ubuntu-latest]
app:
- imperative-sql-jwt-maven-sample
- imperative-sql-jwt-maven-sqllight
- imperative-ngx-jwt-maven-mysql-caffeine
- imperative-ngx-jwt-maven-psql-nocache
- imperative-react-jwt-gradle-mysql-caffeine
include:
- app: imperative-sql-jwt-maven-sample
- app: imperative-sql-jwt-maven-sqllight
- app: imperative-ngx-jwt-maven-mysql-caffeine
- app: imperative-ngx-jwt-maven-psql-nocache
- app: imperative-react-jwt-gradle-mysql-caffeine
env:
JHI_APP: ${{ matrix.app }}
steps:
Expand Down
20 changes: 11 additions & 9 deletions generators/entity-server/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const constants = require('generator-jhipster/generators/generator-constants');
const faker = require('faker');
const jhipsterUtils = require('generator-jhipster/generators/utils');

const NeedleApi = require('../needle-api');

const randexp = jhipsterUtils.RandexpWithFaker;
/* Constants use throughout */
const SERVER_MAIN_SRC_DIR = constants.SERVER_MAIN_SRC_DIR;
Expand Down Expand Up @@ -351,16 +353,16 @@ function writeFiles() {
}
this.addChangelogToLiquibase(`${this.changelogDate}_added_entity_${this.entityClass}`);
}
}
},

if (['ehcache', 'caffeine', 'infinispan', 'redis'].includes(this.cacheProvider) && this.enableHibernateCache) {
this.addEntityToCache(
this.asEntity(this.entityClass),
this.relationships,
this.packageName,
this.packageFolder,
this.cacheProvider
);
}
updateCacheConfiguration() {
if (this.enableHibernateCache) {
new NeedleApi(this).quarkusServerCache.addEntityConfigurationToPropertiesFile(
this.asEntity(this.entityClass),
this.relationships,
this.packageName
);
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
<%_ } if (!hasDto && importApiModelProperty === true) { _%>
import io.swagger.annotations.ApiModelProperty;
<%_ } if (readOnly) { _%>
<%_ } if (enableHibernateCache) { _%>
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
<%_ } if (hasTextBlob === true || (fieldsContainUUID === true && ['mysql', 'mariadb'].includes(prodDatabaseType))) { _%>
Expand Down Expand Up @@ -149,9 +149,9 @@ import static org.springframework.data.couchbase.core.mapping.id.GenerationStrat
<%_ if (databaseType === 'sql') { _%>
@Entity
@Table(name = "<%= entityTableName %>")
<%_ if (readOnly) { _%>
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
<%_ } _%>
<%_ if (enableHibernateCache) { _%>
<% if (readOnly) { %>@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)<% } else { %>@Cacheable<% } %>
<%_ } _%>
<%_ } _%>
<%_ if (databaseType === 'mongodb') { _%>
@Document(collection = "<%= entityTableName %>")
Expand Down Expand Up @@ -288,8 +288,8 @@ public class <%= asEntity(entityClass) %> extends PanacheEntityBase implements S
if (databaseType === 'sql') {
_%>
@OneToMany(mappedBy = "<%= otherEntityRelationshipName %>")
<%_ if (readOnly) { _%>
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
<%_ if (enableHibernateCache) { _%>
@Cache(usage = CacheConcurrencyStrategy.<% if (readOnly) { %>READ_ONLY<% } else { %>READ_WRITE<% } %>)
<%_ } _%>
<%_ } else if (databaseType === 'mongodb' || databaseType === 'couchbase') {
if (databaseType === 'mongodb') { _%>
Expand Down Expand Up @@ -333,9 +333,9 @@ public class <%= asEntity(entityClass) %> extends PanacheEntityBase implements S
<%_ } else if (relationshipType === 'many-to-many') {
if (databaseType === 'sql') { _%>
@ManyToMany<% if (ownerSide === false) { %>(mappedBy = "<%= otherEntityRelationshipNamePlural %>")<% } %>
<%_ if (readOnly) { _%>
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
<%_ } _%>
<%_ if (enableHibernateCache) { _%>
@Cache(usage = CacheConcurrencyStrategy.<% if (readOnly) { %>READ_ONLY<% } else { %>READ_WRITE<% } %>)
<%_ } _%>
<%_ if (ownerSide === true) {
if (relationshipValidate) { _%>
<%- include(fetchFromInstalledJHipster('entity-server/templates') + '/src/main/java/package/domain/relationship_validators', {
Expand Down
7 changes: 6 additions & 1 deletion generators/generator-quarkus-constants.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
const DEFAULT_DATA_ACCESS = 'activeRecord';
const QUARKUS_VERSION = '1.10.2.Final';

const CACHE_MAXIMUM_SIZE = 100;
const CACHE_EXPIRE_AFTER_WRITE = '3600S';

const constants = {
DEFAULT_DATA_ACCESS,
QUARKUS_VERSION
QUARKUS_VERSION,
CACHE_MAXIMUM_SIZE,
CACHE_EXPIRE_AFTER_WRITE
};

module.exports = constants;
25 changes: 25 additions & 0 deletions generators/needle-api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright 2013-2020 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.
*/
const ServerCache = require('./server/needle-api/needle-server-cache');

module.exports = class NeedleApi {
constructor(generator) {
this.quarkusServerCache = new ServerCache(generator);
}
};
10 changes: 5 additions & 5 deletions generators/server/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@ const jhipsterFiles = require('generator-jhipster/generators/server/files').serv
/* Constants use throughout */
const INTERPOLATE_REGEX = constants.INTERPOLATE_REGEX;
const DOCKER_DIR = constants.DOCKER_DIR;
// const TEST_DIR = constants.TEST_DIR;
const SERVER_MAIN_SRC_DIR = constants.SERVER_MAIN_SRC_DIR;
const SERVER_MAIN_RES_DIR = constants.SERVER_MAIN_RES_DIR;
const SERVER_TEST_SRC_DIR = constants.SERVER_TEST_SRC_DIR;
const SERVER_TEST_RES_DIR = constants.SERVER_TEST_RES_DIR;
const { SERVER_MAIN_SRC_DIR, SERVER_MAIN_RES_DIR, SERVER_TEST_SRC_DIR, SERVER_TEST_RES_DIR } = constants;

const serverFiles = {
serverBuild: [
Expand Down Expand Up @@ -76,6 +72,10 @@ const serverFiles = {
'application.properties',
'resources-config.json'
]
},
{
path: SERVER_TEST_RES_DIR,
templates: ['application.properties']
}
],
serverTestSupport: [
Expand Down
17 changes: 13 additions & 4 deletions generators/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const os = require('os');
const ServerGenerator = require('generator-jhipster/generators/server');
const prompts = require('./prompts');
const writeFiles = require('./files').writeFiles;
const quarkusVersion = require('../generator-quarkus-constants').QUARKUS_VERSION;
const { QUARKUS_VERSION, CACHE_MAXIMUM_SIZE, CACHE_EXPIRE_AFTER_WRITE } = require('../generator-quarkus-constants');

module.exports = class extends ServerGenerator {
constructor(args, opts) {
Expand All @@ -26,7 +26,9 @@ module.exports = class extends ServerGenerator {
const phaseFromJHipster = super._initializing();
const phaseFromQuarkus = {
defineQuarkusConstants() {
this.quarkusVersion = quarkusVersion;
this.quarkusVersion = QUARKUS_VERSION;
this.CACHE_MAXIMUM_SIZE = CACHE_MAXIMUM_SIZE;
this.CACHE_EXPIRE_AFTER_WRITE = CACHE_EXPIRE_AFTER_WRITE;
}
};
return { ...phaseFromJHipster, ...phaseFromQuarkus };
Expand All @@ -42,8 +44,15 @@ module.exports = class extends ServerGenerator {
}

get configuring() {
// Here we are not overriding this phase and hence its being handled by JHipster
return super._configuring();
const phaseFromJHipster = super._configuring();
const phaseFromQuarkus = {
configureGlobalQuarkus() {
// Override JHipster cacheManagerIsAvailable property to only handle Quarkus caches
this.cacheManagerIsAvailable = ['caffeine'].includes(this.cacheProvider);
}
};

return { ...phaseFromJHipster, ...phaseFromQuarkus };
}

get default() {
Expand Down
40 changes: 40 additions & 0 deletions generators/server/needle-api/needle-server-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const chalk = require('chalk');
const needleServerCache = require('generator-jhipster/generators/server/needle-api/needle-server-cache');
const constants = require('generator-jhipster/generators/generator-constants');
const { CACHE_MAXIMUM_SIZE, CACHE_EXPIRE_AFTER_WRITE } = require('../../generator-quarkus-constants');

const { SERVER_MAIN_RES_DIR } = constants;

module.exports = class extends needleServerCache {
addEntityConfigurationToPropertiesFile(entityClass, relationships, packageName) {
const errorMessage = chalk.yellow(`\nUnable to add ${entityClass} to application.properties file.`);
const cacheName = `${packageName}.domain.${entityClass}`;
const applicationPropertiesFileName = `${SERVER_MAIN_RES_DIR}application.properties`;
const needle = 'jhipster-quarkus-needle-hibernate-cache-add-entry';
const rewriteFileModel = this.generateFileModel(
applicationPropertiesFileName,
needle,
`quarkus.hibernate-orm.cache."${cacheName}".expiration.max-idle=${CACHE_EXPIRE_AFTER_WRITE}\n` +
`quarkus.hibernate-orm.cache."${cacheName}".memory.object-count=${CACHE_MAXIMUM_SIZE}`
);

this.addBlockContentToFile(rewriteFileModel, errorMessage);

relationships.forEach(relationship => {
const relationshipType = relationship.relationshipType;
if (relationshipType === 'one-to-many' || relationshipType === 'many-to-many') {
const rewriteFileModelWithRelationships = this.generateFileModel(
applicationPropertiesFileName,
needle,
`quarkus.hibernate-orm.cache."${cacheName}.${
relationship.relationshipFieldNamePlural
}".expiration.max-idle=${CACHE_EXPIRE_AFTER_WRITE}\n` +
`quarkus.hibernate-orm.cache."${cacheName}.${
relationship.relationshipFieldNamePlural
}".memory.object-count=${CACHE_MAXIMUM_SIZE}`
);
this.addBlockContentToFile(rewriteFileModelWithRelationships, errorMessage);
}
});
}
};
83 changes: 43 additions & 40 deletions generators/server/prompts.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,46 +189,49 @@ function askForServerSideOpts(meta) {
].concat(constants.SQL_DB_OPTIONS.find(it => it.value === response.prodDatabaseType)),
default: 0
},
// ,
// {
// when: () => !reactive,
// type: 'list',
// name: 'cacheProvider',
// message: 'Do you want to use the Spring cache abstraction?',
// choices: [
// {
// value: 'ehcache',
// name: 'Yes, with the Ehcache implementation (local cache, for a single node)'
// },
// {
// value: 'caffeine',
// name: 'Yes, with the Caffeine implementation (local cache, for a single node)'
// },
// {
// value: 'hazelcast',
// name:
// 'Yes, with the Hazelcast implementation (distributed cache, for multiple nodes, supports rate-limiting for gateway applications)'
// },
// {
// value: 'infinispan',
// name: '[BETA] Yes, with the Infinispan implementation (hybrid cache, for multiple nodes)'
// },
// {
// value: 'memcached',
// 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'
// },
// {
// value: 'no',
// name: 'No - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!'
// }
// ],
// default: applicationType === 'microservice' || applicationType === 'uaa' ? 2 : 0
// },
{
when: () => !reactive,
type: 'list',
name: 'cacheProvider',
message: 'Do you want to use the Quarkus cache abstraction?',
choices: [
/*
{
value: 'ehcache',
name: 'Yes, with the Ehcache implementation (local cache, for a single node)'
},
*/
{
value: 'caffeine',
name: 'Yes, with the Caffeine implementation (local cache, for a single node)'
},
/*
{
value: 'hazelcast',
name:
'Yes, with the Hazelcast implementation (distributed cache, for multiple nodes, supports rate-limiting for gateway applications)'
},
{
value: 'infinispan',
name: '[BETA] Yes, with the Infinispan implementation (hybrid cache, for multiple nodes)'
},
{
value: 'memcached',
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'
},
*/
{
value: 'no',
name: 'No - Warning, when using an SQL database, this will disable the Hibernate 2nd level cache!'
}
]
// default: applicationType === 'microservice' || applicationType === 'uaa' ? 2 : 0
},
{
when: response => response.databaseType === 'sql' && !reactive,
type: 'confirm',
Expand Down
3 changes: 3 additions & 0 deletions generators/server/templates/quarkus/build.gradle.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ dependencies {
implementation "io.quarkus:quarkus-elytron-security"
implementation "io.quarkus:quarkus-smallrye-jwt"
implementation "io.quarkus:quarkus-smallrye-openapi"
<%_ if (cacheManagerIsAvailable) { _%>
implementation 'io.quarkus:quarkus-cache'
<%_ } _%>
implementation "io.quarkus:quarkus-smallrye-health"
implementation "io.quarkus:quarkus-micrometer"
implementation "io.micrometer:micrometer-registry-prometheus"
Expand Down
3 changes: 0 additions & 3 deletions generators/server/templates/quarkus/gradle.properties.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ log4j2_mock_version=0.0.2
<%_ if (enableSwaggerCodegen) { _%>
jackson_databind_nullable_version=<%= JACKSON_DATABIND_NULLABLE_VERSION %>
<%_ } _%>
<%_ if (cacheProvider === 'caffeine') { _%>
caffeine_version=2.8.1
typesafe_config_version=1.4.0
<%_ } _%>
liquibase_hibernate5_version=3.8
liquibaseTaskPrefix=liquibase

Expand Down
Loading

0 comments on commit 54195e4

Please sign in to comment.