blob: 5858c81abac7ad791459cb6ab690c349c7d47531 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.rat.tentacles;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* @version $Rev$ $Date$
*/
public class Main {
static {
Logger root = Logger.getRootLogger();
root.addAppender(new ConsoleAppender(new PatternLayout(PatternLayout.TTCC_CONVERSION_PATTERN)));
root.setLevel(Level.INFO);
}
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(Main.class);
private final File local;
private final URI staging;
private final File repository;
private final File content;
private Reports reports;
private Map<String, String> licenses = new HashMap<String, String>();
private String filter;
private final NexusClient client = new NexusClient();
public Main(String... args) throws Exception {
this.staging = getURI(args[0]);
String name = new File(this.staging.getPath()).getName();
if (args.length > 1) {
this.local = new File(args[1]);
} else {
this.local = new File(name);
}
Files.mkdirs(local);
this.repository = new File(local, "repo");
this.content = new File(local, "content");
Files.mkdirs(repository);
Files.mkdirs(content);
log.info("Repo: " + staging);
log.info("Local: " + local);
this.reports = new Reports();
this.filter = System.getProperty("filter", "org/apache/openejb");
final URL style = this.getClass().getClassLoader().getResource("legal/style.css");
IO.copy(style.openStream(), new File(local, "style.css"));
licenses("asl-2.0");
licenses("cpl-1.0");
licenses("cddl-1.0");
}
private URI getURI(String arg) throws URISyntaxException {
final URI uri = new URI(arg);
if (arg.startsWith("file:")) {
File file = new File(uri);
file = file.getAbsoluteFile();
return file.toURI();
}
return uri;
}
private void licenses(String s) throws IOException {
URL aslURL = this.getClass().getClassLoader().getResource("licenses/" + s + ".txt");
licenses.put(s, IO.slurp(aslURL).trim());
}
public static void main(String[] args) throws Exception {
new Main(args).main();
}
private void main() throws Exception {
prepare();
final List<File> jars = Files.collect(repository, new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isFile();
}
});
final List<Archive> archives = new ArrayList<Archive>();
for (File file : jars) {
final Archive archive = new Archive(file);
archives.add(archive);
}
Templates.template("legal/archives.vm")
.add("archives", archives)
.add("reports", reports)
.write(new File(local, "archives.html"));
reportLicenses(archives);
reportNotices(archives);
reportDeclaredLicenses(archives);
reportDeclaredNotices(archives);
}
private void reportLicenses(List<Archive> archives) throws IOException {
initLicenses(archives);
Templates.template("legal/licenses.vm")
.add("licenses", getLicenses(archives))
.add("reports", reports)
.write(new File(local, "licenses.html"));
}
private void initLicenses(List<Archive> archives) throws IOException {
Map<License, License> licenses = new HashMap<License, License>();
for (Archive archive : archives) {
List<File> files = Files.collect(contents(archive.getFile()), new LicenseFilter());
for (File file : files) {
final License license = new License(IO.slurp(file));
License existing = licenses.get(license);
if (existing == null) {
licenses.put(license, license);
existing = license;
}
existing.locations.add(file);
existing.getArchives().add(archive);
archive.getLicenses().add(existing);
}
}
}
private Collection<License> getLicenses(List<Archive> archives) {
Set<License> licenses = new LinkedHashSet<License>();
for (Archive archive : archives) {
licenses.addAll(archive.getLicenses());
}
return licenses;
}
private void reportDeclaredLicenses(List<Archive> archives) throws IOException {
for (Archive archive : archives) {
classifyLicenses(archive);
}
for (Archive archive : archives) {
Templates.template("legal/archive-licenses.vm")
.add("archive", archive)
.add("reports", reports)
.write(new File(local, reports.licenses(archive)));
}
}
private void classifyLicenses(Archive archive) throws IOException {
final Set<License> undeclared = new HashSet<License>(archive.getLicenses());
final File contents = contents(archive.getFile());
final List<File> files = Files.collect(contents, new Filters(new DeclaredFilter(contents), new LicenseFilter()));
for (File file : files) {
final License license = new License(IO.slurp(file));
undeclared.remove(license);
}
archive.getOtherLicenses().addAll(undeclared);
final Set<License> declared = new HashSet<License>(archive.getLicenses());
declared.removeAll(undeclared);
archive.getDeclaredLicenses().addAll(declared);
for (License license : undeclared) {
for (License declare : declared) {
if (license.implies(declare)) {
archive.getOtherLicenses().remove(license);
}
}
}
}
private void reportDeclaredNotices(List<Archive> archives) throws IOException {
for (Archive archive : archives) {
final Set<Notice> undeclared = new HashSet<Notice>(archive.getNotices());
final File contents = contents(archive.getFile());
final List<File> files = Files.collect(contents, new Filters(new DeclaredFilter(contents), new NoticeFilter()));
for (File file : files) {
final Notice notice = new Notice(IO.slurp(file));
undeclared.remove(notice);
}
archive.getOtherNotices().addAll(undeclared);
final Set<Notice> declared = new HashSet<Notice>(archive.getNotices());
declared.removeAll(undeclared);
archive.getDeclaredNotices().addAll(declared);
for (Notice notice : undeclared) {
for (Notice declare : declared) {
if (notice.implies(declare)) {
archive.getOtherLicenses().remove(notice);
}
}
}
Templates.template("legal/archive-notices.vm")
.add("archive", archive)
.add("reports", reports)
.write(new File(local, reports.notices(archive)));
}
}
private void reportNotices(List<Archive> archives) throws IOException {
Map<Notice, Notice> notices = new HashMap<Notice, Notice>();
for (Archive archive : archives) {
List<File> files = Files.collect(contents(archive.getFile()), new NoticeFilter());
for (File file : files) {
final Notice notice = new Notice(IO.slurp(file));
Notice existing = notices.get(notice);
if (existing == null) {
notices.put(notice, notice);
existing = notice;
}
existing.locations.add(file);
existing.getArchives().add(archive);
archive.getNotices().add(existing);
}
}
Templates.template("legal/notices.vm")
.add("notices", notices.values())
.add("reports", reports)
.write(new File(local, "notices.html"));
}
public class Reports {
public String licenses(Archive archive) {
return archive.uri.toString().replace('/', '.') + ".licenses.html";
}
public String notices(Archive archive) {
return archive.uri.toString().replace('/', '.') + ".notices.html";
}
}
private List<URI> allNoticeFiles() {
List<File> legal = Files.collect(content, new LegalFilter());
for (File file : legal) {
log.info("Legal " + file);
}
URI uri = local.toURI();
List<URI> uris = new ArrayList<URI>();
for (File file : legal) {
URI full = file.toURI();
URI relativize = uri.relativize(full);
uris.add(relativize);
}
return uris;
}
private void prepare() throws URISyntaxException, IOException {
final Set<File> files = new HashSet<File>();
if (staging.toString().startsWith("http")) {
final Set<URI> resources = client.crawl(staging);
for (URI uri : resources) {
if (!uri.getPath().matches(".*(war|jar|zip)")) continue;
files.add(download(uri));
}
} else if (staging.toString().startsWith("file:")) {
File file = new File(staging);
List<File> collect = Files.collect(file, new FileFilter() {
@Override
public boolean accept(File pathname) {
String path = pathname.getAbsolutePath();
return path.matches(filter) && isValidArchive(path);
}
});
for (File f : collect) {
files.add(copy(f));
}
}
for (File file : files) {
unpack(file);
}
}
private void unpack(File archive) throws IOException {
log.info("Unpack " + archive);
try {
final ZipInputStream zip = IO.unzip(archive);
final File contents = contents(archive);
try {
ZipEntry entry = null;
while ((entry = zip.getNextEntry()) != null) {
if (entry.isDirectory()) continue;
final String path = entry.getName();
final File fileEntry = new File(contents, path);
Files.mkparent(fileEntry);
// Open the output file
IO.copy(zip, fileEntry);
if (fileEntry.getName().endsWith(".jar")) {
unpack(fileEntry);
}
}
} finally {
IO.close(zip);
}
} catch (IOException e) {
log.error("Not a zip " + archive);
}
}
public class License {
private final String text;
private String key;
private Set<Archive> archives = new HashSet<Archive>();
private List<File> locations = new ArrayList<File>();
public License(String text) {
key = text.replaceAll("[ \\n\\t\\r]+", "").toLowerCase().intern();
for (Map.Entry<String, String> license : licenses.entrySet()) {
text = text.replace(license.getValue(), String.format("---[%s - full text]---\n\n", license.getKey()));
}
this.text = text.intern();
}
public String getText() {
return text;
}
public String getKey() {
return key;
}
public Set<Archive> getArchives() {
return archives;
}
public Set<URI> locations(Archive archive) {
URI contents = contents(archive.getFile()).toURI();
Set<URI> locations = new HashSet<URI>();
for (File file : this.locations) {
URI uri = file.toURI();
URI relativize = contents.relativize(uri);
if (!relativize.equals(uri)) {
locations.add(relativize);
}
}
return locations;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
License license = (License) o;
if (!key.equals(license.key)) return false;
return true;
}
@Override
public int hashCode() {
return key.hashCode();
}
public boolean implies(License fullLicense) {
return fullLicense.key.contains(this.key);
}
}
public class Notice {
private final String text;
private String key;
private Set<Archive> archives = new HashSet<Archive>();
private List<File> locations = new ArrayList<File>();
public Notice(String text) {
this.text = text.intern();
key = text.replaceAll("[ \\n\\t\\r]+", "").toLowerCase().intern();
}
public String getText() {
return text;
}
public String getKey() {
return key;
}
public Set<Archive> getArchives() {
return archives;
}
public Set<URI> locations(Archive archive) {
URI contents = contents(archive.getFile()).toURI();
Set<URI> locations = new HashSet<URI>();
for (File file : this.locations) {
URI uri = file.toURI();
URI relativize = contents.relativize(uri);
if (!relativize.equals(uri)) {
locations.add(relativize);
}
}
return locations;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Notice notice = (Notice) o;
if (!key.equals(notice.key)) return false;
return true;
}
@Override
public int hashCode() {
return key.hashCode();
}
public boolean implies(Notice fullLicense) {
return fullLicense.key.contains(this.key);
}
}
private File contents(File archive) {
String path = archive.getAbsolutePath().substring(local.getAbsolutePath().length() + 1);
if (path.startsWith("repo/")) path = path.substring("repo/".length());
if (path.startsWith("content/")) path = path.substring("content/".length());
final File contents = new File(content, path + ".contents");
Files.mkdirs(contents);
return contents;
}
private File download(URI uri) throws IOException {
final File file = getFile(uri);
return client.download(uri, file);
}
private File copy(File src) throws IOException {
final URI uri = src.toURI();
final File file = getFile(uri);
log.info("Copy " + uri);
Files.mkparent(file);
IO.copy(IO.read(src), file);
return file;
}
private static class LegalFilter implements FileFilter {
private static final NoticeFilter notice = new NoticeFilter();
private static final LicenseFilter license = new LicenseFilter();
@Override
public boolean accept(File pathname) {
return notice.accept(pathname) || license.accept(pathname);
}
}
private static class NoticeFilter implements FileFilter {
@Override
public boolean accept(File pathname) {
final String name = pathname.getName().toLowerCase();
if (name.equals("notice")) return true;
if (name.equals("notice.txt")) return true;
return false;
}
}
private static class LicenseFilter implements FileFilter {
@Override
public boolean accept(File pathname) {
final String name = pathname.getName().toLowerCase();
if (name.equals("license")) return true;
if (name.equals("license.txt")) return true;
return false;
}
}
private static class N implements FileFilter {
private final FileFilter filter;
private N(FileFilter filter) {
this.filter = filter;
}
@Override
public boolean accept(File pathname) {
return !filter.accept(pathname);
}
}
private static class DeclaredFilter implements FileFilter {
private final File file;
private DeclaredFilter(File file) {
this.file = file;
}
@Override
public boolean accept(File file) {
while (file != null) {
if (file.equals(this.file)) break;
if (file.isDirectory() && file.getName().endsWith(".contents")) return false;
file = file.getParentFile();
}
return true;
}
}
private static class Filters implements FileFilter {
List<FileFilter> filters = new ArrayList<FileFilter>();
private Filters(FileFilter... filters) {
for (FileFilter filter : filters) {
this.filters.add(filter);
}
}
@Override
public boolean accept(File file) {
for (FileFilter filter : filters) {
if (!filter.accept(file)) return false;
}
return true;
}
}
public class Archive {
private final URI uri;
private final File file;
private final Map<URI, URI> map;
private final Set<License> licenses = new HashSet<License>();
private final Set<Notice> notices = new HashSet<Notice>();
private final Set<License> declaredLicenses = new HashSet<License>();
private final Set<Notice> declaredNotices = new HashSet<Notice>();
private final Set<License> otherLicenses = new HashSet<License>();
private final Set<Notice> otherNotices = new HashSet<Notice>();
private Map<URI, URI> others;
public Archive(File file) {
this.uri = repository.toURI().relativize(file.toURI());
this.file = file;
this.map = map();
}
public Set<License> getDeclaredLicenses() {
return declaredLicenses;
}
public Set<Notice> getDeclaredNotices() {
return declaredNotices;
}
public Set<License> getOtherLicenses() {
return otherLicenses;
}
public Set<Notice> getOtherNotices() {
return otherNotices;
}
public Set<License> getLicenses() {
return licenses;
}
public Set<Notice> getNotices() {
return notices;
}
public URI getUri() {
return uri;
}
public File getFile() {
return file;
}
public Map<URI, URI> getLegal() {
return map;
}
public Map<URI, URI> getOtherLegal() {
if (others == null) {
others = mapOther();
}
return others;
}
private Map<URI, URI> mapOther() {
final File jarContents = contents(file);
final List<File> legal = Files.collect(jarContents, new Filters(new N(new DeclaredFilter(jarContents)), new LegalFilter()));
Map<URI, URI> map = new LinkedHashMap<URI, URI>();
for (File file : legal) {
URI name = jarContents.toURI().relativize(file.toURI());
URI link = local.toURI().relativize(file.toURI());
map.put(name, link);
}
return map;
}
private Map<URI, URI> map() {
final File jarContents = contents(file);
final List<File> legal = Files.collect(jarContents, new Filters(new DeclaredFilter(jarContents), new LegalFilter()));
Map<URI, URI> map = new LinkedHashMap<URI, URI>();
for (File file : legal) {
URI name = jarContents.toURI().relativize(file.toURI());
URI link = local.toURI().relativize(file.toURI());
map.put(name, link);
}
return map;
}
}
private File getFile(URI uri) {
final String name = uri.toString().replace(staging.toString(), "").replaceFirst("^/", "");
return new File(repository, name);
}
private boolean isValidArchive(String path) {
return path.matches(".*\\.(jar|zip|war|ear|tar.gz)");
}
}