View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
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  /* TENTACLES-12: disabled root logger configuration       
42      static {
43          final Logger root = LogManager.getRootLogger();
44          root.addAppender(new ConsoleAppender(new PatternLayout(
45                  PatternLayout.TTCC_CONVERSION_PATTERN)));
46          root.setLevel(Level.INFO);
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                     // Open the output file
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 }