Restore previous HTML form in a different location

This commit restores the previous HTML page that is currently used by
the STS wizard at a different location. This allows a single instance
to serve both the new UI and the (now) STS-specific form.

The LegacyStsController can be used to serve that endpoint and is not
auto configured. app.groovy has been updated to explicitely import that
bean.

The tests infrastructure has been abstracted a bit so that both pages
are actually tested with a set of common tests.

Fixes gh-33
This commit is contained in:
Stephane Nicoll
2014-08-26 10:45:52 +02:00
parent a1e01fc0ba
commit 6e4718fb55
16 changed files with 507 additions and 117 deletions

View File

@@ -1,4 +1,13 @@
package app
import io.spring.initializr.web.LegacyStsController
@Grab('io.spring.initalizr:initializr:1.0.0.BUILD-SNAPSHOT')
class InitializerService { }
class InitializerService {
@Bean
@SuppressWarnings("deprecation")
LegacyStsController legacyStsController() {
new LegacyStsController()
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* 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 io.spring.initializr.web
import io.spring.initializr.InitializrMetadata
import org.springframework.beans.factory.annotation.Autowired
import static io.spring.initializr.support.GroovyTemplate.template
/**
* A base controller that uses {@link InitializrMetadata}
*
* @author Stephane Nicoll
* @since 1.0
*/
abstract class AbstractInitializrController {
@Autowired
protected InitializrMetadata metadata
/**
* Render the home page with the specified template.
*/
protected String renderHome(String templatePath) {
def model = [:]
metadata.properties.each { model[it.key] = it.value }
template templatePath, model
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* 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 io.spring.initializr.web
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
/**
* A controller used to serve the legacy home page used by STS.
*
* @author Stephane Nicoll
* @since 1.0
*/
@Controller
@Deprecated
class LegacyStsController extends AbstractInitializrController {
@RequestMapping(value = '/sts', produces = 'text/html')
@ResponseBody
String stsHome() {
renderHome('sts-home.html')
}
}

View File

@@ -31,8 +31,6 @@ import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
import static io.spring.initializr.support.GroovyTemplate.template
/**
* The main initializr controller provides access to the configured
* metadata and serves as a central endpoint to generate projects
@@ -43,13 +41,10 @@ import static io.spring.initializr.support.GroovyTemplate.template
* @since 1.0
*/
@Controller
class MainController {
class MainController extends AbstractInitializrController {
private static final Logger logger = LoggerFactory.getLogger(MainController.class)
@Autowired
private InitializrMetadata metadata
@Autowired
private ProjectGenerator projectGenerator
@@ -69,9 +64,7 @@ class MainController {
@RequestMapping(value = '/', produces = 'text/html')
@ResponseBody
String home() {
def model = [:]
metadata.properties.each { model[it.key] = it.value }
template 'home.html', model
renderHome('home.html')
}
@RequestMapping('/spring')

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<title>Spring Initializr</title>
<link rel="stylesheet"
href="/css/bootstrap-sts.min.css"/>
</head>
<body>
<div class="container">
<div class="navbar">
<div class="navbar-inner">
<a class="brand"
href="https://spring.io">
Spring
</a>
<ul class="nav">
<li>
<a href="/" title="Download a template project or build file with some very basic content.">
Projects
</a>
</li>
<li>
<a href="/spring.zip" title="Download the spring CLI as a zip distribution (unpack and run bin/spring on a command line).">
Download
</a>
</li>
<li>
<a href="/install.sh" title="Installer for the spring CLI command on Un*x-like system (should work on Linux, Mac or Cygwin). Curl this link and pipe to bash, or download the script and run it.">
Installer
</a>
</li>
</ul>
</div>
</div>
<h1>Spring Initializr</h1>
<div>
<form id="form" action="/starter.zip" method="get">
<label for="groupId">Group:</label> <input id="groupId" type="text" value="${defaults.groupId}" name="groupId"/>
<label for="artifactId">Artifact:</label> <input id="artifactId" type="text" value="${defaults.artifactId}" name="artifactId"/>
<label for="name">Name:</label> <input id="name" type="text" value="${defaults.name}" name="name"/>
<label for="name">Description:</label> <input id="description" type="text" value="${defaults.description}" name="description"/>
<label for="packageName">Package Name:</label> <input id="packageName" type="text" value="${defaults.packageName}" name="packageName"/>
<label>Styles:</label>
<% dependencies.each { %>
<% it.content.each { %>
<label class="checkbox">
<input type="checkbox" name="style" value="${it.id}"/>
${it.name}
</label><% } } %>
<label>Type:</label>
<% types.each { %>
<label class="radio">
<input type="radio" name="type" value="${it.id}"${it.default==true ? ' checked="true"' : ''} onclick="javascript:this.form.action='${it.action}'"/>
${it.name}
</label><% } %>
<label>Packaging:</label>
<% packagings.each { %>
<label class="radio">
<input type="radio" name="packaging" value="${it.id}"${it.default==true ? ' checked="true"' : ''}/>
${it.name}
</label><% } %>
<label>Java Version:</label>
<% javaVersions.each { %>
<label class="radio">
<input type="radio" name="javaVersion" value="${it.id}"${it.default==true ? ' checked="true"' : ''}/>
${it.name}
</label><% } %>
<label>Language:</label>
<% languages.each { %>
<label class="radio">
<input type="radio" name="language" value="${it.id}"${it.default==true ? ' checked="true"' : ''}/>
${it.name}
</label><% } %>
<label>Spring Boot Version:</label>
<% bootVersions.each { %>
<label class="radio">
<input type="radio" name="bootVersion" value="${it.id}"${it.default==true ? ' checked="true"' : ''}/>
${it.name}
</label><% } %>
<button name="generate-project" type="submit" class="btn">Generate</button>
</form>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* 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 io.spring.initializr.web
import com.gargoylesoftware.htmlunit.WebClient
import com.gargoylesoftware.htmlunit.WebRequest
import com.gargoylesoftware.htmlunit.html.HtmlPage
import io.spring.initializr.support.ProjectAssert
import io.spring.initializr.web.support.HomePage
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.htmlunit.MockMvcWebConnection
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.context.WebApplicationContext
/**
* Integration tests that are actually using the HTML page to request new
* projects. Used to test both the default home page and the legacy one
* used by STS.
*
* @author Stephane Nicoll
*/
@ActiveProfiles('test-default')
abstract class AbstractInitializerControllerFormIntegrationTests extends AbstractInitializrControllerIntegrationTests {
@Autowired
private WebApplicationContext context
private WebClient webClient
@Before
void setup() {
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.build()
webClient = new WebClient()
webClient.setWebConnection(new MockMvcWebConnection(mockMvc, ''))
}
@After
void cleanup() {
this.webClient.closeAllWindows()
}
@Test
void createDefaultProject() {
HomePage page = home()
ProjectAssert projectAssert = zipProjectAssert(page.generateProject())
projectAssert.isMavenProject().isJavaProject().hasStaticAndTemplatesResources(false)
.pomAssert().hasDependenciesCount(1).hasSpringBootStarterDependency('test')
}
@Test
void createProjectWithCustomDefaults() {
HomePage page = home()
page.groupId = 'com.acme'
page.artifactId = 'foo-bar'
page.name = 'My project'
page.description = 'A description for my project'
page.dependencies << 'web' << 'data-jpa'
ProjectAssert projectAssert = zipProjectAssert(page.generateProject())
projectAssert.isMavenProject().isJavaProject().hasStaticAndTemplatesResources(true)
projectAssert.pomAssert().hasGroupId('com.acme').hasArtifactId('foo-bar')
.hasName('My project').hasDescription('A description for my project')
.hasSpringBootStarterDependency('web')
.hasSpringBootStarterDependency('data-jpa')
.hasSpringBootStarterDependency('test')
}
@Test
void createSimpleGradleProject() {
HomePage page = home()
page.type = 'gradle.zip'
page.dependencies << 'data-jpa'
ProjectAssert projectAssert = zipProjectAssert(page.generateProject())
projectAssert.isGradleProject().isJavaProject().hasStaticAndTemplatesResources(false)
}
@Test
void createWarProject() {
HomePage page = home()
page.packaging = 'war'
ProjectAssert projectAssert = zipProjectAssert(page.generateProject())
projectAssert.isMavenProject().isJavaWarProject()
.pomAssert().hasPackaging('war').hasDependenciesCount(3)
.hasSpringBootStarterDependency('web') // Added with war packaging
.hasSpringBootStarterDependency('tomcat')
.hasSpringBootStarterDependency('test')
}
HomePage home() {
WebRequest request = new WebRequest(new URL('http://localhost' + homeContext()), 'text/html')
HtmlPage home = webClient.getPage(request)
createHomePage(home)
}
/**
* Provide the context of the home page
*/
protected abstract String homeContext();
/**
* Create a {@link HomePage} instance based on the specified {@link HtmlPage}
*/
protected abstract HomePage createHomePage(HtmlPage home)
}

View File

@@ -40,7 +40,7 @@ import org.springframework.web.client.RestTemplate
@SpringApplicationConfiguration(classes = Config.class)
@WebAppConfiguration
@IntegrationTest('server.port=0')
abstract class AbstractMainControllerIntegrationTests {
abstract class AbstractInitializrControllerIntegrationTests {
@Rule
public final TemporaryFolder folder = new TemporaryFolder()
@@ -63,6 +63,17 @@ abstract class AbstractMainControllerIntegrationTests {
/**
* Return a {@link ProjectAssert} for the following archive content.
*/
protected ProjectAssert zipProjectAssert(byte[] content) {
projectAssert(content, ArchiveType.ZIP)
}
/**
* Return a {@link ProjectAssert} for the following TGZ archive.
*/
protected ProjectAssert tgzProjectAssert(byte[] content) {
projectAssert(content, ArchiveType.TGZ)
}
ProjectAssert projectAssert(byte[] content, ArchiveType archiveType) {
File archiveFile = writeArchive(content)
@@ -90,7 +101,7 @@ abstract class AbstractMainControllerIntegrationTests {
}
enum ArchiveType {
private enum ArchiveType {
ZIP,
TGZ

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* 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 io.spring.initializr.web
import com.gargoylesoftware.htmlunit.html.HtmlPage
import io.spring.initializr.web.support.HomePage
import io.spring.initializr.web.support.StsHomePage
import org.springframework.boot.test.SpringApplicationConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* Form based tests for the "legacy" home page that STS is still using.
*
* @author Stephane Nicoll
*/
@SpringApplicationConfiguration(classes = LegacyStsConfig.class)
class LegacyStsControllerFormIntegrationTests extends AbstractInitializerControllerFormIntegrationTests {
@Override
protected String homeContext() {
'/sts'
}
@Override
protected HomePage createHomePage(HtmlPage home) {
new StsHomePage(home)
}
@Configuration
public static class LegacyStsConfig {
@Bean
@SuppressWarnings("deprecation")
LegacyStsController legacyStsController() {
new LegacyStsController()
}
}
}

View File

@@ -11,7 +11,7 @@ import static org.junit.Assert.assertTrue
* @author Stephane Nicoll
*/
@ActiveProfiles(['test-default', 'test-custom-defaults'])
class MainControllerDefaultsIntegrationTests extends AbstractMainControllerIntegrationTests {
class MainControllerDefaultsIntegrationTests extends AbstractInitializrControllerIntegrationTests {
// see defaults customization

View File

@@ -28,7 +28,7 @@ import static org.junit.Assert.assertEquals
* @author Stephane Nicoll
*/
@ActiveProfiles(['test-default', 'test-custom-env'])
class MainControllerEnvIntegrationTests extends AbstractMainControllerIntegrationTests {
class MainControllerEnvIntegrationTests extends AbstractInitializrControllerIntegrationTests {
@Test
void downloadCliWithCustomRepository() {

View File

@@ -16,98 +16,25 @@
package io.spring.initializr.web
import com.gargoylesoftware.htmlunit.WebClient
import com.gargoylesoftware.htmlunit.WebRequest
import com.gargoylesoftware.htmlunit.html.HtmlPage
import io.spring.initializr.support.ProjectAssert
import io.spring.initializr.web.support.DefaultHomePage
import io.spring.initializr.web.support.HomePage
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.htmlunit.MockMvcWebConnection
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.context.WebApplicationContext
/**
* Form based tests for the "regular" home page.
*
* @author Stephane Nicoll
*/
@ActiveProfiles('test-default')
class MainControllerFormIntegrationTests extends AbstractMainControllerIntegrationTests {
class MainControllerFormIntegrationTests extends AbstractInitializerControllerFormIntegrationTests {
@Autowired
private WebApplicationContext context
private WebClient webClient
@Before
void setup() {
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.build()
webClient = new WebClient()
webClient.setWebConnection(new MockMvcWebConnection(mockMvc, ''))
@Override
protected String homeContext() {
'/'
}
@After
void cleanup() {
this.webClient.closeAllWindows()
@Override
protected HomePage createHomePage(HtmlPage home) {
new DefaultHomePage(home)
}
@Test
void createDefaultProject() {
HomePage page = home()
ProjectAssert projectAssert = projectAssert(page.generateProject(), ArchiveType.ZIP)
projectAssert.isMavenProject().isJavaProject().hasStaticAndTemplatesResources(false)
.pomAssert().hasDependenciesCount(1).hasSpringBootStarterDependency('test')
}
@Test
void createProjectWithCustomDefaults() {
HomePage page = home()
page.groupId = 'com.acme'
page.artifactId = 'foo-bar'
page.name = 'My project'
page.description = 'A description for my project'
page.dependencies << 'web' << 'data-jpa'
ProjectAssert projectAssert = projectAssert(page.generateProject(), ArchiveType.ZIP)
projectAssert.isMavenProject().isJavaProject().hasStaticAndTemplatesResources(true)
projectAssert.pomAssert().hasGroupId('com.acme').hasArtifactId('foo-bar')
.hasName('My project').hasDescription('A description for my project')
.hasSpringBootStarterDependency('web')
.hasSpringBootStarterDependency('data-jpa')
.hasSpringBootStarterDependency('test')
}
@Test
void createSimpleGradleProject() {
HomePage page = home()
page.type = 'gradle.zip'
page.dependencies << 'data-jpa'
ProjectAssert projectAssert = projectAssert(page.generateProject(), ArchiveType.ZIP)
projectAssert.isGradleProject().isJavaProject().hasStaticAndTemplatesResources(false)
}
@Test
void createWarProject() {
HomePage page = home()
page.packaging = 'war'
ProjectAssert projectAssert = projectAssert(page.generateProject(), ArchiveType.ZIP)
projectAssert.isMavenProject().isJavaWarProject()
.pomAssert().hasPackaging('war').hasDependenciesCount(3)
.hasSpringBootStarterDependency('web') // Added with war packaging
.hasSpringBootStarterDependency('tomcat')
.hasSpringBootStarterDependency('test')
}
HomePage home() {
WebRequest request = new WebRequest(new URL('http://localhost/'), 'text/html')
HtmlPage home = webClient.getPage(request)
return new HomePage(home)
}
}

View File

@@ -36,11 +36,11 @@ import static org.junit.Assert.*
* @author Stephane Nicoll
*/
@ActiveProfiles('test-default')
class MainControllerIntegrationTests extends AbstractMainControllerIntegrationTests {
class MainControllerIntegrationTests extends AbstractInitializrControllerIntegrationTests {
@Test
void simpleZipProject() {
download('/starter.zip?style=web&style=jpa', ArchiveType.ZIP).isJavaProject().isMavenProject()
downloadZip('/starter.zip?style=web&style=jpa').isJavaProject().isMavenProject()
.hasStaticAndTemplatesResources(true).pomAssert()
.hasDependenciesCount(3)
.hasSpringBootStarterDependency('web')
@@ -50,7 +50,7 @@ class MainControllerIntegrationTests extends AbstractMainControllerIntegrationTe
@Test
void simpleTgzProject() {
download('/starter.tgz?style=org.acme:bar', ArchiveType.TGZ).isJavaProject().isMavenProject()
downloadTgz('/starter.tgz?style=org.acme:bar').isJavaProject().isMavenProject()
.hasStaticAndTemplatesResources(false).pomAssert()
.hasDependenciesCount(2)
.hasDependency('org.acme', 'bar', '2.1.0')
@@ -58,7 +58,7 @@ class MainControllerIntegrationTests extends AbstractMainControllerIntegrationTe
@Test
void gradleWarProject() {
download('/starter.zip?style=web&style=security&packaging=war&type=gradle.zip', ArchiveType.ZIP)
downloadZip('/starter.zip?style=web&style=security&packaging=war&type=gradle.zip')
.isJavaWarProject().isGradleProject()
}
@@ -167,9 +167,14 @@ class MainControllerIntegrationTests extends AbstractMainControllerIntegrationTe
}
private ProjectAssert download(String context, ArchiveType archiveType) {
private ProjectAssert downloadZip(String context) {
byte[] body = restTemplate.getForObject(createUrl(context), byte[])
projectAssert(body, archiveType)
zipProjectAssert(body)
}
private ProjectAssert downloadTgz(String context) {
byte[] body = restTemplate.getForObject(createUrl(context), byte[])
tgzProjectAssert(body)
}
private static JSONObject readJson(String version) {

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* 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 io.spring.initializr.web.support
import com.gargoylesoftware.htmlunit.html.HtmlPage
import com.gargoylesoftware.htmlunit.html.HtmlSelect
/**
* The default home page.
*
* @author Stephane Nicoll
*/
class DefaultHomePage extends HomePage {
DefaultHomePage(HtmlPage page) {
super(page)
}
@Override
protected void setup() {
super.setup()
select('type', type)
select('packaging', packaging)
}
private void select(String selectId, String value) {
if (value != null) {
HtmlSelect input = page.getHtmlElementById(selectId)
input.setSelectedAttribute(value, true)
}
}
}

View File

@@ -25,7 +25,7 @@ import io.spring.initializr.support.ProjectAssert
*
* @author Stephane Nicoll
*/
class HomePage {
abstract class HomePage {
String groupId
String artifactId
@@ -36,9 +36,9 @@ class HomePage {
String packaging
List<String> dependencies = []
private final HtmlPage page
protected final HtmlPage page
HomePage(HtmlPage page) {
protected HomePage(HtmlPage page) {
this.page = page
}
@@ -58,32 +58,23 @@ class HomePage {
* Setup the {@link HtmlPage} with the customization of this
* instance. Only applied when a non-null value is set
*/
private void setup() {
protected void setup() {
setTextValue('groupId', groupId)
setTextValue('artifactId', artifactId)
setTextValue('name', name)
setTextValue('description', description)
setTextValue('packageName', packageName)
select('type', type)
select('packaging', packaging)
selectDependencies(dependencies)
}
private void setTextValue(String elementId, String value) {
protected void setTextValue(String elementId, String value) {
if (value != null) {
HtmlTextInput input = page.getHtmlElementById(elementId)
input.setValueAttribute(value)
}
}
private void select(String selectId, String value) {
if (value != null) {
HtmlSelect input = page.getHtmlElementById(selectId)
input.setSelectedAttribute(value, true)
}
}
private void selectDependencies(List<String> dependencies) {
protected void selectDependencies(List<String> dependencies) {
List<DomElement> styles = page.getElementsByName("style")
Map<String, HtmlCheckBoxInput> allStyles = new HashMap<>()
for (HtmlCheckBoxInput checkBoxInput : styles) {

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* 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 io.spring.initializr.web.support
import com.gargoylesoftware.htmlunit.html.HtmlPage
/**
* The old home page, still used by the STS wizard.
*
* @author Stephane Nicoll
*/
class StsHomePage extends HomePage {
StsHomePage(HtmlPage page) {
super(page)
}
@Override
protected void setup() {
super.setup()
select('type', type)
select('packaging', packaging)
}
private void select(String selectId, String value) {
if (value != null) {
page.getElementsByIdAndOrName(selectId).each {
it.checked = value.equals(it.defaultValue)
}
}
}
}