Java tutorial
/* * Copyright 2012-2017 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.generator; import java.beans.PropertyDescriptor; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import io.spring.initializr.InitializrException; import io.spring.initializr.metadata.BillOfMaterials; import io.spring.initializr.metadata.Dependency; import io.spring.initializr.metadata.InitializrConfiguration.Env.Maven.ParentPom; import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.InitializrMetadataProvider; import io.spring.initializr.metadata.MetadataElement; import io.spring.initializr.util.TemplateRenderer; import io.spring.initializr.util.Version; import io.spring.initializr.util.ZipUtils; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.util.Assert; import org.springframework.util.FileSystemUtils; import org.springframework.util.StreamUtils; /** * Generate a project based on the configured metadata. * * @author Dave Syer * @author Stephane Nicoll * @author Sebastien Deleuze */ public class ProjectGenerator { private static final Logger log = LoggerFactory.getLogger(ProjectGenerator.class); private static final Version VERSION_1_2_0_RC1 = Version.parse("1.2.0.RC1"); private static final Version VERSION_1_3_0_M1 = Version.parse("1.3.0.M1"); private static final Version VERSION_1_4_0_M2 = Version.parse("1.4.0.M2"); private static final Version VERSION_1_4_0_M3 = Version.parse("1.4.0.M3"); private static final Version VERSION_1_4_2_M1 = Version.parse("1.4.2.M1"); private static final Version VERSION_1_5_0_M1 = Version.parse("1.5.0.M1"); @Autowired private ApplicationEventPublisher eventPublisher; @Autowired private InitializrMetadataProvider metadataProvider; @Autowired private ProjectRequestResolver requestResolver; @Autowired private TemplateRenderer templateRenderer = new TemplateRenderer(); @Autowired private ProjectResourceLocator projectResourceLocator = new ProjectResourceLocator(); @Value("${TMPDIR:.}/initializr") private String tmpdir; private File temporaryDirectory; private transient Map<String, List<File>> temporaryFiles = new LinkedHashMap<>(); public InitializrMetadataProvider getMetadataProvider() { return metadataProvider; } public void setEventPublisher(ApplicationEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } public void setMetadataProvider(InitializrMetadataProvider metadataProvider) { this.metadataProvider = metadataProvider; } public void setRequestResolver(ProjectRequestResolver requestResolver) { this.requestResolver = requestResolver; } public void setTemplateRenderer(TemplateRenderer templateRenderer) { this.templateRenderer = templateRenderer; } public void setProjectResourceLocator(ProjectResourceLocator projectResourceLocator) { this.projectResourceLocator = projectResourceLocator; } public void setTmpdir(String tmpdir) { this.tmpdir = tmpdir; } public void setTemporaryDirectory(File temporaryDirectory) { this.temporaryDirectory = temporaryDirectory; } public void setTemporaryFiles(Map<String, List<File>> temporaryFiles) { this.temporaryFiles = temporaryFiles; } /** * Generate a Maven pom for the specified {@link ProjectRequest}. */ public byte[] generateMavenPom(ProjectRequest request) { try { Map<String, Object> model = resolveModel(request); if (!isMavenBuild(request)) { throw new InvalidProjectRequestException( "Could not generate Maven pom, " + "invalid project type " + request.getType()); } byte[] content = doGenerateMavenPom(model); publishProjectGeneratedEvent(request); return content; } catch (InitializrException ex) { publishProjectFailedEvent(request, ex); throw ex; } } /** * Generate a Gradle build file for the specified {@link ProjectRequest}. */ public byte[] generateGradleBuild(ProjectRequest request) { try { Map<String, Object> model = resolveModel(request); if (!isGradleBuild(request)) { throw new InvalidProjectRequestException( "Could not generate Gradle build, " + "invalid project type " + request.getType()); } byte[] content = doGenerateGradleBuild(model); publishProjectGeneratedEvent(request); return content; } catch (InitializrException ex) { publishProjectFailedEvent(request, ex); throw ex; } } /** * Generate a project structure for the specified {@link ProjectRequest}. Returns a * directory containing the project. */ public File generateProjectStructure(ProjectRequest request) { try { return doGenerateProjectStructure(request); } catch (InitializrException ex) { publishProjectFailedEvent(request, ex); throw ex; } } protected File doGenerateProjectStructure(ProjectRequest request) { Map<String, Object> model = resolveModel(request); File rootDir; try { rootDir = File.createTempFile("tmp", "", getTemporaryDirectory()); } catch (IOException e) { throw new IllegalStateException("Cannot create temp dir", e); } addTempFile(rootDir.getName(), rootDir); rootDir.delete(); rootDir.mkdirs(); File dir = initializerProjectDir(rootDir, request); if (isGradleBuild(request)) { String gradle = new String(doGenerateGradleBuild(model)); writeText(new File(dir, "build.gradle"), gradle); writeGradleWrapper(dir, Version.safeParse(request.getBootVersion())); } else { String pom = new String(doGenerateMavenPom(model)); writeText(new File(dir, "pom.xml"), pom); writeMavenWrapper(dir); } generateGitIgnore(dir, request); String applicationName = request.getApplicationName(); String language = request.getLanguage(); String codeLocation = language; File src = new File(new File(dir, "src/main/" + codeLocation), request.getPackageName().replace(".", "/")); src.mkdirs(); String extension = ("kotlin".equals(language) ? "kt" : language); write(new File(src, applicationName + "." + extension), "Application." + extension, model); if ("war".equals(request.getPackaging())) { String fileName = "ServletInitializer." + extension; write(new File(src, fileName), fileName, model); } File test = new File(new File(dir, "src/test/" + codeLocation), request.getPackageName().replace(".", "/")); test.mkdirs(); setupTestModel(request, model); write(new File(test, applicationName + "Tests." + extension), "ApplicationTests." + extension, model); File resources = new File(dir, "src/main/resources"); resources.mkdirs(); writeText(new File(resources, "application.properties"), ""); if (request.hasWebFacet()) { new File(dir, "src/main/resources/templates").mkdirs(); new File(dir, "src/main/resources/static").mkdirs(); } generateExternalStructure(request, dir, model); publishProjectGeneratedEvent(request); return rootDir; } private void generateExternalStructure(ProjectRequest request, File dir, Map<String, Object> model) { try { log.info("Downloading file " + request.getExternalStructure()); File file = downloadExternalStructure(request.getExternalStructure(), getTemporaryDirectory()); log.info("Downloaded file at " + file.getAbsolutePath()); ZipUtils.unzipArchive(file, dir, resolveModel(request)); } catch (IOException e) { log.error("Exception Downloading file:" + e.getMessage()); } } private File downloadExternalStructure(String urlStr, File file) throws IOException { URL url = new URL(urlStr); String filename = FilenameUtils.getBaseName(urlStr) + "." + FilenameUtils.getExtension(urlStr); log.info("Filename: " + filename); ReadableByteChannel rbc = Channels.newChannel(url.openStream()); File f = new File(file, filename); FileOutputStream fos = new FileOutputStream(f); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); rbc.close(); return f; } /** * Create a distribution file for the specified project structure directory and * extension */ public File createDistributionFile(File dir, String extension) { File download = new File(getTemporaryDirectory(), dir.getName() + extension); addTempFile(dir.getName(), download); return download; } private File getTemporaryDirectory() { if (temporaryDirectory == null) { temporaryDirectory = new File(tmpdir, "initializr"); temporaryDirectory.mkdirs(); } return temporaryDirectory; } /** * Clean all the temporary files that are related to this root directory. * @see #createDistributionFile */ public void cleanTempFiles(File dir) { List<File> tempFiles = temporaryFiles.remove(dir.getName()); if (!tempFiles.isEmpty()) { tempFiles.forEach((File file) -> { if (file.isDirectory()) { FileSystemUtils.deleteRecursively(file); } else if (file.exists()) { file.delete(); } }); } } private void publishProjectGeneratedEvent(ProjectRequest request) { ProjectGeneratedEvent event = new ProjectGeneratedEvent(request); eventPublisher.publishEvent(event); } private void publishProjectFailedEvent(ProjectRequest request, Exception cause) { ProjectFailedEvent event = new ProjectFailedEvent(request, cause); eventPublisher.publishEvent(event); } /** * Generate a {@code .gitignore} file for the specified {@link ProjectRequest} * @param dir the root directory of the project * @param request the request to handle */ protected void generateGitIgnore(File dir, ProjectRequest request) { Map<String, Object> model = new LinkedHashMap<>(); if (isMavenBuild(request)) { model.put("build", "maven"); model.put("mavenBuild", true); } else { model.put("build", "gradle"); } write(new File(dir, ".gitignore"), "gitignore.tmpl", model); } /** * Resolve the specified {@link ProjectRequest} and return the model to use to * generate the project * @param originalRequest the request to handle * @return a model for that request */ protected Map<String, Object> resolveModel(ProjectRequest originalRequest) { Assert.notNull(originalRequest.getBootVersion(), "boot version must not be null"); Map<String, Object> model = new LinkedHashMap<>(); InitializrMetadata metadata = metadataProvider.get(); ProjectRequest request = requestResolver.resolve(originalRequest, metadata); // request resolved so we can log what has been requested List<Dependency> dependencies = request.getResolvedDependencies(); List<String> dependencyIds = dependencies.stream().map(Dependency::getId).collect(Collectors.toList()); log.info("Processing request{type=" + request.getType() + ", dependencies=" + dependencyIds); if (isWar(request)) { model.put("war", true); } if (isMavenBuild(request)) { model.put("mavenBuild", true); ParentPom parentPom = metadata.getConfiguration().getEnv().getMaven() .resolveParentPom(request.getBootVersion()); if (parentPom.isIncludeSpringBootBom() && !request.getBoms().containsKey("spring-boot")) { request.getBoms().put("spring-boot", metadata.createSpringBootBom(request.getBootVersion(), "spring-boot.version")); } model.put("mavenParentGroupId", parentPom.getGroupId()); model.put("mavenParentArtifactId", parentPom.getArtifactId()); model.put("mavenParentVersion", parentPom.getVersion()); model.put("includeSpringBootBom", parentPom.isIncludeSpringBootBom()); } model.put("repositoryValues", request.getRepositories().entrySet()); if (!request.getRepositories().isEmpty()) { model.put("hasRepositories", true); } List<BillOfMaterials> resolvedBoms = request.getBoms().values().stream() .sorted(Comparator.comparing(BillOfMaterials::getOrder)).collect(Collectors.toList()); model.put("resolvedBoms", resolvedBoms); ArrayList<BillOfMaterials> reversedBoms = new ArrayList<>(resolvedBoms); Collections.reverse(reversedBoms); model.put("reversedBoms", reversedBoms); model.put("compileDependencies", filterDependencies(dependencies, Dependency.SCOPE_COMPILE)); model.put("runtimeDependencies", filterDependencies(dependencies, Dependency.SCOPE_RUNTIME)); model.put("compileOnlyDependencies", filterDependencies(dependencies, Dependency.SCOPE_COMPILE_ONLY)); model.put("providedDependencies", filterDependencies(dependencies, Dependency.SCOPE_PROVIDED)); model.put("testDependencies", filterDependencies(dependencies, Dependency.SCOPE_TEST)); request.getBoms().forEach((k, v) -> { if (v.getVersionProperty() != null) { request.getBuildProperties().getVersions().computeIfAbsent(v.getVersionProperty(), key -> v::getVersion); } }); Map<String, String> versions = new LinkedHashMap<>(); model.put("buildPropertiesVersions", versions.entrySet()); request.getBuildProperties().getVersions().forEach((k, v) -> versions.put(k, v.get())); Map<String, String> gradle = new LinkedHashMap<>(); model.put("buildPropertiesGradle", gradle.entrySet()); request.getBuildProperties().getGradle().forEach((k, v) -> gradle.put(k, v.get())); Map<String, String> maven = new LinkedHashMap<>(); model.put("buildPropertiesMaven", maven.entrySet()); request.getBuildProperties().getMaven().forEach((k, v) -> maven.put(k, v.get())); // Add various versions model.put("dependencyManagementPluginVersion", metadata.getConfiguration().getEnv().getGradle().getDependencyManagementPluginVersion()); model.put("kotlinVersion", metadata.getConfiguration().getEnv().getKotlin().getVersion()); if ("kotlin".equals(request.getLanguage())) { model.put("kotlin", true); } if ("groovy".equals(request.getLanguage())) { model.put("groovy", true); } model.put("isRelease", request.getBootVersion().contains("RELEASE")); // @SpringBootApplication available as from 1.2.0.RC1 model.put("useSpringBootApplication", VERSION_1_2_0_RC1.compareTo(Version.safeParse(request.getBootVersion())) <= 0); // Gradle plugin has changed as from 1.3.0 model.put("bootOneThreeAvailable", VERSION_1_3_0_M1.compareTo(Version.safeParse(request.getBootVersion())) <= 0); // Gradle plugin has changed again as from 1.4.2 model.put("springBootPluginName", (VERSION_1_4_2_M1.compareTo(Version.safeParse(request.getBootVersion())) <= 0 ? "org.springframework.boot" : "spring-boot")); // New testing stuff model.put("newTestInfrastructure", isNewTestInfrastructureAvailable(request)); // New Servlet Initializer location model.put("newServletInitializer", isNewServletInitializerAvailable(request)); // Java versions model.put("isJava6", isJavaVersion(request, "1.6")); model.put("isJava7", isJavaVersion(request, "1.7")); model.put("isJava8", isJavaVersion(request, "1.8")); // Append the project request to the model BeanWrapperImpl bean = new BeanWrapperImpl(request); for (PropertyDescriptor descriptor : bean.getPropertyDescriptors()) { if (bean.isReadableProperty(descriptor.getName())) { model.put(descriptor.getName(), bean.getPropertyValue(descriptor.getName())); } } if (!request.getBoms().isEmpty()) { model.put("hasBoms", true); } return model; } protected void setupTestModel(ProjectRequest request, Map<String, Object> model) { String imports = ""; String testAnnotations = ""; boolean newTestInfrastructure = isNewTestInfrastructureAvailable(request); if (newTestInfrastructure) { imports += String.format( generateImport("org.springframework.boot.test.context.SpringBootTest", request.getLanguage()) + "%n"); imports += String.format( generateImport("org.springframework.test.context.junit4.SpringRunner", request.getLanguage()) + "%n"); } else { imports += String.format(generateImport("org.springframework.boot.test.SpringApplicationConfiguration", request.getLanguage()) + "%n"); imports += String .format(generateImport("org.springframework.test.context.junit4.SpringJUnit4ClassRunner", request.getLanguage()) + "%n"); } if (request.hasWebFacet() && !newTestInfrastructure) { imports += String.format(generateImport("org.springframework.test.context.web.WebAppConfiguration", request.getLanguage()) + "%n"); testAnnotations = String.format("@WebAppConfiguration%n"); } model.put("testImports", imports); model.put("testAnnotations", testAnnotations); } protected String generateImport(String type, String language) { String end = ("groovy".equals(language) || "kotlin".equals(language)) ? "" : ";"; return "import " + type + end; } private static boolean isGradleBuild(ProjectRequest request) { return "gradle".equals(request.getBuild()); } private static boolean isMavenBuild(ProjectRequest request) { return "maven".equals(request.getBuild()); } private static boolean isWar(ProjectRequest request) { return "war".equals(request.getPackaging()); } private static boolean isNewTestInfrastructureAvailable(ProjectRequest request) { return VERSION_1_4_0_M2.compareTo(Version.safeParse(request.getBootVersion())) <= 0; } private static boolean isNewServletInitializerAvailable(ProjectRequest request) { return VERSION_1_4_0_M3.compareTo(Version.safeParse(request.getBootVersion())) <= 0; } private static boolean isGradle3Available(Version bootVersion) { return VERSION_1_5_0_M1.compareTo(bootVersion) <= 0; } private static boolean isJavaVersion(ProjectRequest request, String version) { return request.getJavaVersion().equals(version); } private byte[] doGenerateMavenPom(Map<String, Object> model) { return templateRenderer.process("starter-pom.xml", model).getBytes(); } private byte[] doGenerateGradleBuild(Map<String, Object> model) { return templateRenderer.process("starter-build.gradle", model).getBytes(); } private void writeGradleWrapper(File dir, Version bootVersion) { String gradlePrefix = isGradle3Available(bootVersion) ? "gradle3" : "gradle"; writeTextResource(dir, "gradlew.bat", gradlePrefix + "/gradlew.bat"); writeTextResource(dir, "gradlew", gradlePrefix + "/gradlew"); File wrapperDir = new File(dir, "gradle/wrapper"); wrapperDir.mkdirs(); writeTextResource(wrapperDir, "gradle-wrapper.properties", gradlePrefix + "/gradle/wrapper/gradle-wrapper.properties"); writeBinaryResource(wrapperDir, "gradle-wrapper.jar", gradlePrefix + "/gradle/wrapper/gradle-wrapper.jar"); } private void writeMavenWrapper(File dir) { writeTextResource(dir, "mvnw.cmd", "maven/mvnw.cmd"); writeTextResource(dir, "mvnw", "maven/mvnw"); File wrapperDir = new File(dir, ".mvn/wrapper"); wrapperDir.mkdirs(); writeTextResource(wrapperDir, "maven-wrapper.properties", "maven/wrapper/maven-wrapper.properties"); writeBinaryResource(wrapperDir, "maven-wrapper.jar", "maven/wrapper/maven-wrapper.jar"); } private File writeBinaryResource(File dir, String name, String location) { return doWriteProjectResource(dir, name, location, true); } private File writeTextResource(File dir, String name, String location) { return doWriteProjectResource(dir, name, location, false); } private File doWriteProjectResource(File dir, String name, String location, boolean binary) { File target = new File(dir, name); if (binary) { writeBinary(target, projectResourceLocator.getBinaryResource("classpath:project/" + location)); } else { writeText(target, projectResourceLocator.getTextResource("classpath:project/" + location)); } return target; } private File initializerProjectDir(File rootDir, ProjectRequest request) { if (request.getBaseDir() != null) { File dir = new File(rootDir, request.getBaseDir()); dir.mkdirs(); return dir; } else { return rootDir; } } public void write(File target, String templateName, Map<String, Object> model) { String body = templateRenderer.process(templateName, model); writeText(target, body); } private void writeText(File target, String body) { try (OutputStream stream = new FileOutputStream(target)) { StreamUtils.copy(body, Charset.forName("UTF-8"), stream); } catch (Exception e) { throw new IllegalStateException("Cannot write file " + target, e); } } private void writeBinary(File target, byte[] body) { try (OutputStream stream = new FileOutputStream(target)) { StreamUtils.copy(body, stream); } catch (Exception e) { throw new IllegalStateException("Cannot write file " + target, e); } } private void addTempFile(String group, File file) { temporaryFiles.computeIfAbsent(group, key -> new ArrayList<>()).add(file); } private static List<Dependency> filterDependencies(List<Dependency> dependencies, String scope) { return dependencies.stream().filter(dep -> scope.equals(dep.getScope())) .sorted(Comparator.comparing(MetadataElement::getId)).collect(Collectors.toList()); } }