Repository: jhipster/generator-jhipster
Pull request #6451 (2017-10-02 00:05:40)

add spring-controller sub-generator
for #6440 

based on service and renamed service subgen to spring-service

I would like to add both, travis tests and integration tests for the controller...

additionally, it might be interesting for the interactive mode to add controller actions like fields in entity generator, where the user can choose the HTTP method and the action's name. WDYT? 

- [x] [Travis tests](https://travis-ci.org/jhipster/generator-jhipster/pull_requests) are green
- [x] Tests are added where necessary
- [ ] Documentation is added/updated where necessary
- [x] Coding Rules & Commit Guidelines as per our [CONTRIBUTING.md document](https://github.com/jhipster/generator-jhipster/blob/master/CONTRIBUTING.md) are followed



Patch:
diff --git a/cli/commands.js b/cli/commands.js
index 8b14f17f6b..cd7224a05f 100644
--- a/cli/commands.js
+++ b/cli/commands.js
@@ -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'
     }
diff --git a/generators/spring-controller/USAGE b/generators/spring-controller/USAGE
new file mode 100644
index 0000000000..9cb9d162b8
--- /dev/null
+++ b/generators/spring-controller/USAGE
@@ -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"
diff --git a/generators/spring-controller/index.js b/generators/spring-controller/index.js
new file mode 100644
index 0000000000..cb93841842
--- /dev/null
+++ b/generators/spring-controller/index.js
@@ -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`
+        );
+    }
+};
diff --git a/generators/spring-controller/prompts.js b/generators/spring-controller/prompts.js
new file mode 100644
index 0000000000..5f5fad881a
--- /dev/null
+++ b/generators/spring-controller/prompts.js
@@ -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)) {
+                        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);
+            } else {
+                done();
+            }
+        });
+    };
+
+    const done = this.async();
+    askForControllerAction(done);
+}
+
diff --git a/generators/spring-controller/templates/src/main/java/package/web/rest/_Controller.java b/generators/spring-controller/templates/src/main/java/package/web/rest/_Controller.java
new file mode 100644
index 0000000000..2f6949f08b
--- /dev/null
+++ b/generators/spring-controller/templates/src/main/java/package/web/rest/_Controller.java
@@ -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 %>")
+public class <%= controllerClass %>Controller {
+
+    private final Logger log = LoggerFactory.getLogger(<%= controllerClass %>Controller.class);
+
+    <%_ 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 %>";
+    }
+
+    <%_ } _%>
+}
diff --git a/generators/service/USAGE b/generators/spring-service/USAGE
similarity index 85%
rename from generators/service/USAGE
rename to generators/spring-service/USAGE
index 4cb4bc55cf..60fc8419f7 100644
--- a/generators/service/USAGE
+++ b/generators/spring-service/USAGE
@@ -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


Test patch:
diff --git a/generators/spring-controller/templates/src/test/java/package/web/rest/_ControllerIntTest.java b/generators/spring-controller/templates/src/test/java/package/web/rest/_ControllerIntTest.java
new file mode 100644
index 0000000000..c09c2d9534
--- /dev/null
+++ b/generators/spring-controller/templates/src/test/java/package/web/rest/_ControllerIntTest.java
@@ -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)
+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());
+    }
+    <%_ } _%>
+
+}
diff --git a/generators/service/index.js b/generators/spring-service/index.js
similarity index 100%
rename from generators/service/index.js
rename to generators/spring-service/index.js
diff --git a/generators/service/templates/src/main/java/package/service/_Service.java b/generators/spring-service/templates/src/main/java/package/service/_Service.java
similarity index 100%
rename from generators/service/templates/src/main/java/package/service/_Service.java
rename to generators/spring-service/templates/src/main/java/package/service/_Service.java
diff --git a/generators/service/templates/src/main/java/package/service/impl/_ServiceImpl.java b/generators/spring-service/templates/src/main/java/package/service/impl/_ServiceImpl.java
similarity index 100%
rename from generators/service/templates/src/main/java/package/service/impl/_ServiceImpl.java
rename to generators/spring-service/templates/src/main/java/package/service/impl/_ServiceImpl.java
diff --git a/test/service.spec.js b/test/service.spec.js
index 89de73c27c..ab34e50563 100644
--- a/test/service.spec.js
+++ b/test/service.spec.js
@@ -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);
                 })
diff --git a/test/spring-controller.spec.js b/test/spring-controller.spec.js
new file mode 100644
index 0000000000..2433b39d26
--- /dev/null
+++ b/test/spring-controller.spec.js
@@ -0,0 +1,36 @@
+/* global describe, beforeEach, it */
+
+const path = require('path');
+const assert = require('yeoman-assert');
+const helpers = require('yeoman-test');
+const fse = require('fs-extra');
+const constants = require('../generators/generator-constants');
+
+const SERVER_MAIN_SRC_DIR = constants.SERVER_MAIN_SRC_DIR;
+const SERVER_TEST_SRC_DIR = constants.SERVER_TEST_SRC_DIR;
+
+describe('JHipster generator spring-controller', () => {
+    describe('creates spring controller', () => {
+        beforeEach((done) => {
+            helpers.run(require.resolve('../generators/spring-controller'))
+                .inTmpDir((dir) => {
+                    fse.copySync(path.join(__dirname, '../test/templates/default'), dir);
+                })
+                .withArguments(['foo'])
+                .withPrompts({
+                    actionAdd: false
+                })
+                .on('end', done);
+        });
+
+        it('creates controller files', () => {
+            assert.file([
+                `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/FooController.java`
+            ]);
+
+            assert.file([
+                `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/web/rest/FooControllerIntTest.java`
+            ]);
+        });
+    });
+});