1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.creadur.tentacles;
18
19 import static org.apache.creadur.tentacles.LicenseType.loadLicensesFrom;
20 import static org.apache.creadur.tentacles.RepositoryType.HTTP;
21 import static org.apache.creadur.tentacles.RepositoryType.LOCAL_FILE_SYSTEM;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.net.URI;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.LinkedHashSet;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.zip.ZipEntry;
35 import java.util.zip.ZipInputStream;
36
37 import org.apache.logging.log4j.*;
38
39 public class Main {
40
41
42
43
44
45
46
47
48
49
50 private static final Logger log = LogManager.getLogger(Main.class);
51 private static final String CRAWL_PATTERN = ".*\\.(jar|zip|war|ear|rar|tar.gz)";
52
53 private final Reports reports;
54 private final Licenses licenses;
55
56 private final Layout layout;
57 private final Platform platform;
58 private final Configuration configuration;
59 private final FileSystem fileSystem;
60 private final IOSystem ioSystem;
61 private final TentaclesResources tentaclesResources;
62 private final Templates templates;
63
64 public Main(final String... args) throws Exception {
65 this(new Configuration(args), Platform.aPlatform());
66 }
67
68 public Main(final Configuration configuration, final Platform platform)
69 throws Exception {
70 this(configuration, platform, new Templates(platform), new Layout(
71 platform, configuration));
72 }
73
74 public Main(final Configuration configuration, final Platform platform,
75 final Templates templates, final Layout layout) throws Exception {
76 this.platform = platform;
77 this.configuration = configuration;
78 this.layout = layout;
79 this.fileSystem = platform.getFileSystem();
80 this.ioSystem = platform.getIoSystem();
81 this.tentaclesResources = platform.getTentaclesResources();
82 this.templates = templates;
83
84 this.reports = new Reports();
85
86 log.info("Remote repository: {}", this.configuration.getStagingRepositoryURI());
87 log.info("Local root directory: {}", this.layout.getLocalRootDirectory());
88
89 this.tentaclesResources.copyTo("legal/style.css",
90 new File(this.layout.getOutputDirectory(), "style.css"));
91
92 this.licenses = loadLicensesFrom(platform);
93 }
94
95 public static void main(final String[] args) throws Exception {
96
97 log.info("Launching Apache Tentacles ...");
98
99 if(args == null || args.length < 1) {
100 log.error("Error: Input parameter missing - you did not specify any component to run Apache Tentacles on.");
101 log.error("Please launch Apache Tentacles with an URI to work on such as 'https://repository.apache.org/content/repositories/orgapachecreadur-1000/'.");
102 } else {
103 new Main(args).main();
104 }
105
106 }
107
108 private void main() throws Exception {
109
110 unpackContents(mirrorRepositoryFrom(this.configuration));
111
112 reportOn(archivesIn(this.layout.getRepositoryDirectory()));
113 }
114
115 private List<Archive> archivesIn(final File repository) {
116 final List<File> jars = this.fileSystem.documentsFrom(repository);
117
118 final List<Archive> archives = new ArrayList<>();
119 for (final File file : jars) {
120 final Archive archive =
121 new Archive(file, this.fileSystem, this.layout);
122 archives.add(archive);
123 }
124 return archives;
125 }
126
127 private void reportOn(final List<Archive> archives) throws IOException {
128 this.templates
129 .template("legal/archives.vm")
130 .add("archives", archives)
131 .add("reports", this.reports)
132 .write(new File(this.layout.getOutputDirectory(),
133 "archives.html"));
134
135 reportLicenses(archives);
136 reportNotices(archives);
137 reportDeclaredLicenses(archives);
138 reportDeclaredNotices(archives);
139 }
140
141 private void reportLicenses(final List<Archive> archives)
142 throws IOException {
143 initLicenses(archives);
144
145 this.templates
146 .template("legal/licenses.vm")
147 .add("licenses", getLicenses(archives))
148 .add("reports", this.reports)
149 .write(new File(this.layout.getOutputDirectory(),
150 "licenses.html"));
151 }
152
153 private void initLicenses(final List<Archive> archives) throws IOException {
154 final Map<License, License> licenses = new HashMap<>();
155
156 for (final Archive archive : archives) {
157 final List<File> files =
158 this.fileSystem.licensesFrom(archive.contentsDirectory());
159 for (final File file : files) {
160 final License license = this.licenses.from(file);
161
162 License existing = licenses.get(license);
163 if (existing == null) {
164 licenses.put(license, license);
165 existing = license;
166 }
167
168 existing.getLocations().add(file);
169 existing.getArchives().add(archive);
170 archive.getLicenses().add(existing);
171 }
172 }
173 }
174
175 private Collection<License> getLicenses(final List<Archive> archives) {
176 final Set<License> licenses = new LinkedHashSet<>();
177 for (final Archive archive : archives) {
178 licenses.addAll(archive.getLicenses());
179 }
180 return licenses;
181 }
182
183 private void reportDeclaredLicenses(final List<Archive> archives)
184 throws IOException {
185
186 for (final Archive archive : archives) {
187
188 classifyLicenses(archive);
189 }
190 for (final Archive archive : archives) {
191
192 this.templates
193 .template("legal/archive-licenses.vm")
194 .add("archive", archive)
195 .add("reports", this.reports)
196 .write(new File(this.layout.getOutputDirectory(),
197 this.reports.licenses(archive)));
198 }
199
200 }
201
202 private void classifyLicenses(final Archive archive) throws IOException {
203 final Set<License> undeclared =
204 new HashSet<>(archive.getLicenses());
205
206 final File contents = archive.contentsDirectory();
207 final List<File> files = this.fileSystem.licensesDeclaredIn(contents);
208
209 for (final File file : files) {
210
211 undeclared.remove(this.licenses.from(file));
212
213 }
214
215 archive.getOtherLicenses().addAll(undeclared);
216
217 final Set<License> declared =
218 new HashSet<>(archive.getLicenses());
219 declared.removeAll(undeclared);
220 archive.getDeclaredLicenses().addAll(declared);
221
222 for (final License license : undeclared) {
223
224 for (final License declare : declared) {
225 if (license.implies(declare)) {
226 archive.getOtherLicenses().remove(license);
227 }
228 }
229 }
230 }
231
232 private void reportDeclaredNotices(final List<Archive> archives)
233 throws IOException {
234
235 for (final Archive archive : archives) {
236
237 final Set<Notice> undeclared =
238 new HashSet<>(archive.getNotices());
239
240 final File contents = archive.contentsDirectory();
241 final List<File> files =
242 this.fileSystem.noticesDeclaredIn(contents);
243
244 for (final File file : files) {
245
246 final Notice notice = new Notice(this.ioSystem.slurp(file));
247
248 undeclared.remove(notice);
249 }
250
251 archive.getOtherNotices().addAll(undeclared);
252
253 final Set<Notice> declared =
254 new HashSet<>(archive.getNotices());
255 declared.removeAll(undeclared);
256 archive.getDeclaredNotices().addAll(declared);
257
258 for (final Notice notice : undeclared) {
259
260 for (final Notice declare : declared) {
261 if (notice.implies(declare)) {
262 archive.getOtherLicenses().remove(notice);
263 }
264 }
265 }
266
267 this.templates
268 .template("legal/archive-notices.vm")
269 .add("archive", archive)
270 .add("reports", this.reports)
271 .write(new File(this.layout.getOutputDirectory(),
272 this.reports.notices(archive)));
273 }
274 }
275
276 private void reportNotices(final List<Archive> archives) throws IOException {
277 final Map<Notice, Notice> notices = new HashMap<>();
278
279 for (final Archive archive : archives) {
280 final List<File> noticeDocuments =
281 this.fileSystem.noticesOnly(archive.contentsDirectory());
282 for (final File file : noticeDocuments) {
283 final Notice notice = new Notice(this.ioSystem.slurp(file));
284
285 Notice existing = notices.get(notice);
286 if (existing == null) {
287 notices.put(notice, notice);
288 existing = notice;
289 }
290
291 existing.getLocations().add(file);
292 existing.getArchives().add(archive);
293 archive.getNotices().add(existing);
294 }
295 }
296
297 this.templates
298 .template("legal/notices.vm")
299 .add("notices", notices.values())
300 .add("reports", this.reports)
301 .write(new File(this.layout.getOutputDirectory(),
302 "notices.html"));
303 }
304
305 private void unpackContents(final Set<File> files) throws IOException {
306 for (final File file : files) {
307 unpack(file);
308 }
309 }
310
311 private Set<File> mirrorRepositoryFrom(final Configuration configuration)
312 throws IOException {
313 final Set<File> files = new HashSet<>();
314 if (HTTP.isRepositoryFor(configuration)) {
315 final NexusClient client = new NexusClient(this.platform);
316 final Set<URI> resources =
317 client.crawl(configuration.getStagingRepositoryURI());
318
319 for (final URI uri : resources) {
320 if (!uri.getPath().matches(CRAWL_PATTERN)) {
321 continue;
322 }
323 files.add(client.download(uri, mirroredFrom(uri)));
324 }
325 } else if (LOCAL_FILE_SYSTEM.isRepositoryFor(configuration)) {
326 final File file = new File(configuration.getStagingRepositoryURI());
327 final List<File> collect =
328 this.platform.getFileSystem().archivesInPath(file,
329 configuration.getFileRepositoryPathNameFilter());
330
331 for (final File f : collect) {
332 files.add(copyToMirror(f));
333 }
334 }
335 return files;
336 }
337
338 private void unpack(final File archive) {
339 log.info("Unpack {}", archive);
340
341 try {
342 final ZipInputStream zip = this.ioSystem.unzip(archive);
343
344 final File contents =
345 new Archive(archive, this.fileSystem, this.layout)
346 .contentsDirectory();
347
348 try {
349 ZipEntry entry = null;
350
351 while ((entry = zip.getNextEntry()) != null) {
352
353 if (entry.isDirectory()) {
354 continue;
355 }
356
357 final String path = entry.getName();
358
359 final File fileEntry = new File(contents, path);
360
361 this.fileSystem.mkparent(fileEntry);
362
363
364
365 this.ioSystem.copy(zip, fileEntry);
366
367 if (fileEntry.getName().endsWith(".jar")) {
368 unpack(fileEntry);
369 }
370 }
371 } finally {
372 this.ioSystem.close(zip);
373 }
374 } catch (final IOException e) {
375 log.error("Not a zip {}", archive);
376 }
377 }
378
379 private File copyToMirror(final File src) throws IOException {
380 final URI uri = src.toURI();
381
382 final File file = mirroredFrom(uri);
383
384 log.info("Copy {}", uri);
385
386 this.fileSystem.mkparent(file);
387
388 this.ioSystem.copy(this.ioSystem.read(src), file);
389
390 return file;
391 }
392
393 private File mirroredFrom(final URI uri) {
394 final String name =
395 uri.toString()
396 .replace(
397 this.configuration.getStagingRepositoryURI()
398 .toString(), "").replaceFirst("^/", "");
399 return new File(this.layout.getRepositoryDirectory(), name);
400 }
401
402 }