1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.maven.plugins.slinginitialcontenttransform;
21
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.nio.file.Files;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.jar.Attributes;
30 import java.util.jar.Manifest;
31 import java.util.stream.Collectors;
32 import java.util.zip.ZipEntry;
33 import java.util.zip.ZipOutputStream;
34
35 import org.apache.commons.io.FilenameUtils;
36 import org.apache.commons.io.IOUtils;
37 import org.apache.commons.lang3.StringUtils;
38 import org.apache.jackrabbit.vault.packaging.PackageType;
39 import org.apache.maven.plugin.AbstractMojo;
40 import org.apache.maven.plugin.MojoExecutionException;
41 import org.apache.maven.plugin.MojoFailureException;
42 import org.apache.maven.plugins.annotations.Component;
43 import org.apache.maven.plugins.annotations.LifecyclePhase;
44 import org.apache.maven.plugins.annotations.Mojo;
45 import org.apache.maven.plugins.annotations.Parameter;
46 import org.apache.maven.project.MavenProject;
47 import org.apache.maven.project.MavenProjectHelper;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 import io.wcm.tooling.commons.contentpackagebuilder.ContentPackage;
52 import io.wcm.tooling.commons.contentpackagebuilder.ContentPackageBuilder;
53 import io.wcm.tooling.commons.contentpackagebuilder.PackageFilter;
54
55
56
57
58
59
60
61
62 @Mojo(name = "transform", requiresProject = true, threadSafe = true, defaultPhase = LifecyclePhase.PACKAGE)
63 public class TransformMojo extends AbstractMojo {
64
65 private static final String CLASSIFIER_CONTENT = "content";
66 private static final String CLASSIFIER_BUNDLE = "bundle";
67 private static final String MANIFEST_FILE = "META-INF/MANIFEST.MF";
68
69 private static final Logger log = LoggerFactory.getLogger(TransformMojo.class);
70
71
72
73
74 @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}.jar", required = true)
75 private File file;
76
77
78
79
80 @Parameter(defaultValue = "${project.groupId}", required = true)
81 private String group;
82
83
84
85
86 @Parameter(defaultValue = "true", required = true)
87 private boolean generateContent;
88
89
90
91
92 @Parameter(defaultValue = "true", required = true)
93 private boolean generateBundle;
94
95
96
97
98 @Parameter
99 private Map<String, String> xmlNamespaces;
100
101
102
103
104 @Parameter(property = "slinginitialcontenttransform.skip", defaultValue = "false")
105 private boolean skip;
106
107 @Parameter(property = "project", required = true, readonly = true)
108 private MavenProject project;
109 @Component
110 private MavenProjectHelper projectHelper;
111
112 @Override
113 public void execute() throws MojoExecutionException, MojoFailureException {
114 if (skip) {
115 log.debug("Skipping execution.");
116 return;
117 }
118 if (!StringUtils.equals(project.getPackaging(), "jar")) {
119 log.debug("Skipping execution - not a jar project: {}", project.getPackaging());
120 return;
121 }
122 if (!file.exists()) {
123 log.warn("File does not exist: {}", file.getPath());
124 return;
125 }
126
127 try (OsgiBundleFile osgiBundle = new OsgiBundleFile(file)) {
128 if (!osgiBundle.hasContent()) {
129 log.debug("Skipping execution - bundle does not contain Sling-Initial-Content.");
130 return;
131 }
132 transformBundle(osgiBundle);
133 }
134 catch (IOException ex) {
135 throw new MojoExecutionException("Unable to transform bundle.", ex);
136 }
137 }
138
139
140
141
142
143 private void transformBundle(OsgiBundleFile osgiBundle) throws IOException {
144 if (generateContent) {
145 File contentPackageFile = createContentPackage(osgiBundle);
146 projectHelper.attachArtifact(project, "zip", CLASSIFIER_CONTENT, contentPackageFile);
147 }
148 if (generateBundle) {
149 File bundleFile = createBundleWithoutContent(osgiBundle);
150 projectHelper.attachArtifact(project, "jar", CLASSIFIER_BUNDLE, bundleFile);
151 }
152 }
153
154
155
156
157
158
159
160 private File createContentPackage(OsgiBundleFile osgiBundle) throws IOException {
161 String contentPackageName = project.getBuild().getFinalName() + "-" + CLASSIFIER_CONTENT + ".zip";
162 File contentPackageFile = new File(project.getBuild().getDirectory(), contentPackageName);
163 if (contentPackageFile.exists()) {
164 Files.delete(contentPackageFile.toPath());
165 }
166
167 ContentPackageBuilder contentPackageBuilder = new ContentPackageBuilder()
168 .group(this.group)
169 .name(project.getArtifactId() + "-" + CLASSIFIER_CONTENT)
170 .version(project.getVersion())
171 .packageType(PackageType.APPLICATION.name().toLowerCase());
172 for (ContentMapping mapping : osgiBundle.getContentMappings()) {
173 contentPackageBuilder.filter(new PackageFilter(mapping.getContentPath()));
174 }
175 for (Map.Entry<String, String> namespace : osgiBundle.getNamespaces().entrySet()) {
176 contentPackageBuilder.xmlNamespace(namespace.getKey(), namespace.getValue());
177 }
178 if (xmlNamespaces != null) {
179 for (Map.Entry<String, String> namespace : xmlNamespaces.entrySet()) {
180 contentPackageBuilder.xmlNamespace(namespace.getKey(), namespace.getValue());
181 }
182 }
183 try (ContentPackage contentPackage = contentPackageBuilder.build(contentPackageFile)) {
184 for (ContentMapping mapping : osgiBundle.getContentMappings()) {
185 List<BundleEntry> entries = osgiBundle.getContentEntries(mapping).collect(Collectors.toList());
186
187
188 CollectNonDirectoryPathsProcessor nonDirectoryPaths = new CollectNonDirectoryPathsProcessor();
189 for (BundleEntry entry : entries) {
190 processContent(contentPackage, entry, mapping, nonDirectoryPaths);
191 }
192
193
194 WriteContentProcessor writeContent = new WriteContentProcessor(nonDirectoryPaths.getPaths());
195 for (BundleEntry entry : entries) {
196 processContent(contentPackage, entry, mapping, writeContent);
197 }
198 }
199 }
200
201 log.info("Created package with Sling-Initial-Content: {}", contentPackageFile.getName());
202 return contentPackageFile;
203 }
204
205
206
207
208
209
210
211
212
213 private void processContent(ContentPackage contentPackage, BundleEntry entry, ContentMapping mapping,
214 BundleEntryProcessor processor) throws IOException {
215 String extension = FilenameUtils.getExtension(entry.getPath());
216 if (entry.isDirectory()) {
217 String path = StringUtils.removeEnd(entry.getPath(), "/");
218 processor.directory(path, contentPackage);
219 }
220 else if (mapping.isJson() && StringUtils.equals(extension, "json")) {
221 String path = StringUtils.substringBeforeLast(entry.getPath(), ".json");
222 processor.jsonContent(path, entry, contentPackage);
223 }
224 else if (mapping.isXml() && StringUtils.equals(extension, "xml")) {
225 String path = StringUtils.substringBeforeLast(entry.getPath(), ".xml");
226 processor.xmlContent(path, entry, contentPackage);
227 }
228 else {
229 String path = entry.getPath();
230 processor.binaryContent(path, entry, contentPackage);
231 }
232 }
233
234
235
236
237
238
239
240 private File createBundleWithoutContent(OsgiBundleFile osgiBundle) throws IOException {
241 String bundleFileName = project.getBuild().getFinalName() + "-" + CLASSIFIER_BUNDLE + ".jar";
242 File bundleFile = new File(project.getBuild().getDirectory(), bundleFileName);
243 if (bundleFile.exists()) {
244 Files.delete(bundleFile.toPath());
245 }
246
247 try (FileOutputStream fos = new FileOutputStream(bundleFile);
248 ZipOutputStream zos = new ZipOutputStream(fos)) {
249 List<BundleEntry> entries = osgiBundle.getNonContentEntries().collect(Collectors.toList());
250 for (BundleEntry entry : entries) {
251 zos.putNextEntry(new ZipEntry(entry.getPath()));
252 if (!entry.isDirectory()) {
253 try (InputStream is = entry.getInputStream()) {
254 if (StringUtils.equals(entry.getPath(), MANIFEST_FILE)) {
255 Manifest transformedManifest = getManifestWithoutSlingInitialContentHeader(is);
256 transformedManifest.write(zos);
257 }
258 else {
259 IOUtils.copy(is, zos);
260 }
261 }
262 }
263 }
264 }
265
266 log.info("Created bundle without content: {}", bundleFile.getName());
267 return bundleFile;
268 }
269
270
271
272
273
274
275
276 private Manifest getManifestWithoutSlingInitialContentHeader(InputStream is) throws IOException {
277 Manifest manifest = new Manifest(is);
278 manifest.getMainAttributes().remove(new Attributes.Name(OsgiBundleFile.HEADER_INITIAL_CONTENT));
279 return manifest;
280 }
281
282 }