Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.camel.springboot.maven.SpringBootStarterMojo Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.camel.springboot.maven;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import freemarker.cache.URLTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.io.IOUtils;
import org.apache.maven.ProjectDependenciesResolver;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuilder;
import org.apache.maven.project.ProjectBuildingResult;
/**
* Generate Spring Boot starter for the component
*/
@Mojo(name = "prepare-spring-boot-starter", threadSafe = true,
requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME,
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME,
defaultPhase = LifecyclePhase.GENERATE_RESOURCES)
public class SpringBootStarterMojo extends AbstractSpringBootGenerator {
private static final String GENERATED_SECTION_START = "START OF GENERATED CODE";
private static final String GENERATED_SECTION_START_COMMENT = "";
private static final String GENERATED_SECTION_END = "END OF GENERATED CODE";
private static final String GENERATED_SECTION_END_COMMENT = "";
/**
* The maven session.
*/
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession session;
/**
* The project directory
*/
@Parameter(defaultValue = "${basedir}")
protected File baseDir;
@Component
private ProjectDependenciesResolver projectDependenciesResolver;
@Component
private ProjectBuilder projectBuilder;
@Override
protected void executeAll() throws MojoExecutionException, MojoFailureException {
try {
// create the starter directory
File starterDir = baseDir;
// create the base pom.xml
Document pom = createBasePom();
// Apply changes to the starter pom
fixExcludedDependencies(pom);
fixAdditionalDependencies(pom);
fixAdditionalRepositories(pom);
// Write the starter pom
File pomFile = new File(starterDir, "pom.xml");
writeXmlFormatted(pom, pomFile);
// write LICENSE, USAGE and spring.provides files
writeStaticFiles();
writeSpringProvides();
} catch (Exception e) {
throw new MojoFailureException("Unable to create starter", e);
}
}
private void fixAdditionalDependencies(Document pom) throws Exception {
Properties properties = new Properties();
properties.load(getClass().getResourceAsStream("/spring-boot-fix-dependencies.properties"));
Set deps = new TreeSet<>();
deps.addAll(csvToSet(properties.getProperty(getMainDepArtifactId())));
Set globalProps = csvToSet(properties.getProperty("global"));
boolean inGlobal = false;
for (String gp : globalProps) {
String camelGav = getMainDepGroupId() + ":" + getMainDepArtifactId();
String camelSpringBootGav = project.getGroupId() + ":" + project.getArtifactId();
if (gp.equals(camelGav) || gp.equals(camelSpringBootGav)) {
inGlobal = true;
break;
}
}
if (!inGlobal) {
// add global properties for all modules not in global properties
deps.addAll(globalProps);
}
if (deps.size() > 0) {
getLog().debug("The following dependencies will be added to the starter: " + deps);
XPath xpath = XPathFactory.newInstance().newXPath();
Node dependencies = ((NodeList) xpath.compile("/project/dependencies").evaluate(pom, XPathConstants.NODESET)).item(0);
if (deps.size() > 0) {
dependencies.appendChild(pom.createComment(GENERATED_SECTION_START));
for (String dep : deps) {
Element dependency = pom.createElement("dependency");
dependencies.appendChild(dependency);
String[] comps = dep.split("\\:");
String groupIdStr = comps[0];
String artifactIdStr = comps[1];
String versionStr = comps.length > 2 ? comps[2] : null;
Element groupId = pom.createElement("groupId");
groupId.setTextContent(groupIdStr);
dependency.appendChild(groupId);
Element artifactId = pom.createElement("artifactId");
artifactId.setTextContent(artifactIdStr);
dependency.appendChild(artifactId);
if (versionStr != null) {
Element version = pom.createElement("version");
version.setTextContent(versionStr);
dependency.appendChild(version);
}
}
dependencies.appendChild(pom.createComment(GENERATED_SECTION_END));
}
}
}
private void fixAdditionalRepositories(Document pom) throws Exception {
if (project.getFile() != null) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = dbf.newDocumentBuilder();
Document originalPom = builder.parse(project.getFile());
XPath xpath = XPathFactory.newInstance().newXPath();
Node repositories = (Node) xpath.compile("/project/repositories").evaluate(originalPom, XPathConstants.NODE);
if (repositories != null) {
pom.getDocumentElement().appendChild(pom.createComment(GENERATED_SECTION_START));
pom.getDocumentElement().appendChild(pom.importNode(repositories, true));
pom.getDocumentElement().appendChild(pom.createComment(GENERATED_SECTION_END));
}
} else {
getLog().warn("Cannot access the project pom file to retrieve repositories");
}
}
private Set csvToSet(String csv) {
if (csv == null || csv.trim().length() == 0) {
return new TreeSet<>();
}
Set set = new TreeSet<>();
for (String s : csv.split(",")) {
set.add(s.trim());
}
return set;
}
private void fixExcludedDependencies(Document pom) throws Exception {
Set loggingImpl = new HashSet<>();
loggingImpl.add("commons-logging:commons-logging");
loggingImpl.add("log4j:log4j");
loggingImpl.add("log4j:apache-log4j-extras");
// removing also the default implementation
loggingImpl.add("ch.qos.logback:logback-core");
loggingImpl.add("ch.qos.logback:logback-classic");
loggingImpl.add("org.apache.logging.log4j:log4j");
loggingImpl.add("org.apache.logging.log4j:log4j-jcl");
loggingImpl.add("org.apache.logging.log4j:log4j-core");
loggingImpl.add("org.apache.logging.log4j:log4j-slf4j-impl");
loggingImpl.add("org.slf4j:slf4j-jcl");
loggingImpl.add("org.slf4j:slf4j-jdk14");
loggingImpl.add("org.slf4j:slf4j-log4j12");
loggingImpl.add("org.slf4j:slf4j-log4j13");
loggingImpl.add("org.slf4j:slf4j-nop");
loggingImpl.add("org.slf4j:slf4j-simple");
// excluded dependencies
Set configExclusions = new HashSet<>();
Properties properties = new Properties();
properties.load(getClass().getResourceAsStream("/spring-boot-fix-dependencies.properties"));
String artExcl = properties.getProperty("exclude_" + getMainDepArtifactId());
getLog().debug("Configured exclusions: " + artExcl);
if (artExcl != null && artExcl.trim().length() > 0) {
for (String dep : artExcl.split(",")) {
getLog().debug("Adding configured exclusion: " + dep);
configExclusions.add(dep);
}
}
Set libsToRemove = new TreeSet<>();
libsToRemove.addAll(loggingImpl);
libsToRemove.addAll(configExclusions);
libsToRemove = filterIncludedArtifacts(libsToRemove);
if (libsToRemove.size() > 0) {
getLog().info("Spring-Boot-Starter: the following dependencies will be removed from the starter: " + libsToRemove);
XPath xpath = XPathFactory.newInstance().newXPath();
Node dependency = ((NodeList) xpath.compile("/project/dependencies/dependency[artifactId/text() = '" + getMainDepArtifactId() + "']").evaluate(pom, XPathConstants.NODESET)).item(0);
Element exclusions = pom.createElement("exclusions");
dependency.appendChild(pom.createComment(GENERATED_SECTION_START));
dependency.appendChild(exclusions);
dependency.appendChild(pom.createComment(GENERATED_SECTION_END));
for (String lib : libsToRemove) {
String groupIdStr = lib.split("\\:")[0];
String artifactIdStr = lib.split("\\:")[1];
Element exclusion = pom.createElement("exclusion");
Element groupId = pom.createElement("groupId");
groupId.setTextContent(groupIdStr);
exclusion.appendChild(groupId);
Element artifactId = pom.createElement("artifactId");
artifactId.setTextContent(artifactIdStr);
exclusion.appendChild(artifactId);
exclusions.appendChild(exclusion);
}
}
}
private Set filterIncludedArtifacts(Set artifacts) {
Set dependencies;
try {
Artifact artifact = project.getArtifactMap().get(getMainDepGroupId() + ":" + getMainDepArtifactId());
ProjectBuildingResult result = projectBuilder.build(artifact, project.getProjectBuildingRequest());
MavenProject prj = result.getProject();
prj.setRemoteArtifactRepositories(project.getRemoteArtifactRepositories());
dependencies = projectDependenciesResolver.resolve(prj, Collections.singleton(Artifact.SCOPE_COMPILE), session);
} catch (Exception e) {
throw new RuntimeException("Unable to build project dependency tree", e);
}
Set included = new TreeSet<>();
dependencies.stream()
.filter(a -> !Artifact.SCOPE_TEST.equals(a.getScope()))
.map(a -> a.getGroupId() + ":" + a.getArtifactId())
.forEach(included::add);
included.retainAll(artifacts);
return included;
}
private Document createBasePom() {
try {
File pomFile = new File(baseDir, "pom.xml");
if (pomFile.exists()) {
try (InputStream in = new FileInputStream(pomFile)) {
String content = IOUtils.toString(in, StandardCharsets.UTF_8);
boolean editablePom = content.contains(GENERATED_SECTION_START_COMMENT);
if (editablePom) {
content = removeGeneratedSections(content, 10);
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document pom;
try (InputStream contentIn = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
pom = builder.parse(contentIn);
}
getLog().debug("Reusing the existing pom.xml for the starter");
return pom;
}
}
}
} catch (Exception e) {
getLog().warn("Cannot use the existing pom.xml file", e);
}
return null;
}
private String removeGeneratedSections(String pom, int maxNumber) {
if (maxNumber > 0 && pom.contains(GENERATED_SECTION_START_COMMENT)) {
int start = pom.indexOf(GENERATED_SECTION_START_COMMENT);
int end = pom.indexOf(GENERATED_SECTION_END_COMMENT);
if (end <= start) {
throw new IllegalArgumentException("Generated sections inside the xml document are not well-formed");
}
String newPom = pom.substring(0, start) + pom.substring(end + GENERATED_SECTION_END_COMMENT.length());
return removeGeneratedSections(newPom, maxNumber - 1);
}
return pom;
}
private Document createBasePomFromScratch() throws Exception {
getLog().info("Creating a new pom.xml for the starter from scratch");
Template pomTemplate = getTemplate("spring-boot-starter-template-pom.template");
Map props = new HashMap<>();
props.put("version", project.getVersion());
props.put("componentId", getComponentId());
props.put("componentName", project.getName());
props.put("componentDescription", project.getDescription());
StringWriter sw = new StringWriter();
pomTemplate.process(props, sw);
String xml = sw.toString();
ByteArrayInputStream bin = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document pom = builder.parse(bin);
return pom;
}
private void writeStaticFiles() throws IOException {
String notice;
String license;
try (InputStream isNotice = getClass().getResourceAsStream("/spring-boot-starter-NOTICE.txt");
InputStream isLicense = getClass().getResourceAsStream("/spring-boot-starter-LICENSE.txt")) {
notice = IOUtils.toString(isNotice, StandardCharsets.UTF_8);
license = IOUtils.toString(isLicense, StandardCharsets.UTF_8);
}
writeIfChanged(notice, new File(baseDir, "src/main/resources/META-INF/NOTICE.txt"));
writeIfChanged(license, new File(baseDir, "src/main/resources/META-INF/LICENSE.txt"));
}
private void writeSpringProvides() throws IOException, TemplateException {
Template fileTemplate = getTemplate("spring-boot-starter-template-spring.provides");
Map props = new HashMap<>();
props.put("artifactId", getMainDepArtifactId());
File outDir = new File(baseDir, "src/main/resources/META-INF");
outDir.mkdirs();
File outFile = new File(outDir, "spring.provides");
StringWriter sw = new StringWriter();
fileTemplate.process(props, sw);
sw.close();
writeIfChanged(sw.toString(), outFile);
}
private Template getTemplate(String name) throws IOException {
Configuration cfg = new Configuration(Configuration.getVersion());
cfg.setTemplateLoader(new URLTemplateLoader() {
@Override
protected URL getURL(String name) {
return SpringBootStarterMojo.class.getResource("/" + name);
}
});
cfg.setDefaultEncoding("UTF-8");
Template template = cfg.getTemplate(name);
return template;
}
private String getComponentId() {
String componentName = getMainDepArtifactId();
String componentId = componentName.replace("camel-", "");
return componentId;
}
private void writeXmlFormatted(Document pom, File destination) throws Exception {
XPathExpression xpath = XPathFactory.newInstance().newXPath().compile("//text()[normalize-space(.) = '']");
NodeList emptyNodes = (NodeList) xpath.evaluate(pom, XPathConstants.NODESET);
// Remove empty text nodes
for (int i = 0; i < emptyNodes.getLength(); i++) {
Node emptyNode = emptyNodes.item(i);
emptyNode.getParentNode().removeChild(emptyNode);
}
pom.setXmlStandalone(true);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
DOMSource source = new DOMSource(pom);
String content;
try (StringWriter out = new StringWriter()) {
StreamResult result = new StreamResult(out);
transformer.transform(source, result);
content = out.toString();
}
// Fix header formatting problem
content = content.replaceFirst("-->", "-->\n").replaceFirst("\\?>