/*
* 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.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import io.spring.initializr.metadata.BillOfMaterials;
import io.spring.initializr.metadata.DefaultMetadataElement;
import io.spring.initializr.metadata.Dependency;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.Repository;
import io.spring.initializr.metadata.Type;
import io.spring.initializr.util.Version;
import io.spring.initializr.util.VersionProperty;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.StringUtils;
/**
* A request to generate a project.
*
* @author Dave Syer
* @author Stephane Nicoll
*/
public class ProjectRequest extends BasicProjectRequest {
/**
* The id of the starter to use if no dependency is defined.
*/
public static final String DEFAULT_STARTER = "root_starter";
private final Map<String, Object> parameters = new LinkedHashMap<>();
// Resolved dependencies based on the ids provided by either "style" or "dependencies"
private List<Dependency> resolvedDependencies;
private final Map<String, BillOfMaterials> boms = new LinkedHashMap<>();
private final Map<String, Repository> repositories = new LinkedHashMap<>();
private final BuildProperties buildProperties = new BuildProperties();
private List<String> facets = new ArrayList<>();
private String build;
public List<Dependency> getResolvedDependencies() {
return resolvedDependencies;
}
public void setResolvedDependencies(List<Dependency> resolvedDependencies) {
this.resolvedDependencies = resolvedDependencies;
}
public List<String> getFacets() {
return facets;
}
public void setFacets(List<String> facets) {
this.facets = facets;
}
public String getBuild() {
return build;
}
public void setBuild(String build) {
this.build = build;
}
/**
* Return the additional parameters that can be used to further identify the request.
*/
public Map<String, Object> getParameters() {
return parameters;
}
public Map<String, BillOfMaterials> getBoms() {
return boms;
}
public Map<String, Repository> getRepositories() {
return repositories;
}
/**
* Return the build properties.
*/
public BuildProperties getBuildProperties() {
return buildProperties;
}
/**
* Initializes this instance with the defaults defined in the specified
* {@link InitializrMetadata}.
*/
public void initialize(InitializrMetadata metadata) {
BeanWrapperImpl bean = new BeanWrapperImpl(this);
metadata.defaults().forEach((key, value) -> {
if (bean.isWritableProperty(key)) {
// We want to be able to infer a package name if none has been
// explicitly set
if (!key.equals("packageName")) {
bean.setPropertyValue(key, value);
}
}
});
}
/**
* Resolve this instance against the specified {@link InitializrMetadata}
*/
public void resolve(InitializrMetadata metadata) {
List<String> depIds = !getStyle().isEmpty() ? getStyle() : getDependencies();
String actualBootVersion = getBootVersion() != null ? getBootVersion()
: metadata.getBootVersions().getDefault().getId();
Version requestedVersion = Version.parse(actualBootVersion);
this.resolvedDependencies = depIds.stream().map(it -> {
Dependency dependency = metadata.getDependencies().get(it);
if (dependency == null) {
throw new InvalidProjectRequestException(
"Unknown dependency '" + it + "' check project metadata");
}
return dependency.resolve(requestedVersion);
}).collect(Collectors.toList());
this.resolvedDependencies.forEach(it -> {
it.getFacets().forEach(facet -> {
if (!facets.contains(facet)) {
facets.add(facet);
}
});
if (!it.match(requestedVersion)) {
throw new InvalidProjectRequestException(
"Dependency '" + it.getId() + "' is not compatible "
+ "with Spring Boot " + requestedVersion);
}
if (it.getBom() != null) {
resolveBom(metadata, it.getBom(), requestedVersion);
}
if (it.getRepository() != null) {
String repositoryId = it.getRepository();
this.repositories.computeIfAbsent(repositoryId, s -> metadata
.getConfiguration().getEnv().getRepositories().get(s));
}
});
if (getType() != null) {
Type type = metadata.getTypes().get(getType());
if (type == null) {
throw new InvalidProjectRequestException(
"Unknown type '" + getType() + "' check project metadata");
}
String buildTag = type.getTags().get("build");
if (buildTag != null) {
this.build = buildTag;
}
}
if (getPackaging() != null) {
DefaultMetadataElement packaging = metadata.getPackagings()
.get(getPackaging());
if (packaging == null) {
throw new InvalidProjectRequestException("Unknown packaging '"
+ getPackaging() + "' check project metadata");
}
}
if (getLanguage() != null) {
DefaultMetadataElement language = metadata.getLanguages().get(getLanguage());
if (language == null) {
throw new InvalidProjectRequestException("Unknown language '"
+ getLanguage() + "' check project metadata");
}
}
if (!StringUtils.hasText(getApplicationName())) {
setApplicationName(
metadata.getConfiguration().generateApplicationName(getName()));
}
setPackageName(metadata.getConfiguration().cleanPackageName(getPackageName(),
metadata.getPackageName().getContent()));
initializeRepositories(metadata, requestedVersion);
initializeProperties(metadata);
afterResolution(metadata);
}
/**
* Set the repositories that this instance should use based on the
* {@link InitializrMetadata} and the requested Spring Boot {@link Version}.
*/
protected void initializeRepositories(InitializrMetadata metadata,
Version requestedVersion) {
if (!"RELEASE".equals(requestedVersion.getQualifier().getQualifier())) {
repositories.put("spring-snapshots", metadata.getConfiguration().getEnv()
.getRepositories().get("spring-snapshots"));
repositories.put("spring-milestones", metadata.getConfiguration().getEnv()
.getRepositories().get("spring-milestones"));
}
boms.values().forEach(it -> it.getRepositories().forEach(key -> {
repositories.computeIfAbsent(key, s -> metadata.getConfiguration()
.getEnv().getRepositories().get(s));
}));
}
protected void initializeProperties(InitializrMetadata metadata) {
if ("gradle".equals(build)) {
buildProperties.getGradle().put("springBootVersion", this::getBootVersion);
if ("kotlin".equals(getLanguage())) {
buildProperties.getGradle().put("kotlinVersion", () -> metadata
.getConfiguration().getEnv().getKotlin().getVersion());
}
}
else {
buildProperties.getMaven().put("project.build.sourceEncoding", () -> "UTF-8");
buildProperties.getMaven().put("project.reporting.outputEncoding",
() -> "UTF-8");
buildProperties.getVersions().put(new VersionProperty("java.version"),
this::getJavaVersion);
if ("kotlin".equals(getLanguage())) {
buildProperties.getVersions().put(new VersionProperty("kotlin.version"),
() -> metadata.getConfiguration().getEnv().getKotlin().getVersion());
buildProperties.getMaven().put("kotlin.compiler.incremental", () -> "true");
}
}
}
private void resolveBom(InitializrMetadata metadata, String bomId,
Version requestedVersion) {
boms.computeIfAbsent(bomId, key -> {
BillOfMaterials bom = metadata.getConfiguration().getEnv().getBoms().get(key)
.resolve(requestedVersion);
bom.getAdditionalBoms()
.forEach(id -> resolveBom(metadata, id, requestedVersion));
return bom;
});
}
/**
* Update this request once it has been resolved with the specified
* {@link InitializrMetadata}.
*/
protected void afterResolution(InitializrMetadata metadata) {
if ("war".equals(getPackaging())) {
if (!hasWebFacet()) {
// Need to be able to bootstrap the web app
resolvedDependencies.add(metadata.getDependencies().get("web"));
facets.add("web");
}
// Add the tomcat starter in provided scope
Dependency tomcat = new Dependency().asSpringBootStarter("tomcat");
tomcat.setScope(Dependency.SCOPE_PROVIDED);
resolvedDependencies.add(tomcat);
}
if (resolvedDependencies.stream().noneMatch(Dependency::isStarter)) {
// There"s no starter so we add the default one
addDefaultDependency();
}
}
/**
* Add a default dependency if the project does not define any dependency
*/
protected void addDefaultDependency() {
Dependency root = new Dependency();
root.setId(DEFAULT_STARTER);
root.asSpringBootStarter("");
resolvedDependencies.add(root);
}
/**
* Specify if this request has the web facet enabled.
*/
public boolean hasWebFacet() {
return hasFacet("web");
}
/**
* Specify if this request has the specified facet enabled
*/
public boolean hasFacet(String facet) {
return facets.contains(facet);
}
@Override
public String toString() {
return "ProjectRequest [" + "parameters=" + parameters + ", "
+ (resolvedDependencies != null
? "resolvedDependencies=" + resolvedDependencies + ", "
: "")
+ "boms=" + boms + ", " + "repositories="
+ repositories + ", " + "buildProperties="
+ buildProperties + ", " + (facets != null
? "facets=" + facets + ", " : "")
+ (build != null ? "build=" + build : "") + "]";
}
}