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

add spring-controller sub-generator #6451

Merged
merged 10 commits into from Oct 11, 2017
Copy path View file
@@ -52,10 +52,15 @@ module.exports = {
server: {
desc: 'Create a new JHipster server-side application'
},
service: {
'spring-service': {
alias: 'service',
argument: ['name'],
desc: 'Create a new Spring service bean'
},
'spring-controller': {
argument: ['name'],
desc: 'Create a new Spring controller'
},
upgrade: {
desc: 'Upgrade the JHipster version, and upgrade the generated application'
}
@@ -0,0 +1,8 @@
Description:
Creates a new simple spring REST MVC controller.
Example:
jhipster spring-controller Foo
This will create:
src/main/java/package/web/rest/FooController.java with API on "api/foo"
@@ -0,0 +1,101 @@
/**
* Copyright 2013-2017 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see http://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 _ = require('lodash');
const chalk = require('chalk');
const BaseGenerator = require('../generator-base');
const constants = require('../generator-constants');
const prompts = require('./prompts');
const SERVER_MAIN_SRC_DIR = constants.SERVER_MAIN_SRC_DIR;
const SERVER_TEST_SRC_DIR = constants.SERVER_TEST_SRC_DIR;
module.exports = class extends BaseGenerator {
constructor(args, opts) {
super(args, opts);
this.argument('name', { type: String, required: true });
this.name = this.options.name;
}
initializing() {
this.log(`The spring-controller ${this.name} is being created.`);
this.baseName = this.config.get('baseName');
this.packageName = this.config.get('packageName');
this.packageFolder = this.config.get('packageFolder');
this.databaseType = this.config.get('databaseType');
this.controllerActions = [];
}
get prompting() {
return {
askForControllerActions: prompts.askForControllerActions
};
}
get default() {
return {
insight() {
const insight = this.insight();
insight.trackWithEvent('generator', 'spring-controller');
}
};
}
writing() {
this.controllerClass = _.upperFirst(this.name);
this.controllerInstance = _.lowerFirst(this.name);
this.apiPrefix = _.kebabCase(this.name);
if (this.controllerActions.length === 0) {
this.log(chalk.green('No controller actions found, addin a default action'));
this.controllerActions.push({
actionName: 'defaultAction',
actionMethod: 'Get'
});
}
// helper for Java imports
this.usedMethods = _.uniq(this.controllerActions.map(action => action.actionMethod));
this.usedMethods = this.usedMethods.sort();
this.mappingImports = this.usedMethods.map(method => `org.springframework.web.bind.annotation.${method}Mapping`);
this.mockRequestImports = this.usedMethods.map(method => `static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.${method.toLowerCase()}`);
// IntelliJ optimizes imports after a certain count
this.mockRequestImports = this.mockRequestImports.length > 3 ? ['static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*'] : this.mockRequestImports;
this.mainClass = this.getMainClassName();
this.controllerActions.forEach((action) => {
action.actionPath = _.kebabCase(action.actionName);
action.actionNameUF = _.upperFirst(action.actionName);
this.log(chalk.green(`adding ${action.actionMethod} action '${action.actionName}' for /api/${this.apiPrefix}/${action.actionPath}`));
});
this.template(
`${SERVER_MAIN_SRC_DIR}package/web/rest/_Controller.java`,
`${SERVER_MAIN_SRC_DIR}${this.packageFolder}/web/rest/${this.controllerClass}Controller.java`
);
this.template(
`${SERVER_TEST_SRC_DIR}package/web/rest/_ControllerIntTest.java`,
`${SERVER_TEST_SRC_DIR}${this.packageFolder}/web/rest/${this.controllerClass}ControllerIntTest.java`
);
}
};
@@ -0,0 +1,99 @@
/**
* Copyright 2013-2017 the original author or authors from the JHipster project.
*
* This file is part of the JHipster project, see http://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 jhiCore = require('jhipster-core');
module.exports = {
askForControllerActions
};
function askForControllerActions() {
const askForControllerAction = (done) => {
const prompts = [
{
type: 'confirm',
name: 'actionAdd',
message: 'Do you want to add an action to your controller?',
default: true
},
{
when: response => response.actionAdd === true,
type: 'input',
name: 'actionName',
validate: (input) => {
if (!(/^([a-zA-Z0-9_]*)$/.test(input))) {
return 'Your action name cannot contain special characters';
} else if (input === '') {
return 'Your action name cannot be empty';
} else if (input.charAt(0) === input.charAt(0).toUpperCase()) {
return 'Your action name cannot start with an upper case letter';
} else if (jhiCore.isReservedFieldName(input)) {

This comment has been minimized.

@deepu105

deepu105 Oct 11, 2017

Member

@xetys does this matter? I guess reserved words are ok in a method name??

This comment has been minimized.

@deepu105

deepu105 Oct 11, 2017

Member

Ok never mind seems some keywords cant be used anyway

return 'Your action name cannot contain a Java or Angular reserved keyword';
}
return true;
},
message: 'What is the name of your action?'
},
{
when: response => response.actionAdd === true,
type: 'list',
name: 'actionMethod',
message: 'What is the HTTP method of your action?',
choices: [
{
name: 'POST',
value: 'Post'
},
{
name: 'GET',
value: 'Get'
},
{
name: 'PUT',
value: 'Put'
},
{
name: 'DELETE',
value: 'Delete'
}
],
default: 1
}
];
this.prompt(prompts).then((props) => {
if (props.actionAdd) {
const controllerAction = {
actionName: props.actionName,
actionMethod: props.actionMethod
};
this.controllerActions.push(controllerAction);
askForControllerAction.call(this, done);

This comment has been minimized.

@deepu105

deepu105 Oct 11, 2017

Member

you cant do this for arrow methods just call askForControllerAction(done)

} else {
done();
}
});
};
const done = this.async();
askForControllerAction(done);
}
@@ -0,0 +1,52 @@
<%#
Copyright 2013-2017 the original author or authors from the JHipster project.
This file is part of the JHipster project, see http://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.
-%>
package <%=packageName%>.web.rest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
<%_ if (mappingImports.length > 2) { _%>
import org.springframework.web.bind.annotation.*;
<%_ } else { _%>
<%_ for(let idx in mappingImports) { _%>
import <%= mappingImports[idx] %>;
<%_ } _%>
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
<%_ } _%>
/**
* <%= controllerClass %> controller
*/
@RestController
@RequestMapping("/api/<%= apiPrefix %>")

This comment has been minimized.

@jdubois

jdubois Oct 3, 2017

Member

Maybe we should also generate a unique URL? Here you would get error when you generate 2 controllers in a row. How about "/api/<%= apiPrefix %>-<%=controllerClass%>"

This comment has been minimized.

@jdubois

jdubois Oct 3, 2017

Member

Maybe use Lodash to use a dasherized version of "controllerClass"

This comment has been minimized.

@xetys

xetys Oct 3, 2017

Member

apiPrefix = kebabCase(controllerClass)

This comment has been minimized.

@jdubois

jdubois Oct 3, 2017

Member

the issue with KebabCase is that you would keep upper case letters -> maybe do a lower case after?

This comment has been minimized.

@xetys

xetys Oct 3, 2017

Member

no, it doesn't, I tried already...

/**
 * AnotherTest controller
 */
@RestController
@RequestMapping("/api/another-test")
public class AnotherTestController {
    //...
}

This comment has been minimized.

@jdubois

jdubois Oct 4, 2017

Member

I'm quite surprised, good news then, let's do it!

public class <%= controllerClass %>Controller {
private final Logger log = LoggerFactory.getLogger(<%= controllerClass %>Controller.class);

This comment has been minimized.

@jdubois

jdubois Oct 3, 2017

Member

Maybe we should also add a simple "GET" method? Like a "helloworld()" method that returns a String?

This comment has been minimized.

@xetys

xetys Oct 3, 2017

Member

How about a similar process like the field adding in entity sub-generator? And if no method added, then add a hello-world method

This comment has been minimized.

@deepu105

deepu105 Oct 3, 2017

Member

That would be much better otherwise the class we generate wouldnt have much value over the one you could create in an IDE

This comment has been minimized.

@jdubois

jdubois Oct 3, 2017

Member

yes +1 - but keep the question simple

<%_ for(let idx in controllerActions) { _%>
/**
* <%= controllerActions[idx].actionMethod.toUpperCase() %> <%= controllerActions[idx].actionName %>
*/
@<%= controllerActions[idx].actionMethod %>Mapping("/<%= (controllerActions[idx].actionPath) %>")
public String <%= controllerActions[idx].actionName %>() {
return "<%= controllerActions[idx].actionName %>";
}
<%_ } _%>
}
@@ -0,0 +1,68 @@
<%#
Copyright 2013-2017 the original author or authors from the JHipster project.
This file is part of the JHipster project, see http://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.
-%>
package <%=packageName%>.web.rest;
import <%=packageName%>.<%= mainClass %>;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
<%_ for(let idx in mockRequestImports) { _%>
import <%= mockRequestImports[idx] %>;
<%_ } _%>
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Test class for the <%= controllerClass %> REST controller.
*
* @see <%= controllerClass %>Controller
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = <%= mainClass %>.class)

This comment has been minimized.

@jdubois

jdubois Oct 6, 2017

Member

Oh you even generate tests on your controller!!! Big +1!

public class <%= controllerClass %>ControllerIntTest {
private MockMvc restMockMvc;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
<%= controllerClass %>Controller <%= controllerInstance %>Controller = new <%= controllerClass %>Controller();
restMockMvc = MockMvcBuilders
.standaloneSetup(<%= controllerInstance %>Controller)
.build();
}
<%_ for(let idx in controllerActions) { _%>
/**
* Test <%= controllerActions[idx].actionName %>
*/
@Test
public void test<%= controllerActions[idx].actionNameUF %>() throws Exception {
restMockMvc.perform(<%= controllerActions[idx].actionMethod.toLowerCase() %>("/api/<%= apiPrefix %>/<%= controllerActions[idx].actionPath %>"))
.andExpect(status().isOk());
}
<%_ } _%>
}
@@ -2,7 +2,7 @@ Description:
Creates a new JHipster service: this is a simple transactional Spring service bean.
Example:
jhipster service Foo
jhipster spring-service Foo
This will create:
src/main/java/package/service/FooService.java
File renamed without changes.
Copy path View file
@@ -11,7 +11,7 @@ const SERVER_MAIN_SRC_DIR = constants.SERVER_MAIN_SRC_DIR;
describe('JHipster generator service', () => {
describe('creates service without interface', () => {
beforeEach((done) => {
helpers.run(require.resolve('../generators/service'))
helpers.run(require.resolve('../generators/spring-service'))
.inTmpDir((dir) => {
fse.copySync(path.join(__dirname, '../test/templates/default'), dir);
})
@@ -37,7 +37,7 @@ describe('JHipster generator service', () => {
describe('creates service with interface', () => {
beforeEach((done) => {
helpers.run(require.resolve('../generators/service'))
helpers.run(require.resolve('../generators/spring-service'))
.inTmpDir((dir) => {
fse.copySync(path.join(__dirname, '../test/templates/default'), dir);
})
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.