Exclude support in the maven plugin

This commit provides several options to exclude one or more
dependencies:

* excludes allows to specify an arbitrary number of exclude sub
  element defining the groupId and artifactId of the dependency
  to exclude
* excludedGroupIds defines the comma separated list of groupIds
  to exclude
* excludeArtifactIds defines the comma separated list of artifactIds
  to exclude

While any artifact can be excluded, this is designed to exclude
provided-scoped dependencies that should not be bundled in the
executable jar/war.

The outcome of java -jar myapp.jar should be consistent with the run
goal: these exclusions are therefore applied to the classpath that
the run goal computes to launch the application.

This commit also adds some integration tests and updates the
plugin's documentation

Fixes gh-649, gh-650 and gh-674
This commit is contained in:
Stephane Nicoll 2014-05-06 14:54:09 +02:00
parent 28bd87cbae
commit 15501eaafb
32 changed files with 1282 additions and 54 deletions

View File

@ -98,6 +98,11 @@
<artifactId>maven-settings-builder</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-common-artifact-filters</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>

View File

@ -126,6 +126,10 @@
<groupId>org.apache.maven</groupId>
<artifactId>maven-settings</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-common-artifact-filters</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-archiver</artifactId>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-exclude-artifact</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<excludeArtifactIds>servlet-api</excludeArtifactIds>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,8 @@
package org.test;
public class SampleApplication {
public static void main(String[] args) {
}
}

View File

@ -0,0 +1,11 @@
import java.io.*;
import org.springframework.boot.maven.*;
File f = new File( basedir, "target/jar-exclude-artifact-0.0.1.BUILD-SNAPSHOT.jar");
new Verify.JarArchiveVerification(f, Verify.SAMPLE_APP) {
@Override
protected void verifyZipEntries(Verify.ArchiveVerifier verifier) throws Exception {
super.verifyZipEntries(verifier)
verifier.assertHasNoEntryNameStartingWith("lib/servlet-api-2.5.jar")
}
}.verify();

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2012-2014 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-exclude-entry</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<excludes>
<exclude>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,8 @@
package org.test;
public class SampleApplication {
public static void main(String[] args) {
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2012-2014 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.
*/
import java.io.*;
import org.springframework.boot.maven.*;
File f = new File( basedir, "target/jar-exclude-entry-0.0.1.BUILD-SNAPSHOT.jar");
new Verify.JarArchiveVerification(f, Verify.SAMPLE_APP) {
@Override
protected void verifyZipEntries(Verify.ArchiveVerifier verifier) throws Exception {
super.verifyZipEntries(verifier)
verifier.assertHasNoEntryNameStartingWith("lib/servlet-api-2.5.jar")
}
}.verify();

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2012-2014 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-exclude-group</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<excludeGroupIds>log4j</excludeGroupIds>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,8 @@
package org.test;
public class SampleApplication {
public static void main(String[] args) {
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2012-2014 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.
*/
import java.io.*;
import org.springframework.boot.maven.*;
File f = new File( basedir, "target/jar-exclude-group-0.0.1.BUILD-SNAPSHOT.jar");
new Verify.JarArchiveVerification(f, Verify.SAMPLE_APP) {
@Override
protected void verifyZipEntries(Verify.ArchiveVerifier verifier) throws Exception {
super.verifyZipEntries(verifier)
verifier.assertHasNoEntryNameStartingWith("lib/log4j-1.2.17.jar")
}
}.verify();

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>jar-test-scope</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<excludeArtifactIds>servlet-api</excludeArtifactIds>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Not-Used>Foo</Not-Used>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,8 @@
package org.test;
public class SampleApplication {
public static void main(String[] args) {
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2012-2014 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.
*/
import java.io.*;
import org.springframework.boot.maven.*;
File f = new File( basedir, "target/jar-test-scope-0.0.1.BUILD-SNAPSHOT.jar");
new Verify.JarArchiveVerification(f, Verify.SAMPLE_APP) {
@Override
protected void verifyZipEntries(Verify.ArchiveVerifier verifier) throws Exception {
super.verifyZipEntries(verifier)
verifier.assertHasNoEntryNameStartingWith("lib/log4j-1.2.17.jar")
}
}.verify();

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework.boot.maven.it</groupId>
<artifactId>run-exclude</artifactId>
<version>0.0.1.BUILD-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>@project.groupId@</groupId>
<artifactId>@project.artifactId@</artifactId>
<version>@project.version@</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<excludes>
<exclude>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclude>
</excludes>
<excludeGroupIds>javax.servlet</excludeGroupIds>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,27 @@
package org.test;
public class SampleApplication {
public static void main(String[] args) {
if (isClassPresent("org.apache.log4j.Logger")) {
throw new IllegalStateException("Log4j was present and should not");
}
if (isClassPresent("javax.servlet.Servlet")) {
throw new IllegalStateException("servlet-api was present and should not");
}
System.out.println("I haz been run");
}
private static boolean isClassPresent(String className) {
try {
ClassLoader classLoader = SampleApplication.class.getClassLoader();
classLoader.loadClass(className);
return true;
}
catch (ClassNotFoundException e) {
return false;
}
}
}

View File

@ -0,0 +1,3 @@
def file = new File(basedir, "build.log")
return file.text.contains("I haz been run")

View File

@ -16,7 +16,6 @@
<version>@project.version@</version>
<executions>
<execution>
<id></id>
<phase>package</phase>
<goals>
<goal>run</goal>

View File

@ -0,0 +1,111 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.maven;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
import org.apache.maven.shared.artifact.filter.collection.ArtifactIdFilter;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.apache.maven.shared.artifact.filter.collection.GroupIdFilter;
/**
* A base mojo filtering the dependencies of the project.
*
* @author Stephane Nicoll
* @since 1.1
*/
public abstract class AbstractDependencyFilterMojo extends AbstractMojo {
/**
* Collection of artifact definitions to exclude. The {@link Exclude}
* element defines a {@code groupId} and {@code artifactId} mandatory
* properties and an optional {@code classifier} property.
* @since 1.1
*/
@Parameter
private List<Exclude> excludes;
/**
* Comma separated list of groupId names to exclude.
* @since 1.1
*/
@Parameter(property = "excludeGroupIds", defaultValue = "")
protected String excludeGroupIds;
/**
* Comma separated list of artifact names to exclude.
* @since 1.1
*/
@Parameter(property = "excludeArtifactIds", defaultValue = "")
protected String excludeArtifactIds;
protected void setExcludes(List<Exclude> excludes) {
this.excludes = excludes;
}
protected void setExcludeGroupIds(String excludeGroupIds) {
this.excludeGroupIds = excludeGroupIds;
}
protected void setExcludeArtifactIds(String excludeArtifactIds) {
this.excludeArtifactIds = excludeArtifactIds;
}
@SuppressWarnings("unchecked")
protected Set<Artifact> filterDependencies(Set<Artifact> dependencies, FilterArtifacts filters)
throws MojoExecutionException {
try {
return filters.filter(dependencies);
}
catch (ArtifactFilterException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
protected void initializeFilterArtifacts(FilterArtifacts filters) {
filters.addFilter(new ArtifactIdFilter("", cleanConfigItem(this.excludeArtifactIds)));
filters.addFilter(new GroupIdFilter("", cleanConfigItem(this.excludeGroupIds)));
if (this.excludes != null) {
filters.addFilter(new ExcludeFilter(this.excludes));
}
}
static String cleanConfigItem(String content) {
if (content == null || content.trim().isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
StringTokenizer st = new StringTokenizer(content, ",");
while (st.hasMoreElements()) {
String t = st.nextToken();
sb.append(t.trim());
if (st.hasMoreElements()) {
sb.append(",");
}
}
return sb.toString();
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.maven;
import org.apache.maven.plugins.annotations.Parameter;
/**
* A model for a dependency to exclude.
*
* @author Stephane Nicoll
* @since 1.1
*/
public class Exclude {
/**
* The groupId of the artifact to exclude.
*/
@Parameter(required = true)
private String groupId;
/**
* The artifactId of the artifact to exclude.
*/
@Parameter(required = true)
private String artifactId;
/**
* The classifier of the artifact to exclude
*/
@Parameter
private String classifier;
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 getClassifier() {
return classifier;
}
public void setClassifier(String classifier) {
this.classifier = classifier;
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.maven;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactsFilter;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
/**
* An {@link org.apache.maven.shared.artifact.filter.collection.ArtifactsFilter
* ArtifactsFilter} that filters out any artifact matching a configurable list
* of {@link Exclude} instances.
*
* @author Stephane Nicoll
* @since 1.1
*/
public class ExcludeFilter extends AbstractArtifactsFilter{
private final List<Exclude> excludes;
/**
* Create a new instance with the list of {@link Exclude}
* instance(s) to use.
*/
public ExcludeFilter(List<Exclude> excludes) {
this.excludes = excludes;
}
@Override
public Set filter(Set artifacts) throws ArtifactFilterException {
Set<Artifact> result = new HashSet<Artifact>();
for (Object a : artifacts) {
Artifact artifact = (Artifact) a;
if (!matchExclude(artifact)) {
result.add(artifact);
}
}
return result;
}
/**
* Check if the specified {@link Artifact} matches one of the
* known excludes. Returns {@code true} if it should be excluded
*/
private boolean matchExclude(Artifact artifact) {
for (Exclude exclude : excludes) {
if (match(artifact, exclude)) {
return true;
}
}
return false;
}
/**
* Check if the specified {@link Artifact} matches the specified
* {@link Exclude}. Returns {@code true} if it should be excluded
*/
private boolean match(Artifact artifact, Exclude exclude) {
if (!exclude.getGroupId().equals(artifact.getGroupId())) {
return false;
}
if (!exclude.getArtifactId().equals(artifact.getArtifactId())) {
return false;
}
return exclude.getClassifier() == null ||
artifact.getClassifier() != null && exclude.getClassifier().equals(artifact.getClassifier());
}
}

View File

@ -18,9 +18,11 @@ package org.springframework.boot.maven;
import java.io.File;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.jar.JarFile;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
@ -31,6 +33,8 @@ 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.MavenProjectHelper;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.springframework.boot.loader.tools.Layout;
import org.springframework.boot.loader.tools.Layouts;
import org.springframework.boot.loader.tools.Libraries;
@ -46,7 +50,7 @@ import org.springframework.boot.loader.tools.Repackager;
* @author Stephane Nicoll
*/
@Mojo(name = "repackage", defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true, threadSafe = true, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME)
public class RepackageMojo extends AbstractMojo {
public class RepackageMojo extends AbstractDependencyFilterMojo {
private static final long FIND_WARNING_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
@ -134,7 +138,12 @@ public class RepackageMojo extends AbstractMojo {
getLog().info("Layout: " + this.layout);
repackager.setLayout(this.layout.layout());
}
Libraries libraries = new ArtifactsLibraries(this.project.getArtifacts());
FilterArtifacts filters = new FilterArtifacts();
initializeFilterArtifacts(filters);
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), filters);
Libraries libraries = new ArtifactsLibraries(artifacts);
try {
repackager.repackage(target, libraries);
}

View File

@ -24,10 +24,10 @@ import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Execute;
@ -36,6 +36,9 @@ 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.shared.artifact.filter.collection.AbstractArtifactFeatureFilter;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.springframework.boot.loader.tools.FileUtils;
import org.springframework.boot.loader.tools.JavaExecutable;
import org.springframework.boot.loader.tools.MainClassFinder;
@ -43,13 +46,13 @@ import org.springframework.boot.loader.tools.RunProcess;
/**
* Run an executable archive application.
*
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
@Mojo(name = "run", requiresProject = true, defaultPhase = LifecyclePhase.VALIDATE, requiresDependencyResolution = ResolutionScope.TEST)
@Execute(phase = LifecyclePhase.TEST_COMPILE)
public class RunMojo extends AbstractMojo {
public class RunMojo extends AbstractDependencyFilterMojo {
private static final String SPRING_LOADED_AGENT_CLASSNAME = "org.springsource.loaded.agent.SpringLoadedAgent";
@ -253,14 +256,27 @@ public class RunMojo extends AbstractMojo {
urls.add(this.classesDirectory.toURI().toURL());
}
private void addDependencies(List<URL> urls) throws MalformedURLException {
for (Artifact artifact : this.project.getArtifacts()) {
private void addDependencies(List<URL> urls) throws MalformedURLException, MojoExecutionException {
FilterArtifacts filters = new FilterArtifacts();
filters.addFilter(new TestArtifactFilter());
initializeFilterArtifacts(filters);
Set<Artifact> artifacts = filterDependencies(this.project.getArtifacts(), filters);
for (Artifact artifact : artifacts) {
if (artifact.getFile() != null) {
if (!Artifact.SCOPE_TEST.equals(artifact.getScope())) {
urls.add(artifact.getFile().toURI().toURL());
}
urls.add(artifact.getFile().toURI().toURL());
}
}
}
private static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
public TestArtifactFilter() {
super("", Artifact.SCOPE_TEST);
}
protected String getArtifactFeature(Artifact artifact) {
return artifact.getScope();
}
}
}

View File

@ -0,0 +1,117 @@
-----
Exclude a dependency
-----
Stephane Nicoll
-----
2014-05-06
-----
By default, both the <<<repackage>>> and the <<<run>>> goals will include any <<<provided>>>
dependencies that are defined in the project. A boot-based project should consider
<<<provided>>> dependencies as <<container>> dependencies that are required to run
the application.
Some of these dependencies may not be required at all and should be excluded from the
executable jar. For consistency, they should not be present either when running the
application.
There are tree ways one can exclude a dependency from being packaged/used at runtime
* Exclude a specific artifact identified by <<<groupId>>> and <<<artifactId>>>
(optionnaly with a <<<classifier>>> if needed)
* Exclude any artifact matching a given <<<artifactId>>>
* Exclude any artifact belonging to a given <<<groupId>>>
[]
The following excludes <<<com.foo:bar>>> (and only that artifact)
---
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<configuration>
<excludes>
<exclude>
<groupId>com.foo</groupId>
<artifactId>bar</artifactId>
</exclude>
</excludes>
</configuration>
...
</plugin>
...
</plugins>
...
</build>
...
</project>
---
This example excludes every artifacts having the <<<my-lib>>> or <<<another-lib>>>
artifact identifiers
---
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<configuration>
<excludes>
<excludeArtifactIds>my-lib,another-lib</excludeArtifactIds>
</excludes>
</configuration>
...
</plugin>
...
</plugins>
...
</build>
...
</project>
---
Finally this example excludes any artifact belonging to the <<<com.foo>>> group
---
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>${project.artifactId}</artifactId>
<version>${project.version}</version>
<configuration>
<excludes>
<excludeGroupIds>com.foo</excludeGroupIds>
</excludes>
</configuration>
...
</plugin>
...
</plugins>
...
</build>
...
</project>
---

View File

@ -38,6 +38,8 @@ Spring Boot Maven Plugin
* {{{./examples/repackage-classifier.html}Custom repackage classifier}}
* {{{./examples/exclude-dependency.html}Exclude a dependency}}
[]

View File

@ -4,7 +4,7 @@
-----
Stephane Nicoll
-----
2014-05-02
2014-05-06
-----
Usage
@ -40,9 +40,13 @@ Usage
</build>
---
The example above repackages a jar or war that is built during the package phase of the Maven lifecycle. The
original (i.e. non exectuable) artifact is renamed to <<<.original>>> by default but it is also possible to
keep the original artifact using a custom classifier.
The example above repackages a jar or war that is built during the package phase of the Maven lifecycle,
including any <<<provided>>> dependencies that are defined in the project. If some of these dependencies
need to be excluded, you can use one of the exclude options,
see {{{./examples/exclude-dependency.html}Exclude a dependency}} for more details.
The original (i.e. non exectuable) artifact is renamed to <<<.original>>> by default but it is also
possible to keep the original artifact using a custom classifier.
The plugin rewrites your manifest, and in particular it manages the <<Main-Class>> and <<Start-Class>>
entries, so if the defaults don't work you have to configure those there (not in the jar plugin). The
@ -82,6 +86,8 @@ Usage
* {{{./examples/repackage-classifier.html}Custom repackage classifier}}
* {{{./examples/exclude-dependency.html}Exclude a dependency}}
[]
* Running the application
@ -120,4 +126,9 @@ mvn spring-boot:run
</plugins>
...
</build>
---
---
In order to be consistent with the <<<repackage>>> goal, the <<<run>>> goal builds the classpath
in such a way that any dependency that is excluded in the plugin's configuration gets excluded
from the classpath as well. See {{{./examples/exclude-dependency.html}Exclude a dependency}} for
more details.

View File

@ -8,6 +8,7 @@
</menu>
<menu name="Examples">
<item name="Custom repackage classifier" href="examples/repackage-classifier.html"/>
<item name="Exclude a dependency" href="examples/exclude-dependency.html"/>
</menu>
<menu ref="reports"/>
</body>

View File

@ -0,0 +1,85 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.maven;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.junit.Test;
/**
*
* @author Stephane Nicoll
*/
public class DependencyFilterMojoTests {
@Test
public void filterDependencies() throws MojoExecutionException {
TestableDependencyFilterMojo mojo = new TestableDependencyFilterMojo(
Collections.<Exclude>emptyList(), "com.foo", "exclude-id");
Artifact artifact = createArtifact("com.bar", "one");
Set<Artifact> artifacts = mojo.filterDependencies(createArtifact("com.foo", "one"),
createArtifact("com.foo", "two"),
createArtifact("com.bar", "exclude-id"),
artifact);
assertEquals("wrong filtering of artifacts", 1, artifacts.size());
assertSame("Wrong filtered artifact", artifact, artifacts.iterator().next());
}
private Artifact createArtifact(String groupId, String artifactId) {
Artifact a = mock(Artifact.class);
given(a.getGroupId()).willReturn(groupId);
given(a.getArtifactId()).willReturn(artifactId);
return a;
}
private static class TestableDependencyFilterMojo extends AbstractDependencyFilterMojo {
private TestableDependencyFilterMojo(List<Exclude> excludes, String excludeGroupIds, String excludeArtifactIds) {
setExcludes(excludes);
setExcludeGroupIds(excludeGroupIds);
setExcludeArtifactIds(excludeArtifactIds);
}
public Set<Artifact> filterDependencies(Artifact... artifacts) throws MojoExecutionException {
Set<Artifact> input = new HashSet<Artifact>(Arrays.asList(artifacts));
FilterArtifacts filters = new FilterArtifacts();
initializeFilterArtifacts(filters);
return filterDependencies(input, filters);
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
}
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.maven;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
import static org.mockito.Mockito.mock;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.shared.artifact.filter.collection.ArtifactFilterException;
import org.junit.Test;
/**
*
* @author Stephane Nicoll
*/
public class ExcludeFilterTests {
@Test
public void excludeSimple() throws ArtifactFilterException {
ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar")));
Set result = filter.filter(Collections.singleton(createArtifact("com.foo", "bar")));
assertEquals("Should have been filtered", 0, result.size());
}
@Test
public void excludeGroupIdNoMatch() throws ArtifactFilterException {
ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar")));
Artifact artifact = createArtifact("com.baz", "bar");
Set result = filter.filter(Collections.singleton(artifact));
assertEquals("Should not have been filtered", 1, result.size());
assertSame(artifact, result.iterator().next());
}
@Test
public void excludeArtifactIdNoMatch() throws ArtifactFilterException {
ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar")));
Artifact artifact = createArtifact("com.foo", "biz");
Set result = filter.filter(Collections.singleton(artifact));
assertEquals("Should not have been filtered", 1, result.size());
assertSame(artifact, result.iterator().next());
}
@Test
public void excludeClassifier() throws ArtifactFilterException {
ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar", "jdk5")));
Set result = filter.filter(Collections.singleton(createArtifact("com.foo", "bar", "jdk5")));
assertEquals("Should have been filtered", 0, result.size());
}
@Test
public void excludeClassifierNoTargetClassifier() throws ArtifactFilterException {
ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar", "jdk5")));
Artifact artifact = createArtifact("com.foo", "bar");
Set result = filter.filter(Collections.singleton(artifact));
assertEquals("Should not have been filtered", 1, result.size());
assertSame(artifact, result.iterator().next());
}
@Test
public void excludeClassifierNoMatch() throws ArtifactFilterException {
ExcludeFilter filter = new ExcludeFilter(Arrays.asList(createExclude("com.foo", "bar", "jdk5")));
Artifact artifact = createArtifact("com.foo", "bar", "jdk6");
Set result = filter.filter(Collections.singleton(artifact));
assertEquals("Should not have been filtered", 1, result.size());
assertSame(artifact, result.iterator().next());
}
@Test
public void excludeMulti() throws ArtifactFilterException {
ExcludeFilter filter = new ExcludeFilter(Arrays.asList(
createExclude("com.foo", "bar"),
createExclude("com.foo", "bar2"),
createExclude("org.acme", "app")));
Set<Artifact> artifacts = new HashSet<Artifact>();
artifacts.add(createArtifact("com.foo", "bar"));
artifacts.add(createArtifact("com.foo", "bar"));
Artifact anotherAcme = createArtifact("org.acme", "another-app");
artifacts.add(anotherAcme);
Set result = filter.filter(artifacts);
assertEquals("Two dependencies should have been filtered", 1, result.size());
assertSame(anotherAcme, result.iterator().next());
}
private Exclude createExclude(String groupId, String artifactId, String classifier) {
Exclude e = new Exclude();
e.setGroupId(groupId);
e.setArtifactId(artifactId);
if (classifier != null) {
e.setClassifier(classifier);
}
return e;
}
private Exclude createExclude(String groupId, String artifactId) {
return createExclude(groupId, artifactId, null);
}
private Artifact createArtifact(String groupId, String artifactId, String classifier) {
Artifact a = mock(Artifact.class);
given(a.getGroupId()).willReturn(groupId);
given(a.getArtifactId()).willReturn(artifactId);
given(a.getClassifier()).willReturn(classifier);
return a;
}
private Artifact createArtifact(String groupId, String artifactId) {
return createArtifact(groupId, artifactId, null);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2012-2014 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 org.springframework.boot.maven;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
/**
*
* @author Stephane Nicoll
*/
public class RepackageMojoTests {
@Test
public void cleanConfigItemWithSpaces() {
assertEquals("foo,bar,biz", RepackageMojo.cleanConfigItem("foo, bar , biz"));
}
@Test
public void cleanNullConfigItemWith() {
assertEquals("", RepackageMojo.cleanConfigItem(null));
}
@Test
public void cleanEmptyConfigItemWith() {
assertEquals("", RepackageMojo.cleanConfigItem(""));
}
}

View File

@ -17,6 +17,8 @@
package org.springframework.boot.maven;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@ -34,8 +36,10 @@ import static org.junit.Assert.assertTrue;
*/
public class Verify {
public static final String SAMPLE_APP = "org.test.SampleApplication";
public static void verifyJar(File file) throws Exception {
new JarArchiveVerification(file, "org.test.SampleApplication").verify();
new JarArchiveVerification(file, SAMPLE_APP).verify();
}
public static void verifyJar(File file, String main) throws Exception {
@ -50,6 +54,57 @@ public class Verify {
new ZipArchiveVerification(file).verify();
}
public static class ArchiveVerifier {
private final ZipFile zipFile;
private final Map<String, ZipEntry> content;
public ArchiveVerifier(ZipFile zipFile) {
this.zipFile = zipFile;
Enumeration<? extends ZipEntry> entries = zipFile.entries();
this.content = new HashMap<String, ZipEntry>();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
this.content.put(zipEntry.getName(), zipEntry);
}
}
public void assertHasEntryNameStartingWith(String entry) {
for (String name : this.content.keySet()) {
if (name.startsWith(entry)) {
return;
}
}
throw new IllegalStateException("Expected entry starting with " + entry);
}
public void assertHasNoEntryNameStartingWith(String entry) {
for (String name : this.content.keySet()) {
if (name.startsWith(entry)) {
throw new IllegalStateException("Entry starting with "
+ entry + " should not have been found");
}
}
}
public boolean hasEntry(String entry) {
return this.content.containsKey(entry);
}
public ZipEntry getEntry(String entry) {
return this.content.get(entry);
}
public InputStream getEntryContent(String entry) throws IOException {
ZipEntry zipEntry = getEntry(entry);
if (zipEntry == null) {
throw new IllegalArgumentException("No entry with name ["+entry+"]");
}
return this.zipFile.getInputStream(zipEntry);
}
}
private static abstract class AbstractArchiveVerification {
private final File file;
@ -63,40 +118,30 @@ public class Verify {
assertTrue("Archive not a file", this.file.isFile());
ZipFile zipFile = new ZipFile(this.file);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
Map<String, ZipEntry> zipMap = new HashMap<String, ZipEntry>();
while (entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
zipMap.put(zipEntry.getName(), zipEntry);
try {
ArchiveVerifier verifier = new ArchiveVerifier(zipFile);
verifyZipEntries(verifier);
}
finally {
zipFile.close();
}
verifyZipEntries(zipFile, zipMap);
zipFile.close();
}
protected void verifyZipEntries(ZipFile zipFile, Map<String, ZipEntry> entries)
protected void verifyZipEntries(ArchiveVerifier verifier)
throws Exception {
verifyManifest(zipFile, entries.get("META-INF/MANIFEST.MF"));
verifyManifest(verifier);
}
private void verifyManifest(ZipFile zipFile, ZipEntry zipEntry) throws Exception {
Manifest manifest = new Manifest(zipFile.getInputStream(zipEntry));
private void verifyManifest(ArchiveVerifier verifier) throws Exception {
Manifest manifest = new Manifest(verifier.getEntryContent("META-INF/MANIFEST.MF"));
verifyManifest(manifest);
}
protected abstract void verifyManifest(Manifest manifest) throws Exception;
protected final void assertHasEntryNameStartingWith(
Map<String, ZipEntry> entries, String value) {
for (String name : entries.keySet()) {
if (name.startsWith(value)) {
return;
}
}
throw new IllegalStateException("Expected entry starting with " + value);
}
}
private static class JarArchiveVerification extends AbstractArchiveVerification {
public static class JarArchiveVerification extends AbstractArchiveVerification {
private final String main;
@ -106,15 +151,15 @@ public class Verify {
}
@Override
protected void verifyZipEntries(ZipFile zipFile, Map<String, ZipEntry> entries)
protected void verifyZipEntries(ArchiveVerifier verifier)
throws Exception {
super.verifyZipEntries(zipFile, entries);
assertHasEntryNameStartingWith(entries, "lib/spring-context");
assertHasEntryNameStartingWith(entries, "lib/spring-core");
assertHasEntryNameStartingWith(entries, "lib/javax.servlet-api-3.0.1.jar");
assertTrue("Unpacked launcher classes", entries.containsKey("org/"
super.verifyZipEntries(verifier);
verifier.assertHasEntryNameStartingWith("lib/spring-context");
verifier.assertHasEntryNameStartingWith("lib/spring-core");
verifier.assertHasEntryNameStartingWith("lib/javax.servlet-api-3.0.1.jar");
assertTrue("Unpacked launcher classes", verifier.hasEntry("org/"
+ "springframework/boot/loader/JarLauncher.class"));
assertTrue("Own classes", entries.containsKey("org/"
assertTrue("Own classes", verifier.hasEntry("org/"
+ "test/SampleApplication.class"));
}
@ -127,25 +172,25 @@ public class Verify {
}
}
private static class WarArchiveVerification extends AbstractArchiveVerification {
public static class WarArchiveVerification extends AbstractArchiveVerification {
public WarArchiveVerification(File file) {
super(file);
}
@Override
protected void verifyZipEntries(ZipFile zipFile, Map<String, ZipEntry> entries)
protected void verifyZipEntries(ArchiveVerifier verifier)
throws Exception {
super.verifyZipEntries(zipFile, entries);
assertHasEntryNameStartingWith(entries, "WEB-INF/lib/spring-context");
assertHasEntryNameStartingWith(entries, "WEB-INF/lib/spring-core");
assertHasEntryNameStartingWith(entries,
super.verifyZipEntries(verifier);
verifier.assertHasEntryNameStartingWith("WEB-INF/lib/spring-context");
verifier.assertHasEntryNameStartingWith("WEB-INF/lib/spring-core");
verifier.assertHasEntryNameStartingWith(
"WEB-INF/lib-provided/javax.servlet-api-3.0.1.jar");
assertTrue("Unpacked launcher classes", entries.containsKey("org/"
assertTrue("Unpacked launcher classes", verifier.hasEntry("org/"
+ "springframework/boot/loader/JarLauncher.class"));
assertTrue("Own classes", entries.containsKey("WEB-INF/classes/org/"
assertTrue("Own classes", verifier.hasEntry("WEB-INF/classes/org/"
+ "test/SampleApplication.class"));
assertTrue("Web content", entries.containsKey("index.html"));
assertTrue("Web content", verifier.hasEntry("index.html"));
}
@Override