From 1ea33488f6957ab8bb2c4fea92ba14d6562bd43b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Mon, 18 Aug 2014 16:14:17 +0200 Subject: [PATCH] Initializr library This commit splits the single file script in a library that can be released and published to the central repository. This allows anyone to start its own initializr instance with a proper configuration file and the following simple script: package app @Grab('io.spring.initalizr:initializr:1.0.0.BUILD-SNAPSHOT') class InitializerService { } The integration tests have been migrated so that they run as part of the build. Additional tests have been added to extract the content of the generated archive and assert the directory structure as well as the content of key files. Fixes gh-20 --- .gitignore | 2 +- README.md | 23 +- app.groovy | 301 ------------------ initializr-service/app.groovy | 4 + .../application.yml | 81 ++--- logback.xml => initializr-service/logback.xml | 0 .../manifest.yml | 0 initializr/pom.xml | 125 ++++++++ .../InitializrAutoConfiguration.groovy | 37 +++ .../initializr/InitializrMetadata.groovy | 106 ++++++ .../spring/initializr/ProjectGenerator.groovy | 171 ++++++++++ .../spring/initializr/ProjectRequest.groovy | 39 +++ .../initializr/support/GroovyTemplate.groovy | 62 ++++ .../initializr/web/MainController.groovy | 111 +++++++ .../main/resources/META-INF/spring.factories | 1 + .../static}/css/bootstrap-theme.min.css | 0 .../resources/static}/css/bootstrap.min.css | 0 .../src/main/resources/static}/css/spring.css | 0 .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin .../main/resources/static}/img/favicon.png | Bin .../resources/static}/img/platform-bg.png | Bin .../src/main/resources/static}/install.sh | 0 .../templates}/Application.groovy.tmpl | 0 .../resources/templates}/Application.java | 0 .../templates}/ApplicationTests.groovy.tmpl | 0 .../templates}/ApplicationTests.java | 0 .../templates}/ServletInitializer.groovy.tmpl | 0 .../templates}/ServletInitializer.java | 0 .../src/main/resources/templates}/gs.html | 0 .../src/main/resources/templates}/home.html | 14 +- .../resources/templates}/starter-build.gradle | 0 .../main/resources/templates}/starter-pom.xml | 0 .../initializr/InitializrMetadataTests.groovy | 48 +++ .../initializr/ProjectGeneratorTests.groovy | 78 +++++ .../support/InitializrMetadataBuilder.groovy | 127 ++++++++ .../initializr/support/PomAssert.groovy | 209 ++++++++++++ .../initializr/support/ProjectAssert.groovy | 94 ++++++ .../web/MainControllerIntegrationTests.groovy | 185 +++++++++++ .../resources/application-test-default.yml | 63 ++++ test/integration.groovy | 83 ----- 43 files changed, 1532 insertions(+), 432 deletions(-) delete mode 100644 app.groovy create mode 100644 initializr-service/app.groovy rename application.yml => initializr-service/application.yml (65%) rename logback.xml => initializr-service/logback.xml (100%) rename manifest.yml => initializr-service/manifest.yml (100%) create mode 100644 initializr/pom.xml create mode 100644 initializr/src/main/groovy/io/spring/initializr/InitializrAutoConfiguration.groovy create mode 100644 initializr/src/main/groovy/io/spring/initializr/InitializrMetadata.groovy create mode 100644 initializr/src/main/groovy/io/spring/initializr/ProjectGenerator.groovy create mode 100644 initializr/src/main/groovy/io/spring/initializr/ProjectRequest.groovy create mode 100644 initializr/src/main/groovy/io/spring/initializr/support/GroovyTemplate.groovy create mode 100644 initializr/src/main/groovy/io/spring/initializr/web/MainController.groovy create mode 100644 initializr/src/main/resources/META-INF/spring.factories rename {static => initializr/src/main/resources/static}/css/bootstrap-theme.min.css (100%) rename {static => initializr/src/main/resources/static}/css/bootstrap.min.css (100%) rename {static => initializr/src/main/resources/static}/css/spring.css (100%) rename {static => initializr/src/main/resources/static}/fonts/glyphicons-halflings-regular.eot (100%) rename {static => initializr/src/main/resources/static}/fonts/glyphicons-halflings-regular.svg (100%) rename {static => initializr/src/main/resources/static}/fonts/glyphicons-halflings-regular.ttf (100%) rename {static => initializr/src/main/resources/static}/fonts/glyphicons-halflings-regular.woff (100%) rename {static => initializr/src/main/resources/static}/img/favicon.png (100%) rename {static => initializr/src/main/resources/static}/img/platform-bg.png (100%) rename {static => initializr/src/main/resources/static}/install.sh (100%) rename {templates => initializr/src/main/resources/templates}/Application.groovy.tmpl (100%) rename {templates => initializr/src/main/resources/templates}/Application.java (100%) rename {templates => initializr/src/main/resources/templates}/ApplicationTests.groovy.tmpl (100%) rename {templates => initializr/src/main/resources/templates}/ApplicationTests.java (100%) rename {templates => initializr/src/main/resources/templates}/ServletInitializer.groovy.tmpl (100%) rename {templates => initializr/src/main/resources/templates}/ServletInitializer.java (100%) rename {templates => initializr/src/main/resources/templates}/gs.html (100%) rename {templates => initializr/src/main/resources/templates}/home.html (91%) rename {templates => initializr/src/main/resources/templates}/starter-build.gradle (100%) rename {templates => initializr/src/main/resources/templates}/starter-pom.xml (100%) create mode 100644 initializr/src/test/groovy/io/spring/initializr/InitializrMetadataTests.groovy create mode 100644 initializr/src/test/groovy/io/spring/initializr/ProjectGeneratorTests.groovy create mode 100644 initializr/src/test/groovy/io/spring/initializr/support/InitializrMetadataBuilder.groovy create mode 100644 initializr/src/test/groovy/io/spring/initializr/support/PomAssert.groovy create mode 100644 initializr/src/test/groovy/io/spring/initializr/support/ProjectAssert.groovy create mode 100644 initializr/src/test/groovy/io/spring/initializr/web/MainControllerIntegrationTests.groovy create mode 100644 initializr/src/test/resources/application-test-default.yml delete mode 100644 test/integration.groovy diff --git a/.gitignore b/.gitignore index c0646cb6..fb3644ab 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,7 @@ build target .springBeans tmp* -spring +initializer-service/spring grapes spring.zip repository/ diff --git a/README.md b/README.md index 5c957142..f46ec998 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,31 @@ Windows users we recommend [cygwin](http://cygwin.org)), or you can download the [zip file](http://start.spring.io/spring.zip) and unpack it yourself. +## Project structure + +Initializr is a library that provides all the default features and a service with a very simple script +that uses the auto-configuration feature of Spring Boot. All you need is _grabbing_ the library and +create a proper configuration file with the following script: + +``` +package org.acme.myapp + +@Grab('io.spring.initalizr:initializr:1.0.0.BUILD-SNAPSHOT') +class InitializerService { } +``` + +As a reference, `initializr-service` represents the _default_ service that runs at http://start.spring.io + ## Running the app locally -Use the spring command: +First make sure that you have built the library: + + $ cd initializr + $ mvn clean install + +Once you have done that, you can easily start the app using the spring command from the `initializr-service` +directory (`cd ../initializr-service`): $ spring run app.groovy diff --git a/app.groovy b/app.groovy deleted file mode 100644 index 65fcf0f7..00000000 --- a/app.groovy +++ /dev/null @@ -1,301 +0,0 @@ -package app - -@Grab('spring-boot-starter-actuator') -@Grab('org.codehaus.groovy:groovy-ant:2.3.2') - -@Controller -@Log -class MainController { - - @Value('${info.home:http://localhost:8080/}') - private String home - - @Value('${info.spring-boot.version}') - private String bootVersion - - @Value('${TMPDIR:.}') - private String tmpdir - - @Autowired - private Reactor reactor - - @Autowired - private Projects projects - - @ModelAttribute - PomRequest pomRequest() { - PomRequest request = new PomRequest() - request.bootVersion = bootVersion - request - } - - @RequestMapping(value='/') - @ResponseBody - Projects projects() { - projects - } - - @RequestMapping(value='/', produces='text/html') - @ResponseBody - String home() { - def model = [:] - projects.properties.each { model[it.key] = it.value } - template 'home.html', model - } - - @RequestMapping('/spring') - @ResponseBody - ResponseEntity spring() { - File download = new File(tmpdir, 'spring.zip') - if (!download.exists()) { - log.info('Creating: ' + download) - new AntBuilder().zip(destfile: download) { - zipfileset(dir:'.', includes:'spring/bin/**', filemode:'775') - zipfileset(dir:'.', includes:'spring/**', excludes:'spring/bin/**') - } - } - log.info('Uploading: ' + download) - new ResponseEntity(download.bytes, ['Content-Type':'application/zip'] as HttpHeaders, HttpStatus.OK) - } - - @RequestMapping(value='/starter.tgz', produces='application/x-compress') - @ResponseBody - ResponseEntity springTgz(PomRequest request) { - - File dir = getProjectFiles(request) - - File download = new File(tmpdir, dir.name + '.tgz') - addTempFile(dir.name, download) - - new AntBuilder().tar(destfile: download, compression: 'gzip') { - zipfileset(dir:dir, includes:'**') - } - log.info("Uploading: ${download} (${download.bytes.length} bytes)") - def result = new ResponseEntity(download.bytes, ['Content-Type':'application/x-compress'] as HttpHeaders, HttpStatus.OK) - - cleanTempFiles(dir.name) - - result - } - - @RequestMapping('/starter.zip') - @ResponseBody - ResponseEntity springZip(PomRequest request) { - - def dir = getProjectFiles(request) - - File download = new File(tmpdir, dir.name + '.zip') - addTempFile(dir.name, download) - - new AntBuilder().zip(destfile: download) { - zipfileset(dir:dir, includes:'**') - } - log.info("Uploading: ${download} (${download.bytes.length} bytes)") - def result = new ResponseEntity(download.bytes, ['Content-Type':'application/zip'] as HttpHeaders, HttpStatus.OK) - - cleanTempFiles(dir.name) - - result - } - - private void addTempFile(String group, File file) { - reactor.notify('/temp/' + group, Event.wrap(file)) - } - - private void cleanTempFiles(String group) { - reactor.notify('/clean/' + group) - } - - def getProjectFiles(PomRequest request) { - - def model = [:] - - File dir = File.createTempFile('tmp','',new File(tmpdir)); - addTempFile(dir.name, dir) - dir.delete() - dir.mkdirs() - - if (request.type.contains('gradle')) { - String gradle = new String(gradle(request, model).body) - new File(dir, 'build.gradle').write(gradle) - } else { - String pom = new String(pom(request, model).body) - new File(dir, 'pom.xml').write(pom) - } - - String language = request.language - - File src = new File(new File(dir, 'src/main/' + language),request.packageName.replace('.', '/')) - src.mkdirs() - write(src, 'Application.' + language, model) - - if (request.packaging=='war') { - write(src, 'ServletInitializer.' + language, model) - } - - File test = new File(new File(dir, 'src/test/' + language),request.packageName.replace('.', '/')) - test.mkdirs() - if (model.styles.contains('-web')) { - model.testAnnotations = '@WebAppConfiguration\n' - model.testImports = 'import org.springframework.test.context.web.WebAppConfiguration;\n' - } else { - model.testAnnotations = '' - model.testImports = '' - } - write(test, 'ApplicationTests.' + language, model) - - File resources = new File(dir, 'src/main/resources') - resources.mkdirs() - new File(resources, 'application.properties').write('') - - if (request.isWebStyle()) { - new File(dir, 'src/main/resources/templates').mkdirs() - new File(dir, 'src/main/resources/static').mkdirs() - } - - dir - - } - - def write(File src, String name, def model) { - String tmpl = name.endsWith('.groovy') ? name + '.tmpl' : name - def body = template tmpl, model - new File(src, name).write(body) - } - - @RequestMapping('/pom') - @ResponseBody - ResponseEntity pom(PomRequest request, Map model) { - model.bootVersion = request.bootVersion - new ResponseEntity(render('starter-pom.xml', request, model), ['Content-Type':'application/octet-stream'] as HttpHeaders, HttpStatus.OK) - - } - - @RequestMapping('/build') - @ResponseBody - ResponseEntity gradle(PomRequest request, Map model) { - model.bootVersion = request.bootVersion - new ResponseEntity(render('starter-build.gradle', request, model), ['Content-Type':'application/octet-stream'] as HttpHeaders, HttpStatus.OK) - } - - byte[] render(String path, PomRequest request, Map model) { - if (request.packaging=='war' && !request.isWebStyle()) { - request.style << 'web' - } - log.info("Styles requested: ${request.style}, Type requested: ${request.type}") - request.properties.each { model[it.key] = it.value } - model.styles = fixStyles(request.style) - template path, model - } - - private def fixStyles(def style) { - if (style==null || style.size()==0) { - style = [''] - } - if (!style.class.isArray() && !(style instanceof Collection)) { - style = [style] - } - style = style.collect{ it=='jpa' ? 'data-jpa' : it } - style.collect{ it=='' ? '' : '-' + it } - } - -} - -@EnableReactor -@Consumer -@Log -class TemporaryFileCleaner { - - @Autowired - Reactor reactor - - private Map files = [:].withDefault { [] } - - @Selector(value='/temp/{stem}', type=SelectorType.URI) - void add(Event file) { - String stem = file.headers.stem - files[stem] << file.data - } - - @Selector(value='/clean/{stem}', type=SelectorType.URI) - void clean(Event event) { - String stem = event.headers.stem - def tempFiles = files.remove(stem) - log.fine 'Tempfiles: ' + tempFiles - tempFiles.each { File file -> - if (file.directory) { - file.deleteDir() - } else { - file.delete() - } - } - } - -} - -class PomRequest { - def style = [] - - String name = 'demo' - String type = 'starter' - String description = 'Demo project for Spring Boot' - String groupId = 'org.test' - String artifactId - String version = '0.0.1-SNAPSHOT' - String bootVersion - String packaging = 'jar' - String language = 'java' - String packageName - String javaVersion = '1.7' - String getArtifactId() { - artifactId == null ? name : artifactId - } - String getPackageName() { - packageName == null ? name.replace('-', '.') : packageName - } - boolean isWebStyle() { - style.any { webStyle(it) } - } - private boolean webStyle(String style) { - style.contains('web') || style.contains('thymeleaf') || style.contains('freemarker') || style.contains('velocity') || style.contains('groovy-template') - } -} - -@Component -@ConfigurationProperties(prefix='projects', ignoreUnknownFields=false) -class Projects { - List