Skip to content

Commit ffd6380

Browse files
authored
Merge pull request #1 from netgrif/feature/manifest-info
Add support for custom JAR manifests and Spring auto-config
2 parents 81901ef + 9bc02f9 commit ffd6380

File tree

3 files changed

+233
-23
lines changed

3 files changed

+233
-23
lines changed

pom.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.netgrif</groupId>
88
<artifactId>nae-module-maven-plugin</artifactId>
9-
<version>1.0.1</version>
9+
<version>1.1.0</version>
1010
<packaging>maven-plugin</packaging>
1111

1212
<name>nae-module-maven-plugin</name>
@@ -145,7 +145,11 @@
145145
<version>3.6.4</version>
146146
<scope>provided</scope>
147147
</dependency>
148-
148+
<dependency>
149+
<groupId>io.github.classgraph</groupId>
150+
<artifactId>classgraph</artifactId>
151+
<version>4.8.179</version>
152+
</dependency>
149153
<dependency>
150154
<groupId>org.twdata.maven</groupId>
151155
<artifactId>mojo-executor</artifactId>

src/main/java/com/netgrif/maven/plugin/module/BuildModuleMojo.java

Lines changed: 206 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
import com.netgrif.maven.plugin.module.assembly.AssemblyDescriptorBuilder;
44
import com.netgrif.maven.plugin.module.assembly.DependencySetBuilder;
55
import com.netgrif.maven.plugin.module.parameters.SimpleArtifact;
6+
import io.github.classgraph.ClassGraph;
7+
import io.github.classgraph.ClassInfo;
8+
import io.github.classgraph.ScanResult;
69
import org.apache.maven.artifact.Artifact;
710
import org.apache.maven.execution.MavenSession;
11+
import org.apache.maven.model.Developer;
812
import org.apache.maven.plugin.AbstractMojo;
913
import org.apache.maven.plugin.BuildPluginManager;
1014
import org.apache.maven.plugin.MojoExecutionException;
@@ -17,13 +21,19 @@
1721
import org.apache.maven.shared.dependency.graph.DependencyNode;
1822
import org.eclipse.sisu.Nullable;
1923

20-
import java.io.File;
21-
import java.io.IOException;
24+
import java.io.*;
2225
import java.nio.file.Files;
2326
import java.util.ArrayList;
2427
import java.util.HashSet;
2528
import java.util.List;
2629
import java.util.Set;
30+
import java.util.*;
31+
import java.util.jar.Attributes;
32+
import java.util.jar.JarEntry;
33+
import java.util.jar.JarInputStream;
34+
import java.util.jar.JarOutputStream;
35+
import java.util.jar.Manifest;
36+
import java.util.stream.Collectors;
2737

2838
import static org.twdata.maven.mojoexecutor.MojoExecutor.*;
2939

@@ -38,7 +48,9 @@
3848
requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
3949
public class BuildModuleMojo extends AbstractMojo {
4050

41-
private final Log log = getLog();
51+
private Log log() {
52+
return getLog();
53+
}
4254

4355
@Component
4456
private MavenProject project;
@@ -64,6 +76,9 @@ public class BuildModuleMojo extends AbstractMojo {
6476
@Parameter(property = "singleOutput")
6577
private boolean singleOutput = true;
6678

79+
@Parameter(property = "customManifestOutputJar", defaultValue = "false")
80+
private boolean customManifestOutputJar;
81+
6782
@Override
6883
public void execute() throws MojoExecutionException {
6984
ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(session.getProjectBuildingRequest());
@@ -76,21 +91,30 @@ public void execute() throws MojoExecutionException {
7691
SimpleArtifact hostAppArtifact = getHostAppArtifact();
7792

7893
if (hostAppArtifact == null) {
79-
log.warn("Packaging module without host application");
94+
log().warn("Packaging module without host application");
8095
} else {
8196
hostDep = findDependency(hostAppArtifact.getGroupId(), hostAppArtifact.getArtifactId(), hostAppArtifact.getVersion(), rootNode);
8297
if (hostDep == null || hostDep.getArtifact() == null)
8398
throw new HostApplicationNotFoundException("Cannot find host app artifact as dependency: " + hostAppArtifact);
84-
if (log.isDebugEnabled())
85-
log.debug("Host app dependency: " + hostDep.getArtifact());
99+
if (log().isDebugEnabled())
100+
log().debug("Host app dependency: " + hostDep.getArtifact());
86101
hostAppDependencies = flattenDependencies(hostDep);
87-
log.info("Dependencies aggregated " + hostAppDependencies.size() + " from host app");
102+
log().info("Dependencies aggregated " + hostAppDependencies.size() + " from host app");
88103
hostAppDependencies.forEach(d -> {
89-
if (log.isDebugEnabled())
90-
log.debug(d.getArtifact().toString());
104+
if (log().isDebugEnabled())
105+
log().debug(d.getArtifact().toString());
91106
});
92107
}
93108

109+
try {
110+
createJarWithCustomManifest();
111+
112+
generateSpringFactoriesToOutputDir();
113+
114+
} catch (IOException ioEx) {
115+
log().error("Failed to create JAR with custom manifest", ioEx);
116+
throw new MojoExecutionException("Failed to create JAR with custom manifest", ioEx);
117+
}
94118

95119
// Build assembly descriptor
96120
AssemblyDescriptorBuilder assembly = new AssemblyDescriptorBuilder();
@@ -103,16 +127,16 @@ public void execute() throws MojoExecutionException {
103127
File targetDir = new File(project.getBuild().getDirectory() + File.separator + "assembly");
104128
try {
105129
Files.createDirectories(targetDir.toPath());
106-
log.info("Successfully ensured target directory exists: " + targetDir.getAbsolutePath());
130+
log().info("Successfully ensured target directory exists: " + targetDir.getAbsolutePath());
107131
} catch (IOException e) {
108132
throw new RuntimeException("Could not create target directory for package assembly: " + targetDir, e);
109133
}
110134
if (!targetDir.getParentFile().canWrite()) {
111135
throw new RuntimeException("Cannot write to parent directory: " + targetDir.getParent());
112136
}
113137
File descriptor = assembly.build(targetDir.getPath());
114-
if (log.isDebugEnabled())
115-
log.debug("maven-assembly-plugin descriptor saved on path: " + descriptor.getAbsolutePath());
138+
if (log().isDebugEnabled())
139+
log().debug("maven-assembly-plugin descriptor saved on path: " + descriptor.getAbsolutePath());
116140

117141
// Executes maven-assembly-plugin to build the resulting package
118142
executeMojo(
@@ -134,10 +158,139 @@ public void execute() throws MojoExecutionException {
134158
}
135159
}
136160

161+
private void createJarWithCustomManifest() throws IOException {
162+
File sourceJar = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".jar");
163+
if (!sourceJar.exists()) {
164+
log().warn("Original JAR file not found: " + sourceJar.getAbsolutePath());
165+
return;
166+
}
167+
168+
Manifest manifest = buildCustomManifest();
169+
170+
if (!customManifestOutputJar) {
171+
File tempJar = new File(sourceJar.getParent(), sourceJar.getName() + ".tmp");
172+
copyJarExcludingManifest(sourceJar, tempJar, manifest);
173+
174+
if (!sourceJar.delete()) {
175+
throw new IOException("Could not delete original JAR before renaming temp JAR!");
176+
}
177+
if (!tempJar.renameTo(sourceJar)) {
178+
throw new IOException("Could not rename temp JAR to original name!");
179+
}
180+
} else {
181+
File outJar = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + "-with-manifest.jar");
182+
copyJarExcludingManifest(sourceJar, outJar, manifest);
183+
log().info("Created JAR with custom manifest: " + outJar.getAbsolutePath());
184+
}
185+
}
186+
187+
private void copyJarExcludingManifest(File inputJar, File outputJar, Manifest manifest) throws IOException {
188+
try (
189+
JarInputStream jarIn = new JarInputStream(new FileInputStream(inputJar));
190+
JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(outputJar), manifest)
191+
) {
192+
JarEntry entry;
193+
byte[] buffer = new byte[8192];
194+
while ((entry = jarIn.getNextJarEntry()) != null) {
195+
String name = entry.getName();
196+
if ("META-INF/MANIFEST.MF".equalsIgnoreCase(name)) {
197+
continue;
198+
}
199+
jarOut.putNextEntry(new JarEntry(name));
200+
int bytesRead;
201+
while ((bytesRead = jarIn.read(buffer)) != -1) {
202+
jarOut.write(buffer, 0, bytesRead);
203+
}
204+
jarOut.closeEntry();
205+
}
206+
}
207+
}
208+
209+
private Manifest buildCustomManifest() throws IOException {
210+
File sourceJar = new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".jar");
211+
Manifest manifest;
212+
try (JarInputStream jarIn = new JarInputStream(new FileInputStream(sourceJar))) {
213+
manifest = jarIn.getManifest();
214+
if (manifest == null) {
215+
manifest = new Manifest();
216+
}
217+
}
218+
Attributes mainAttrs = manifest.getMainAttributes();
219+
if (mainAttrs.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
220+
mainAttrs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
221+
}
222+
223+
List<Developer> developers = project.getDevelopers() != null ? project.getDevelopers() : Collections.emptyList();
224+
225+
Map<String, String> customAttributes = new LinkedHashMap<>();
226+
putIfNotNull(customAttributes, "Netgrif-Name", project.getName());
227+
putIfNotNull(customAttributes, "Netgrif-Version", project.getVersion());
228+
putIfNotNull(customAttributes, "Netgrif-Url", project.getUrl());
229+
putIfNotNull(customAttributes, "Netgrif-Description", project.getDescription());
230+
putIfNotNull(customAttributes, "Netgrif-GroupId", project.getGroupId());
231+
putIfNotNull(customAttributes, "Netgrif-ArtifactId", project.getArtifactId());
232+
233+
if (project.getScm() != null) {
234+
putIfNotNull(customAttributes, "Netgrif-SCM-Connection", project.getScm().getConnection());
235+
putIfNotNull(customAttributes, "Netgrif-SCM-URL", project.getScm().getUrl());
236+
}
237+
238+
if (project.getLicenses() != null && !project.getLicenses().isEmpty()) {
239+
putIfNotNull(customAttributes, "Netgrif-License", project.getLicenses().getFirst());
240+
}
241+
242+
if (project.getOrganization() != null) {
243+
putIfNotNull(customAttributes, "Netgrif-Organization", project.getOrganization().getName());
244+
putIfNotNull(customAttributes, "Netgrif-OrganizationUrl", project.getOrganization().getUrl());
245+
}
246+
247+
if (project.getIssueManagement() != null) {
248+
putIfNotNull(customAttributes, "Netgrif-IssueSystem", project.getIssueManagement().getSystem());
249+
putIfNotNull(customAttributes, "Netgrif-IssueUrl", project.getIssueManagement().getUrl());
250+
}
251+
252+
putIfNotNull(customAttributes, "Netgrif-BuildJdk", System.getProperty("java.version"));
253+
254+
customAttributes.put("Netgrif-BuildTime", java.time.ZonedDateTime.now().toString());
255+
256+
String authors = developers.stream()
257+
.map(dev -> {
258+
String name = dev.getName();
259+
String email = dev.getEmail();
260+
String org = dev.getOrganization();
261+
if (name == null || name.isBlank()) {
262+
return null;
263+
}
264+
StringBuilder sb = new StringBuilder(name);
265+
if (org != null && !org.isBlank()) {
266+
sb.append(" (").append(org).append(")");
267+
}
268+
if (email != null && !email.isBlank()) {
269+
sb.append(" <").append(email).append(">");
270+
}
271+
return sb.toString();
272+
})
273+
.filter(Objects::nonNull)
274+
.collect(Collectors.joining("\n"));
275+
276+
if (!authors.isBlank()) {
277+
putIfNotNull(customAttributes, "Netgrif-Author", authors);
278+
}
279+
customAttributes.forEach(mainAttrs::putValue);
280+
281+
return manifest;
282+
}
283+
284+
private void putIfNotNull(Map<String, String> map, String key, Object value) {
285+
if (value != null && !String.valueOf(value).isBlank()) {
286+
map.put(key, String.valueOf(value));
287+
}
288+
}
289+
137290
private SimpleArtifact getHostAppArtifact() {
138291
SimpleArtifact hostAppArtifact = null;
139292
if (hostApp == null && (naeVersion == null || naeVersion.isEmpty())) {
140-
log.warn("Host application was not found. Packaging module without host application");
293+
log().warn("Host application was not found. Packaging module without host application");
141294
return null;
142295
}
143296
if (naeVersion != null && !naeVersion.isEmpty()) {
@@ -191,8 +344,8 @@ public void separateDescriptor(AssemblyDescriptorBuilder assembly, @Nullable Dep
191344
if (hostAppDependencies != null) {
192345
hostAppDependencies.forEach(depSet::exclude);
193346
}
194-
if (log.isDebugEnabled())
195-
log.debug("Excluding extra dependencies: " + excludes);
347+
if (log().isDebugEnabled())
348+
log().debug("Excluding extra dependencies: " + excludes);
196349
excludes.forEach(depSet::exclude);
197350
}
198351

@@ -225,9 +378,45 @@ public void singleDescriptor(AssemblyDescriptorBuilder assembly, @Nullable Depen
225378
if (hostAppDependencies != null) {
226379
hostAppDependencies.forEach(depSet::exclude);
227380
}
228-
if (log.isDebugEnabled())
229-
log.debug("Excluding extra dependencies: " + excludes);
381+
if (log().isDebugEnabled())
382+
log().debug("Excluding extra dependencies: " + excludes);
230383
excludes.forEach(depSet::exclude);
231384
}
232385

386+
private void generateSpringFactoriesToOutputDir() throws IOException {
387+
String outputDir = project.getBuild().getOutputDirectory();
388+
File springMetaInf = new File(outputDir, "META-INF/spring");
389+
if (!springMetaInf.exists()) {
390+
springMetaInf.mkdirs();
391+
}
392+
List<String> configClasses = new ArrayList<>();
393+
try (ScanResult scanResult = new ClassGraph()
394+
.overrideClasspath(outputDir)
395+
.enableAnnotationInfo()
396+
.enableClassInfo()
397+
.scan()) {
398+
for (ClassInfo ci : scanResult.getAllClasses()) {
399+
if (ci.hasAnnotation("org.springframework.context.annotation.Configuration")
400+
|| ci.hasAnnotation("org.springframework.boot.autoconfigure.AutoConfiguration")) {
401+
configClasses.add(ci.getName());
402+
}
403+
}
404+
}
405+
406+
if (configClasses.isEmpty()) {
407+
return;
408+
}
409+
Collections.sort(configClasses);
410+
411+
File importsFile = new File(springMetaInf, "org.springframework.boot.autoconfigure.AutoConfiguration.imports");
412+
try (PrintWriter writer = new PrintWriter(new FileWriter(importsFile))) {
413+
for (String className : configClasses) {
414+
log().info("Adding to imports: " + className);
415+
writer.println(className);
416+
}
417+
}
418+
log().info("Generated " + importsFile.getAbsolutePath() + " with auto-configuration: " + configClasses.size() + " entries");
419+
}
420+
421+
233422
}

src/main/java/com/netgrif/maven/plugin/module/parameters/SimpleArtifact.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
package com.netgrif.maven.plugin.module.parameters;
22

3-
import lombok.AllArgsConstructor;
43
import lombok.Data;
5-
import lombok.NoArgsConstructor;
64
import org.apache.maven.artifact.Artifact;
75

86
@Data
9-
@NoArgsConstructor
10-
@AllArgsConstructor
117
public class SimpleArtifact {
128

139
public static final String SEPARATOR = ":";
@@ -28,6 +24,15 @@ public SimpleArtifact(String artifact) {
2824
}
2925
}
3026

27+
public SimpleArtifact() {
28+
}
29+
30+
public SimpleArtifact(String groupId, String artifactId, String version) {
31+
this.groupId = groupId;
32+
this.artifactId = artifactId;
33+
this.version = version;
34+
}
35+
3136
public boolean equalsToArtifact(Artifact artifact) {
3237
if (artifact == null) return false;
3338
return this.groupId.equals(artifact.getGroupId()) && this.artifactId.equals(artifact.getArtifactId()) && this.version.equals(artifact.getVersion());
@@ -39,6 +44,18 @@ public boolean isValid() {
3944
version != null && !version.isBlank();
4045
}
4146

47+
public String getGroupId() {
48+
return groupId;
49+
}
50+
51+
public String getArtifactId() {
52+
return artifactId;
53+
}
54+
55+
public String getVersion() {
56+
return version;
57+
}
58+
4259
@Override
4360
public String toString() {
4461
return groupId + SEPARATOR + artifactId + SEPARATOR + version;

0 commit comments

Comments
 (0)