/*
* 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.metadata;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.util.StringUtils;
/**
* Various configuration options used by the service.
*
* @author Stephane Nicoll
*/
public class InitializrConfiguration {
/**
* Environment options.
*/
@NestedConfigurationProperty
private final Env env = new Env();
public Env getEnv() {
return env;
}
public void validate() {
env.validate();
}
public void merge(InitializrConfiguration other) {
env.merge(other.env);
}
/**
* Generate a suitable application name based on the specified name. If no suitable
* application name can be generated from the specified {@code name}, the
* {@link Env#fallbackApplicationName} is used instead.
* <p>
* No suitable application name can be generated if the name is {@code null} or if it
* contains an invalid character for a class identifier.
* @see Env#fallbackApplicationName
* @see Env#invalidApplicationNames
*/
public String generateApplicationName(String name) {
if (!StringUtils.hasText(name)) {
return env.fallbackApplicationName;
}
String text = splitCamelCase(name.trim());
// TODO: fix this
String result = unsplitWords(text);
if (!result.endsWith("Application")) {
result = result + "Application";
}
String candidate = StringUtils.capitalize(result);
if (hasInvalidChar(candidate)
|| env.invalidApplicationNames.contains(candidate)) {
return env.fallbackApplicationName;
}
else {
return candidate;
}
}
/**
* Clean the specified package name if necessary. If the package name cannot be
* transformed to a valid package name, the {@code defaultPackageName} is used
* instead.
* <p>
* The package name cannot be cleaned if the specified {@code packageName} is
* {@code null} or if it contains an invalid character for a class identifier.
* @see Env#invalidPackageNames
*/
public String cleanPackageName(String packageName, String defaultPackageName) {
if (!StringUtils.hasText(packageName)) {
return defaultPackageName;
}
String candidate = cleanPackageName(packageName);
if (hasInvalidChar(candidate.replace(".", ""))
|| env.invalidPackageNames.contains(candidate)) {
return defaultPackageName;
}
else {
return candidate;
}
}
static String cleanPackageName(String packageName) {
return String.join(".", packageName.trim().replaceAll("-", "").split("\\W+"))
.replaceAll("\\.[0-9]+", ".");
}
private static String unsplitWords(String text) {
return String
.join("", Arrays.stream(text
.split("(_|-| |:)+")).map(StringUtils::capitalize)
.collect(Collectors.toList()).toArray(new String[0]));
}
private static String splitCamelCase(String text) {
return String
.join("", Arrays.stream(text
.split("(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])"))
.map(it -> StringUtils.capitalize(it.toLowerCase()))
.collect(Collectors.toList())
.toArray(new String[0]));
}
private static boolean hasInvalidChar(String text) {
if (!Character.isJavaIdentifierStart(text.charAt(0))) {
return true;
}
if (text.length() > 1) {
for (int i = 1; i < text.length(); i++) {
if (!Character.isJavaIdentifierPart(text.charAt(i))) {
return true;
}
}
}
return false;
}
/**
* Defines additional environment settings.
*/
public static class Env {
/**
* The url of the repository servicing distribution bundle.
*/
private String artifactRepository = "https://repo.spring.io/release/";
/**
* The metadata url of the Spring Boot project.
*/
private String springBootMetadataUrl = "https://spring.io/project_metadata/spring-boot";
/**
* Tracking code for Google Analytics. Only enabled if a value is explicitly
* provided.
*/
private String googleAnalyticsTrackingCode;
/**
* The application name to use if none could be generated.
*/
private String fallbackApplicationName = "Application";
/**
* The list of invalid application names. If such name is chosen or generated, the
* "fallbackApplicationName" should be used instead.
*/
private List<String> invalidApplicationNames = new ArrayList<>(
Arrays.asList("SpringApplication", "SpringBootApplication"));
/**
* The list of invalid package names. If such name is chosen or generated, the the
* default package name should be used instead.
*/
private List<String> invalidPackageNames = new ArrayList<>(
Collections.singletonList("org.springframework"));
/**
* Force SSL support. When enabled, any access using http generate https links.
*/
private boolean forceSsl = true;
/**
* The "BillOfMaterials" that are referenced in this instance, identified by an
* arbitrary identifier that can be used in the dependencies definition.
*/
private final Map<String, BillOfMaterials> boms = new LinkedHashMap<>();
/**
* The "Repository" instances that are referenced in this instance, identified by
* an arbitrary identifier that can be used in the dependencies definition.
*/
private final Map<String, Repository> repositories = new LinkedHashMap<>();
/**
* Gradle-specific settings.
*/
@NestedConfigurationProperty
private final Gradle gradle = new Gradle();
/**
* Kotlin-specific settings.
*/
@NestedConfigurationProperty
private final Kotlin kotlin = new Kotlin();
/**
* Maven-specific settings.
*/
@NestedConfigurationProperty
private final Maven maven = new Maven();
public Env() {
try {
repositories.put("spring-snapshots", new Repository("Spring Snapshots",
new URL("https://repo.spring.io/snapshot"), true));
repositories.put("spring-milestones", new Repository("Spring Milestones",
new URL("https://repo.spring.io/milestone"), false));
}
catch (MalformedURLException e) {
throw new IllegalStateException("Cannot parse URL", e);
}
}
public String getSpringBootMetadataUrl() {
return springBootMetadataUrl;
}
public void setSpringBootMetadataUrl(String springBootMetadataUrl) {
this.springBootMetadataUrl = springBootMetadataUrl;
}
public String getGoogleAnalyticsTrackingCode() {
return googleAnalyticsTrackingCode;
}
public void setGoogleAnalyticsTrackingCode(String googleAnalyticsTrackingCode) {
this.googleAnalyticsTrackingCode = googleAnalyticsTrackingCode;
}
public String getFallbackApplicationName() {
return fallbackApplicationName;
}
public void setFallbackApplicationName(String fallbackApplicationName) {
this.fallbackApplicationName = fallbackApplicationName;
}
public List<String> getInvalidApplicationNames() {
return invalidApplicationNames;
}
public void setInvalidApplicationNames(List<String> invalidApplicationNames) {
this.invalidApplicationNames = invalidApplicationNames;
}
public List<String> getInvalidPackageNames() {
return invalidPackageNames;
}
public void setInvalidPackageNames(List<String> invalidPackageNames) {
this.invalidPackageNames = invalidPackageNames;
}
public boolean isForceSsl() {
return forceSsl;
}
public void setForceSsl(boolean forceSsl) {
this.forceSsl = forceSsl;
}
public String getArtifactRepository() {
return artifactRepository;
}
public Map<String, BillOfMaterials> getBoms() {
return boms;
}
public Map<String, Repository> getRepositories() {
return repositories;
}
public Gradle getGradle() {
return gradle;
}
public Kotlin getKotlin() {
return kotlin;
}
public Maven getMaven() {
return maven;
}
public void setArtifactRepository(String artifactRepository) {
if (!artifactRepository.endsWith("/")) {
artifactRepository = artifactRepository + "/";
}
this.artifactRepository = artifactRepository;
}
public void validate() {
maven.parent.validate();
boms.forEach((k, v) -> v.validate());
}
public void merge(Env other) {
artifactRepository = other.artifactRepository;
springBootMetadataUrl = other.springBootMetadataUrl;
googleAnalyticsTrackingCode = other.googleAnalyticsTrackingCode;
fallbackApplicationName = other.fallbackApplicationName;
invalidApplicationNames = other.invalidApplicationNames;
forceSsl = other.forceSsl;
gradle.merge(other.gradle);
kotlin.version = other.kotlin.version;
maven.merge(other.maven);
other.boms.forEach(boms::putIfAbsent);
other.repositories.forEach(repositories::putIfAbsent);
}
public static class Gradle {
/**
* Version of the "dependency-management-plugin" to use.
*/
private String dependencyManagementPluginVersion = "1.0.0.RELEASE";
private void merge(Gradle other) {
dependencyManagementPluginVersion = other.dependencyManagementPluginVersion;
}
public String getDependencyManagementPluginVersion() {
return dependencyManagementPluginVersion;
}
public void setDependencyManagementPluginVersion(
String dependencyManagementPluginVersion) {
this.dependencyManagementPluginVersion = dependencyManagementPluginVersion;
}
}
public static class Kotlin {
/**
* Kotlin version to use.
*/
private String version;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
public static class Maven {
/**
* Custom parent pom to use for generated projects.
*/
private final ParentPom parent = new ParentPom();
public ParentPom getParent() {
return parent;
}
private void merge(Maven other) {
parent.groupId = other.parent.groupId;
parent.artifactId = other.parent.artifactId;
parent.version = other.parent.version;
parent.includeSpringBootBom = other.parent.includeSpringBootBom;
}
/**
* Resolve the parent pom to use. If no custom parent pom is set, the standard
* spring boot parent pom with the specified {@code bootVersion} is used.
*/
public ParentPom resolveParentPom(String bootVersion) {
return StringUtils.hasText(parent.groupId) ? parent
: new ParentPom("org.springframework.boot",
"spring-boot-starter-parent", bootVersion);
}
public static class ParentPom {
/**
* Parent pom groupId.
*/
private String groupId;
/**
* Parent pom artifactId.
*/
private String artifactId;
/**
* Parent pom version.
*/
private String version;
/**
* Add the "spring-boot-dependencies" BOM to the project.
*/
private boolean includeSpringBootBom;
public ParentPom(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}
public ParentPom() {
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getArtifactId() {
return artifactId;
}
public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public boolean isIncludeSpringBootBom() {
return includeSpringBootBom;
}
public void setIncludeSpringBootBom(boolean includeSpringBootBom) {
this.includeSpringBootBom = includeSpringBootBom;
}
public void validate() {
if (!((!StringUtils.hasText(groupId)
&& !StringUtils.hasText(artifactId)
&& !StringUtils.hasText(version))
|| (StringUtils.hasText(groupId)
&& StringUtils.hasText(artifactId)
&& StringUtils.hasText(version)))) {
throw new InvalidInitializrMetadataException("Custom maven pom "
+ "requires groupId, artifactId and version");
}
}
}
}
}
}