Skip to content

Commit

Permalink
Merge pull request #25768 from mshima/gateway-imperative
Browse files Browse the repository at this point in the history
Add spring-cloud:gateway generator and initial imperative gateway support.
  • Loading branch information
DanielFran authored Apr 5, 2024
2 parents 99e757c + 911f4f3 commit 61e2a3b
Show file tree
Hide file tree
Showing 28 changed files with 446 additions and 200 deletions.
1 change: 1 addition & 0 deletions generators/server/__test-support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const mockedGenerators = [
`jhipster:${GENERATOR_MAVEN}:code-quality`,
`jhipster:${GENERATOR_MAVEN}:jib`,
`jhipster:${GENERATOR_MAVEN}:node`,
'jhipster:spring-cloud:gateway',
`jhipster:${GENERATOR_SPRING_DATA_CASSANDRA}`,
`jhipster:${GENERATOR_SPRING_DATA_MONGODB}`,
`jhipster:${GENERATOR_SPRING_DATA_RELATIONAL}`,
Expand Down
33 changes: 1 addition & 32 deletions generators/server/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ export default class JHipsterServerGenerator extends BaseApplicationGenerator {
const excludeWebapp = application.skipClient ? '' : ' -Dskip.installnodenpm -Dskip.npm';
scriptsStorage.set({
'app:start': './mvnw',
'backend:info': './mvnw -ntp enforcer:display-info --batch-mode',
'backend:info': './mvnw --version',
'backend:doc:test': './mvnw -ntp javadoc:javadoc --batch-mode',
'backend:nohttp:test': './mvnw -ntp checkstyle:check --batch-mode',
'backend:start': `./mvnw${excludeWebapp}`,
Expand Down Expand Up @@ -637,37 +637,6 @@ export default class JHipsterServerGenerator extends BaseApplicationGenerator {
return this.delegateTasksToBlueprint(() => this.postWriting);
}

get postWritingEntities() {
return this.asPostWritingEntitiesTaskGroup({
packageJsonE2eScripts({ application, entities }) {
if (application.applicationTypeGateway) {
const { serverPort, lowercaseBaseName } = application;
const microservices = [...new Set(entities.map(entity => entity.microserviceName))].filter(Boolean).map(ms => ms.toLowerCase());
const scriptsStorage = this.packageJson.createStorage('scripts');
const waitServices = microservices
.concat(lowercaseBaseName)
.map(ms => `npm run ci:server:await:${ms}`)
.join(' && ');

scriptsStorage.set({
[`ci:server:await:${lowercaseBaseName}`]: `wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:$npm_package_config_backend_port/management/health`,
...Object.fromEntries(
microservices.map(ms => [
`ci:server:await:${ms}`,
`wait-on -t ${WAIT_TIMEOUT} http-get://127.0.0.1:${serverPort}/services/${ms}/management/health/readiness`,
]),
),
'ci:server:await': `echo "Waiting for services to start" && ${waitServices} && echo "Services started"`,
});
}
},
});
}

get [BaseApplicationGenerator.POST_WRITING_ENTITIES]() {
return this.delegateTasksToBlueprint(() => this.postWritingEntities);
}

_configureServer(config = this.jhipsterConfigWithDefaults, dest = this.jhipsterConfig) {
// Generate JWT secret key if key does not already exist in config
if (
Expand Down
3 changes: 0 additions & 3 deletions generators/server/templates/build.gradle.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,6 @@ if (addSpringMilestoneRepository) { _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryConsul) { _%>
implementation "org.springframework.cloud:spring-cloud-starter-consul-discovery"
<%_ } _%>
<%_ if (applicationTypeGateway) { _%>
implementation "org.springframework.cloud:spring-cloud-starter-gateway"
<%_ } _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryEureka) { _%>
implementation "org.springframework.cloud:spring-cloud-starter-netflix-eureka-client"
<%_ } _%>
Expand Down
13 changes: 4 additions & 9 deletions generators/server/templates/pom.xml.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -263,17 +263,18 @@
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<%_ } _%>
<%_ if ((applicationTypeMicroservice || applicationTypeGateway) && reactive) { _%>
<%_ if (applicationTypeMicroservice || applicationTypeGateway) { _%>
<%_ if (reactive) { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<%_ } _%>
<%_ if ((applicationTypeMicroservice || applicationTypeGateway) && !reactive) { _%>
<%_ } else { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<%_ } _%>
<%_ } _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryEureka) { _%>
<dependency>
Expand All @@ -291,12 +292,6 @@
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<%_ } _%>
<%_ if (applicationTypeGateway) { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<%_ } _%>
<%_ if (serviceDiscoveryAny && serviceDiscoveryEureka) { _%>
<dependency>
<groupId>org.springframework.cloud</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ import <%= packageName %>.security.oauth2.JwtGrantedAuthorityConverter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
<%_ } _%>
<%_ if (authenticationTypeOauth2 && applicationTypeMonolith) { _%>
<%_ if (authenticationTypeOauth2 && !applicationTypeMicroservice) { _%>
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.boot.web.client.RestTemplateBuilder;
import <%= packageName %>.security.oauth2.CustomClaimConverter;
Expand Down Expand Up @@ -221,6 +221,18 @@ public class SecurityConfiguration {
<%_ } _%>
.requestMatchers(mvc.pattern("/api/admin/**")).hasAuthority(AuthoritiesConstants.ADMIN)
.requestMatchers(mvc.pattern("/api/**")).authenticated()
<%_ if (applicationTypeGateway) { _%>
<%_ if (microfrontend) { _%>
// microfrontend resources are loaded by webpack without authentication, they need to be public
.requestMatchers(mvc.pattern("/services/*/*.js")).permitAll()
.requestMatchers(mvc.pattern("/services/*/*.txt")).permitAll()
.requestMatchers(mvc.pattern("/services/*/*.json")).permitAll()
.requestMatchers(mvc.pattern("/services/*/*.js.map")).permitAll()
<%_ } _%>
.requestMatchers(mvc.pattern("/services/*/management/health/readiness")).permitAll()
.requestMatchers(mvc.pattern("/services/*/v3/api-docs")).hasAuthority(AuthoritiesConstants.ADMIN)
.requestMatchers(mvc.pattern("/services/**")).authenticated()
<%_ } _%>
<%_ if (communicationSpringWebsocket) { _%>
.requestMatchers(mvc.pattern("/websocket/**")).authenticated()
<%_ } _%>
Expand Down Expand Up @@ -267,9 +279,9 @@ public class SecurityConfiguration {
.accessDeniedHandler(new BearerTokenAccessDeniedHandler()))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()));
<%_ } else if (authenticationTypeOauth2) { _%>
<%_ if (applicationTypeMonolith) { _%>
<%_ if (!applicationTypeMicroservice) { _%>
.oauth2Login(oauth2 -> oauth2.loginPage("/").userInfoEndpoint(userInfo -> userInfo.oidcUserService(this.oidcUserService())))
<%_ } else if (applicationTypeMicroservice) { _%>
<%_ } else { _%>
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
<%_ } _%>
.oauth2ResourceServer(oauth2 -> oauth2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ public class SpaWebFilter extends OncePerRequestFilter {
<%_ if (devDatabaseTypeH2Any) { _%>
!path.startsWith("/h2-console") &&
<%_ } _%>
<%_ if (authenticationTypeOauth2 && applicationTypeMonolith) { _%>
<%_ if (authenticationTypeOauth2 && (applicationTypeGateway || applicationTypeMonolith)) { _%>
!path.startsWith("/login") &&
!path.startsWith("/oauth2") &&
<%_ } _%>
<%_ if (applicationTypeGateway) { _%>
!path.startsWith("/services") &&
<%_ } _%>
<%_ if (communicationSpringWebsocket) { _%>
!path.startsWith("/websocket") &&
<%_ } _%>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ eureka:
git-branch: ${git.branch:}
context-path: ${server.servlet.context-path:}
<%_ } _%>
<%_ if (applicationTypeGateway || (applicationTypeMicroservice && reactive)) { _%>
<%_ if ((applicationTypeGateway || applicationTypeMicroservice) && reactive) { _%>
reactive:
feign:
circuit:
Expand Down Expand Up @@ -99,7 +99,7 @@ management:
include:
- configprops
- env
<% if (applicationTypeGateway && reactive) { -%>
<% if (applicationTypeGateway) { -%>
- gateway
<% } -%>
- health
Expand Down Expand Up @@ -175,7 +175,7 @@ spring:
<%_ } _%>
application:
name: <%= baseName %>
<%_ if (serviceDiscoveryConsul || (applicationTypeGateway && reactive) || messageBrokerKafka) { _%>
<%_ if (serviceDiscoveryConsul || applicationTypeGateway || messageBrokerKafka) { _%>
cloud:
<%_ if (serviceDiscoveryConsul) { _%>
consul:
Expand All @@ -187,8 +187,9 @@ spring:
watch:
enabled: false
<%_ } _%>
<%_ if (applicationTypeGateway && reactive) { _%>
<%_ if (applicationTypeGateway) { _%>
gateway:
<%_ if (reactive) { _%>
default-filters:
- <% if (authenticationTypeJwt) { %>JWTRelay<% } else { %>TokenRelay<% } %>
discovery:
Expand All @@ -207,6 +208,16 @@ spring:
httpclient:
pool:
max-connections: 1000
<%_ } else { _%>
mvc:
routes:
- id: <%= lowercaseBaseName %>_route
uri: lb://<%= lowercaseBaseName %>:<%= serverPort %>
predicates:
- Path=/services/<%= lowercaseBaseName %>/**
filters:
- RewritePath=/services/<%= lowercaseBaseName %>/?(?<segment>.*), /$\{segment}
<%_ } _%>
<%_ } _%>
<%_ if (messageBrokerKafka) { _%>
function:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ mongock:
spring:
application:
name: <%= baseName %>
<%_ if (applicationTypeGateway && reactive) { _%>
<%_ if (applicationTypeGateway) { _%>
autoconfigure:
exclude:
- org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration
Expand Down
24 changes: 0 additions & 24 deletions generators/spring-boot/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,29 +235,6 @@ const jwtFiles = {
],
};

const gatewayFiles = {
gatewayFiles: [
{
condition: generator => generator.authenticationTypeJwt,
path: `${SERVER_MAIN_SRC_DIR}_package_/`,
renameTo: moveToJavaPackageSrcDir,
templates: ['security/jwt/JWTRelayGatewayFilterFactory.java'],
},
{
condition: generator => generator.serviceDiscoveryAny,
path: `${SERVER_MAIN_SRC_DIR}_package_/`,
renameTo: moveToJavaPackageSrcDir,
templates: ['web/rest/vm/RouteVM.java', 'web/rest/GatewayResource.java', 'web/filter/ModifyServersOpenApiFilter.java'],
},
{
condition: generator => generator.serviceDiscoveryAny,
path: `${SERVER_TEST_SRC_DIR}_package_/`,
renameTo: moveToJavaPackageTestDir,
templates: ['web/filter/ModifyServersOpenApiFilterTest.java'],
},
],
};

const swaggerFiles = {
swagger: [
{
Expand Down Expand Up @@ -544,7 +521,6 @@ export const serverFiles = mergeSections(
baseServerFiles,
addSectionsCondition(jwtFiles, context => context.authenticationTypeJwt),
addSectionsCondition(oauth2Files, context => context.authenticationTypeOauth2),
addSectionsCondition(gatewayFiles, context => context.applicationTypeGateway),
addSectionsCondition(accountFiles, context => context.generateAuthenticationApi),
addSectionsCondition(userManagementFiles, context => context.generateUserManagement),
addSectionsCondition(imperativeConfigFiles, context => !context.reactive),
Expand Down
33 changes: 16 additions & 17 deletions generators/spring-boot/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,29 +115,12 @@ export default class SpringBootGenerator extends BaseApplicationGenerator {

get configuring() {
return this.asConfiguringTaskGroup({
forceReactiveGateway() {
if (this.jhipsterConfig.applicationType === GATEWAY) {
if (this.jhipsterConfig.reactive !== undefined && !this.jhipsterConfig.reactive) {
this.log.warn('Non reactive gateway is not supported. Switching to reactive.');
}
this.jhipsterConfig.reactive = true;
}
},
checks() {
const config = this.jhipsterConfigWithDefaults;
if (config.enableHibernateCache && [NO_CACHE, MEMCACHED].includes(config.cacheProvider)) {
this.log.verboseInfo(`Disabling hibernate cache for cache provider ${config.cacheProvider}`);
this.jhipsterConfig.enableHibernateCache = false;
}

if (config.websocket && config.websocket !== NO_WEBSOCKET) {
if (config.reactive) {
throw new Error('Spring Websocket is not supported with reactive applications.');
}
if (config.applicationType === MICROSERVICE) {
throw new Error('Spring Websocket is not supported with microservice applications.');
}
}
},
feignMigration() {
const { reactive, applicationType, feignClient } = this.jhipsterConfigWithDefaults;
Expand Down Expand Up @@ -169,6 +152,7 @@ export default class SpringBootGenerator extends BaseApplicationGenerator {
return this.asComposingTaskGroup({
async composing() {
const {
applicationType,
databaseType,
messageBroker,
searchEngine,
Expand All @@ -189,6 +173,10 @@ export default class SpringBootGenerator extends BaseApplicationGenerator {
await this.composeWithJHipster('jhipster:java:node');
}

if (applicationType === GATEWAY) {
await this.composeWithJHipster('jhipster:spring-cloud:gateway');
}

if (enableTranslation) {
await this.composeWithJHipster(GENERATOR_LANGUAGES);
}
Expand Down Expand Up @@ -235,6 +223,17 @@ export default class SpringBootGenerator extends BaseApplicationGenerator {

get preparing() {
return this.asPreparingTaskGroup({
checksWebsocket({ application }) {
const { websocket } = application as any;
if (websocket && websocket !== NO_WEBSOCKET) {
if (application.reactive) {
throw new Error('Spring Websocket is not supported with reactive applications.');
}
if (application.applicationType === MICROSERVICE) {
throw new Error('Spring Websocket is not supported with microservice applications.');
}
}
},
loadSpringBootBom({ application }) {
if (this.useVersionPlaceholders) {
application.springBootDependencies = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`generator - spring-cloud:gateway with defaults options should call source snapshot 1`] = `
{
"addJavaDependencies": [
[
{
"artifactId": "spring-cloud-starter-gateway",
"groupId": "org.springframework.cloud",
},
],
],
}
`;

exports[`generator - spring-cloud:gateway with defaults options should match files snapshot 1`] = `
{
".yo-rc.json": {
"stateCleared": "modified",
},
"src/main/java/com/mycompany/myapp/security/jwt/JWTRelayGatewayFilterFactory.java": {
"stateCleared": "modified",
},
}
`;

exports[`generator - spring-cloud:gateway with imperative option should call source snapshot 1`] = `
{
"addJavaDependencies": [
[
{
"artifactId": "spring-cloud-starter-gateway-mvc",
"groupId": "org.springframework.cloud",
},
],
],
}
`;

exports[`generator - spring-cloud:gateway with imperative option should match files snapshot 1`] = `
{
".yo-rc.json": {
"stateCleared": "modified",
},
}
`;

exports[`generator - spring-cloud:gateway with serviceDiscovery option should call source snapshot 1`] = `
{
"addJavaDependencies": [
[
{
"artifactId": "spring-cloud-starter-gateway",
"groupId": "org.springframework.cloud",
},
],
],
}
`;

exports[`generator - spring-cloud:gateway with serviceDiscovery option should match files snapshot 1`] = `
{
".yo-rc.json": {
"stateCleared": "modified",
},
"src/main/java/com/mycompany/myapp/security/jwt/JWTRelayGatewayFilterFactory.java": {
"stateCleared": "modified",
},
}
`;
Loading

0 comments on commit 61e2a3b

Please sign in to comment.