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