Overview
License compliance checking is crucial for ensuring that your Java application and its dependencies comply with legal requirements. This involves identifying dependencies, analyzing their licenses, and verifying compliance with your project's license policy.
Tools and Libraries
1. Core Tools for License Checking
// Maven dependencies for license checking tools // pom.xml <dependencies> <!-- License Maven Plugin --> <dependency> <groupId>org.codehaus.mojo</groupId> <artifactId>license-maven-plugin</artifactId> <version>2.0.0</version> </dependency> <!-- OSS Review Toolkit integration --> <dependency> <groupId>org.ossreviewtoolkit</groupId> <artifactId>ort-model</artifactId> <version>1.0.0</version> </dependency> <!-- ClearlyDefined client --> <dependency> <groupId>org.clearlydefined</groupId> <artifactId>clearlydefined-client</artifactId> <version>1.0.0</version> </dependency> </dependencies>
2. Custom License Checker Implementation
package com.example.licensecheck;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.json.*;
public class LicenseComplianceChecker {
private final Set<String> allowedLicenses;
private final Set<String> forbiddenLicenses;
private final Map<String, String> licenseMapping;
public LicenseComplianceChecker() {
this.allowedLicenses = new HashSet<>(Arrays.asList(
"Apache-2.0", "MIT", "BSD-2-Clause", "BSD-3-Clause",
"EPL-2.0", "LGPL-2.1", "LGPL-3.0", "ISC"
));
this.forbiddenLicenses = new HashSet<>(Arrays.asList(
"AGPL-3.0", "GPL-3.0", "SSPL-1.0"
));
this.licenseMapping = new HashMap<>();
initializeLicenseMapping();
}
private void initializeLicenseMapping() {
licenseMapping.put("The Apache Software License, Version 2.0", "Apache-2.0");
licenseMapping.put("Apache License, Version 2.0", "Apache-2.0");
licenseMapping.put("MIT License", "MIT");
licenseMapping.put("BSD License", "BSD-2-Clause");
// Add more mappings as needed
}
public ComplianceReport checkProjectCompliance(Path projectDir) throws IOException {
List<Dependency> dependencies = extractDependencies(projectDir);
ComplianceReport report = new ComplianceReport();
for (Dependency dependency : dependencies) {
DependencyLicenseInfo licenseInfo = analyzeDependencyLicense(dependency);
report.addDependency(licenseInfo);
if (!isLicenseCompliant(licenseInfo)) {
report.addViolation(createViolation(licenseInfo));
}
}
return report;
}
private List<Dependency> extractDependencies(Path projectDir) throws IOException {
List<Dependency> dependencies = new ArrayList<>();
// Check for Maven pom.xml
Path pomXml = projectDir.resolve("pom.xml");
if (Files.exists(pomXml)) {
dependencies.addAll(parseMavenDependencies(pomXml));
}
// Check for Gradle build.gradle
Path buildGradle = projectDir.resolve("build.gradle");
if (Files.exists(buildGradle)) {
dependencies.addAll(parseGradleDependencies(buildGradle));
}
return dependencies;
}
private List<Dependency> parseMavenDependencies(Path pomXml) throws IOException {
List<Dependency> dependencies = new ArrayList<>();
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(pomXml.toFile());
NodeList dependencyNodes = doc.getElementsByTagName("dependency");
for (int i = 0; i < dependencyNodes.getLength(); i++) {
Element dependencyElement = (Element) dependencyNodes.item(i);
String groupId = getElementText(dependencyElement, "groupId");
String artifactId = getElementText(dependencyElement, "artifactId");
String version = getElementText(dependencyElement, "version");
if (groupId != null && artifactId != null && version != null) {
dependencies.add(new Dependency(groupId, artifactId, version));
}
}
} catch (Exception e) {
throw new IOException("Failed to parse Maven POM", e);
}
return dependencies;
}
private List<Dependency> parseGradleDependencies(Path buildGradle) throws IOException {
// Simplified Gradle dependency parsing
// In practice, you'd use a proper Gradle parser or execute Gradle commands
List<Dependency> dependencies = new ArrayList<>();
List<String> lines = Files.readAllLines(buildGradle);
Pattern dependencyPattern = Pattern.compile(
"implementation\\s+['\"]([^:]+):([^:]+):([^:]+)['\"]");
for (String line : lines) {
Matcher matcher = dependencyPattern.matcher(line);
if (matcher.find()) {
dependencies.add(new Dependency(
matcher.group(1), matcher.group(2), matcher.group(3)));
}
}
return dependencies;
}
private DependencyLicenseInfo analyzeDependencyLicense(Dependency dependency) {
// Try multiple sources to determine license
String license = determineLicenseFromMavenCentral(dependency);
if (license == null) {
license = determineLicenseFromClearlyDefined(dependency);
}
if (license == null) {
license = "UNKNOWN";
}
return new DependencyLicenseInfo(dependency, license, normalizeLicense(license));
}
private String determineLicenseFromMavenCentral(Dependency dependency) {
// Implementation to query Maven Central for license information
// This is a simplified version - in practice, use Maven Central API
try {
String url = String.format(
"https://search.maven.org/solrsearch/select?q=g:%s+AND+a:%s+AND+v:%s&wt=json",
dependency.groupId, dependency.artifactId, dependency.version);
// Make HTTP request and parse JSON response
// Extract license information from response
return "Apache-2.0"; // Placeholder
} catch (Exception e) {
return null;
}
}
private String determineLicenseFromClearlyDefined(Dependency dependency) {
// Implementation to query ClearlyDefined service
try {
String url = String.format(
"https://api.clearlydefined.io/definitions/maven/mavencentral/%s/%s/%s",
dependency.groupId, dependency.artifactId, dependency.version);
// Make HTTP request and parse response
return "MIT"; // Placeholder
} catch (Exception e) {
return null;
}
}
private String normalizeLicense(String license) {
return licenseMapping.getOrDefault(license, license);
}
private boolean isLicenseCompliant(DependencyLicenseInfo licenseInfo) {
String normalizedLicense = licenseInfo.getNormalizedLicense();
if (forbiddenLicenses.contains(normalizedLicense)) {
return false;
}
return allowedLicenses.contains(normalizedLicense) ||
"UNKNOWN".equals(normalizedLicense);
}
private LicenseViolation createViolation(DependencyLicenseInfo licenseInfo) {
String severity = forbiddenLicenses.contains(licenseInfo.getNormalizedLicense()) ?
"HIGH" : "MEDIUM";
return new LicenseViolation(
licenseInfo.getDependency(),
licenseInfo.getOriginalLicense(),
licenseInfo.getNormalizedLicense(),
severity
);
}
private String getElementText(Element parent, String tagName) {
NodeList nodes = parent.getElementsByTagName(tagName);
if (nodes.getLength() > 0) {
return nodes.item(0).getTextContent();
}
return null;
}
// Data classes
public static class Dependency {
private final String groupId;
private final String artifactId;
private final String version;
public Dependency(String groupId, String artifactId, String version) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
}
// Getters
public String getGroupId() { return groupId; }
public String getArtifactId() { return artifactId; }
public String getVersion() { return version; }
@Override
public String toString() {
return groupId + ":" + artifactId + ":" + version;
}
}
public static class DependencyLicenseInfo {
private final Dependency dependency;
private final String originalLicense;
private final String normalizedLicense;
public DependencyLicenseInfo(Dependency dependency, String originalLicense, String normalizedLicense) {
this.dependency = dependency;
this.originalLicense = originalLicense;
this.normalizedLicense = normalizedLicense;
}
// Getters
public Dependency getDependency() { return dependency; }
public String getOriginalLicense() { return originalLicense; }
public String getNormalizedLicense() { return normalizedLicense; }
}
public static class ComplianceReport {
private final List<DependencyLicenseInfo> dependencies = new ArrayList<>();
private final List<LicenseViolation> violations = new ArrayList<>();
public void addDependency(DependencyLicenseInfo dependency) {
dependencies.add(dependency);
}
public void addViolation(LicenseViolation violation) {
violations.add(violation);
}
// Getters
public List<DependencyLicenseInfo> getDependencies() { return dependencies; }
public List<LicenseViolation> getViolations() { return violations; }
public boolean isCompliant() { return violations.isEmpty(); }
}
public static class LicenseViolation {
private final Dependency dependency;
private final String originalLicense;
private final String normalizedLicense;
private final String severity;
public LicenseViolation(Dependency dependency, String originalLicense,
String normalizedLicense, String severity) {
this.dependency = dependency;
this.originalLicense = originalLicense;
this.normalizedLicense = normalizedLicense;
this.severity = severity;
}
// Getters
public Dependency getDependency() { return dependency; }
public String getOriginalLicense() { return originalLicense; }
public String getNormalizedLicense() { return normalizedLicense; }
public String getSeverity() { return severity; }
@Override
public String toString() {
return String.format("%s - License: %s (normalized: %s) - Severity: %s",
dependency, originalLicense, normalizedLicense, severity);
}
}
}
Integration with Build Tools
1. Maven Plugin Integration
<!-- pom.xml --> <build> <plugins> <!-- License Maven Plugin --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>license-maven-plugin</artifactId> <version>2.0.0</version> <configuration> <licenseMerges> <licenseMerge>Apache-2.0|The Apache Software License, Version 2.0</licenseMerge> <licenseMerge>MIT|MIT License</licenseMerge> </licenseMerges> <allowedLicenses> <allowedLicense>Apache-2.0</allowedLicense> <allowedLicense>MIT</allowedLicense> <allowedLicense>BSD-2-Clause</allowedLicense> <allowedLicense>BSD-3-Clause</allowedLicense> <allowedLicense>EPL-2.0</allowedLicense> </allowedLicenses> <banedLicenses> <banedLicense>AGPL-3.0</banedLicense> <banedLicense>GPL-3.0</banedLicense> </banedLicenses> </configuration> <executions> <execution> <id>check-licenses</id> <phase>verify</phase> <goals> <goal>check-license</goal> </goals> </execution> <execution> <id>download-licenses</id> <phase>package</phase> <goals> <goal>download-licenses</goal> </goals> </execution> </executions> </plugin> <!-- Custom license check goal --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>license-compliance-check</id> <phase>verify</phase> <goals> <goal>java</goal> </goals> <configuration> <mainClass>com.example.licensecheck.MavenLicenseChecker</mainClass> </configuration> </execution> </executions> </plugin> </plugins> </build>
2. Gradle Plugin Implementation
// build.gradle
plugins {
id 'java'
id 'com.github.hierynomus.license-report' version '0.16.1'
}
// Custom license check task
task checkLicenseCompliance(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'com.example.licensecheck.GradleLicenseChecker'
args = [projectDir.absolutePath]
}
// License report configuration
downloadLicenses {
includeProjectDependencies = true
dependencyConfiguration = 'runtimeClasspath'
allowedLicenses = [
'Apache-2.0',
'MIT',
'BSD-2-Clause',
'BSD-3-Clause',
'EPL-2.0'
]
aliases = [
'The Apache Software License, Version 2.0': 'Apache-2.0',
'Apache License, Version 2.0': 'Apache-2.0',
'MIT License': 'MIT'
]
}
check.dependsOn checkLicenseCompliance
Advanced License Analysis
1. Multi-source License Detection
package com.example.licensecheck.advanced;
import java.util.*;
import java.util.concurrent.*;
import java.net.http.*;
import java.net.URI;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MultiSourceLicenseDetector {
private final HttpClient httpClient;
private final ObjectMapper objectMapper;
private final ExecutorService executorService;
public MultiSourceLicenseDetector() {
this.httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
this.objectMapper = new ObjectMapper();
this.executorService = Executors.newFixedThreadPool(5);
}
public LicenseDetectionResult detectLicense(String groupId, String artifactId, String version) {
List<CompletableFuture<LicenseSource>> futures = Arrays.asList(
queryMavenCentral(groupId, artifactId, version),
queryClearlyDefined(groupId, artifactId, version),
queryOSSIndex(groupId, artifactId, version),
queryGitHub(groupId, artifactId, version)
);
try {
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0]));
LicenseDetectionResult result = allFutures.thenApply(v ->
futures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList())
).thenApply(this::resolveConflicts)
.get(30, TimeUnit.SECONDS);
return result;
} catch (Exception e) {
return new LicenseDetectionResult("UNKNOWN",
Collections.singletonList("Error during license detection: " + e.getMessage()));
}
}
private CompletableFuture<LicenseSource> queryMavenCentral(String groupId, String artifactId, String version) {
return CompletableFuture.supplyAsync(() -> {
try {
String url = String.format(
"https://search.maven.org/solrsearch/select?q=g:%s+AND+a:%s+AND+v:%s&wt=json",
groupId, artifactId, version);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
JsonNode root = objectMapper.readTree(response.body());
JsonNode docs = root.path("response").path("docs");
if (docs.isArray() && docs.size() > 0) {
JsonNode license = docs.get(0).path("license");
if (!license.isMissingNode()) {
return new LicenseSource("MavenCentral",
license.asText(), 0.9);
}
}
}
} catch (Exception e) {
// Log error but don't fail the entire detection
}
return null;
}, executorService);
}
private CompletableFuture<LicenseSource> queryClearlyDefined(String groupId, String artifactId, String version) {
return CompletableFuture.supplyAsync(() -> {
try {
String url = String.format(
"https://api.clearlydefined.io/definitions/maven/mavencentral/%s/%s/%s",
groupId, artifactId, version);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
JsonNode root = objectMapper.readTree(response.body);
JsonNode licensed = root.path("licensed");
if (licensed.has("declared")) {
return new LicenseSource("ClearlyDefined",
licensed.path("declared").asText(), 0.95);
}
}
} catch (Exception e) {
// Log error
}
return null;
}, executorService);
}
private CompletableFuture<LicenseSource> queryOSSIndex(String groupId, String artifactId, String version) {
return CompletableFuture.supplyAsync(() -> {
try {
String url = "https://ossindex.sonatype.org/api/v3/component-report/maven:" +
groupId + ":" + artifactId + "@" + version;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.build();
HttpResponse<String> response = httpClient.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
JsonNode components = objectMapper.readTree(response.body());
if (components.isArray() && components.size() > 0) {
JsonNode licenses = components.get(0).path("licenses");
if (licenses.isArray() && licenses.size() > 0) {
return new LicenseSource("OSSIndex",
licenses.get(0).path("id").asText(), 0.85);
}
}
}
} catch (Exception e) {
// Log error
}
return null;
}, executorService);
}
private CompletableFuture<LicenseSource> queryGitHub(String groupId, String artifactId, String version) {
// Implementation for GitHub license detection
// This would search for the project on GitHub and check its LICENSE file
return CompletableFuture.completedFuture(null);
}
private LicenseDetectionResult resolveConflicts(List<LicenseSource> sources) {
if (sources.isEmpty()) {
return new LicenseDetectionResult("UNKNOWN",
Collections.singletonList("No license information found"));
}
// Group by license and calculate confidence
Map<String, Double> licenseConfidence = new HashMap<>();
for (LicenseSource source : sources) {
licenseConfidence.merge(source.getLicense(),
source.getConfidence(), Double::sum);
}
// Find license with highest confidence
Map.Entry<String, Double> bestLicense = licenseConfidence.entrySet()
.stream()
.max(Map.Entry.comparingByValue())
.orElseThrow();
List<String> sourcesList = sources.stream()
.map(s -> s.getSource() + ": " + s.getLicense())
.collect(Collectors.toList());
return new LicenseDetectionResult(bestLicense.getKey(), sourcesList);
}
public void shutdown() {
executorService.shutdown();
}
// Data classes
public static class LicenseSource {
private final String source;
private final String license;
private final double confidence;
public LicenseSource(String source, String license, double confidence) {
this.source = source;
this.license = license;
this.confidence = confidence;
}
// Getters
public String getSource() { return source; }
public String getLicense() { return license; }
public double getConfidence() { return confidence; }
}
public static class LicenseDetectionResult {
private final String detectedLicense;
private final List<String> sources;
public LicenseDetectionResult(String detectedLicense, List<String> sources) {
this.detectedLicense = detectedLicense;
this.sources = sources;
}
// Getters
public String getDetectedLicense() { return detectedLicense; }
public List<String> getSources() { return sources; }
}
}
2. License Compatibility Checker
package com.example.licensecheck.compatibility;
import java.util.*;
public class LicenseCompatibilityChecker {
private final Map<String, Set<String>> compatibleLicenses;
private final Map<String, Set<String>> incompatibleLicenses;
public LicenseCompatibilityChecker() {
this.compatibleLicenses = new HashMap<>();
this.incompatibleLicenses = new HashMap<>();
initializeCompatibilityMatrix();
}
private void initializeCompatibilityMatrix() {
// Apache 2.0 compatibility
compatibleLicenses.put("Apache-2.0", new HashSet<>(Arrays.asList(
"MIT", "BSD-2-Clause", "BSD-3-Clause", "Apache-2.0"
)));
// MIT compatibility
compatibleLicenses.put("MIT", new HashSet<>(Arrays.asList(
"Apache-2.0", "MIT", "BSD-2-Clause", "BSD-3-Clause"
)));
// GPL incompatibility
incompatibleLicenses.put("GPL-3.0", new HashSet<>(Arrays.asList(
"Apache-2.0" // GPL is incompatible with Apache 2.0
)));
// AGPL incompatibility
incompatibleLicenses.put("AGPL-3.0", new HashSet<>(Arrays.asList(
"Apache-2.0", "MIT", "BSD-2-Clause", "BSD-3-Clause"
)));
}
public CompatibilityResult checkCompatibility(String projectLicense, Set<String> dependencyLicenses) {
List<String> conflicts = new ArrayList<>();
Set<String> warnings = new HashSet<>();
for (String dependencyLicense : dependencyLicenses) {
CompatibilityStatus status = checkLicensePairCompatibility(
projectLicense, dependencyLicense);
switch (status) {
case INCOMPATIBLE:
conflicts.add(String.format(
"Project license %s is incompatible with dependency license %s",
projectLicense, dependencyLicense));
break;
case WARNING:
warnings.add(String.format(
"Potential compatibility issue between %s and %s",
projectLicense, dependencyLicense));
break;
case COMPATIBLE:
// No action needed
break;
}
}
return new CompatibilityResult(conflicts, warnings);
}
private CompatibilityStatus checkLicensePairCompatibility(String license1, String license2) {
if (license1.equals(license2)) {
return CompatibilityStatus.COMPATIBLE;
}
// Check explicit incompatibility
if (incompatibleLicenses.getOrDefault(license1, Collections.emptySet())
.contains(license2)) {
return CompatibilityStatus.INCOMPATIBLE;
}
if (incompatibleLicenses.getOrDefault(license2, Collections.emptySet())
.contains(license1)) {
return CompatibilityStatus.INCOMPATIBLE;
}
// Check explicit compatibility
if (compatibleLicenses.getOrDefault(license1, Collections.emptySet())
.contains(license2)) {
return CompatibilityStatus.COMPATIBLE;
}
// Unknown combination - treat as warning
return CompatibilityStatus.WARNING;
}
public enum CompatibilityStatus {
COMPATIBLE, WARNING, INCOMPATIBLE
}
public static class CompatibilityResult {
private final List<String> conflicts;
private final Set<String> warnings;
private final boolean isCompatible;
public CompatibilityResult(List<String> conflicts, Set<String> warnings) {
this.conflicts = conflicts;
this.warnings = warnings;
this.isCompatible = conflicts.isEmpty();
}
// Getters
public List<String> getConflicts() { return conflicts; }
public Set<String> getWarnings() { return warnings; }
public boolean isCompatible() { return isCompatible; }
public String getSummary() {
if (isCompatible) {
return String.format("Compatible (with %d warnings)", warnings.size());
} else {
return String.format("INCOMPATIBLE (%d conflicts, %d warnings)",
conflicts.size(), warnings.size());
}
}
}
}
Reporting and Export
1. Comprehensive License Report Generator
package com.example.licensecheck.reporting;
import java.io.*;
import java.util.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.opencsv.CSVWriter;
public class LicenseReportGenerator {
private final ObjectMapper jsonMapper;
private final XmlMapper xmlMapper;
public LicenseReportGenerator() {
this.jsonMapper = new ObjectMapper();
this.xmlMapper = new XmlMapper();
}
public void generateJsonReport(LicenseComplianceChecker.ComplianceReport report,
File outputFile) throws IOException {
Map<String, Object> reportData = new LinkedHashMap<>();
reportData.put("generated", new Date());
reportData.put("complianceStatus", report.isCompliant() ? "COMPLIANT" : "NON_COMPLIANT");
List<Map<String, Object>> dependencies = new ArrayList<>();
for (LicenseComplianceChecker.DependencyLicenseInfo dep : report.getDependencies()) {
Map<String, Object> depInfo = new LinkedHashMap<>();
depInfo.put("groupId", dep.getDependency().getGroupId());
depInfo.put("artifactId", dep.getDependency().getArtifactId());
depInfo.put("version", dep.getDependency().getVersion());
depInfo.put("license", dep.getOriginalLicense());
depInfo.put("normalizedLicense", dep.getNormalizedLicense());
depInfo.put("status", getDependencyStatus(dep, report));
dependencies.add(depInfo);
}
reportData.put("dependencies", dependencies);
List<Map<String, Object>> violations = new ArrayList<>();
for (LicenseComplianceChecker.LicenseViolation violation : report.getViolations()) {
Map<String, Object> violationInfo = new LinkedHashMap<>();
violationInfo.put("dependency", violation.getDependency().toString());
violationInfo.put("license", violation.getOriginalLicense());
violationInfo.put("normalizedLicense", violation.getNormalizedLicense());
violationInfo.put("severity", violation.getSeverity());
violations.add(violationInfo);
}
reportData.put("violations", violations);
jsonMapper.writerWithDefaultPrettyPrinter().writeValue(outputFile, reportData);
}
public void generateCsvReport(LicenseComplianceChecker.ComplianceReport report,
File outputFile) throws IOException {
try (CSVWriter writer = new CSVWriter(new FileWriter(outputFile))) {
// Write header
writer.writeNext(new String[]{
"Group ID", "Artifact ID", "Version",
"License", "Normalized License", "Status"
});
// Write data
for (LicenseComplianceChecker.DependencyLicenseInfo dep : report.getDependencies()) {
String status = getDependencyStatus(dep, report);
writer.writeNext(new String[]{
dep.getDependency().getGroupId(),
dep.getDependency().getArtifactId(),
dep.getDependency().getVersion(),
dep.getOriginalLicense(),
dep.getNormalizedLicense(),
status
});
}
}
}
public void generateHtmlReport(LicenseComplianceChecker.ComplianceReport report,
File outputFile) throws IOException {
String htmlTemplate = """
<!DOCTYPE html>
<html>
<head>
<title>License Compliance Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.compliant { color: green; }
.non-compliant { color: red; }
.warning { color: orange; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1>License Compliance Report</h1>
<p>Generated: %s</p>
<p>Overall Status: <span class="%s">%s</span></p>
<h2>Summary</h2>
<p>Total Dependencies: %d</p>
<p>Violations: %d</p>
<h2>Dependencies</h2>
<table>
<tr>
<th>Dependency</th>
<th>License</th>
<th>Normalized License</th>
<th>Status</th>
</tr>
%s
</table>
<h2>Violations</h2>
%s
</body>
</html>
""";
String overallStatus = report.isCompliant() ? "COMPLIANT" : "NON-COMPLIANT";
String statusClass = report.isCompliant() ? "compliant" : "non-compliant";
StringBuilder dependenciesRows = new StringBuilder();
for (LicenseComplianceChecker.DependencyLicenseInfo dep : report.getDependencies()) {
String status = getDependencyStatus(dep, report);
String rowClass = "COMPLIANT".equals(status) ? "compliant" :
"WARNING".equals(status) ? "warning" : "non-compliant";
dependenciesRows.append(String.format(
"<tr class=\"%s\"><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>",
rowClass,
dep.getDependency().toString(),
dep.getOriginalLicense(),
dep.getNormalizedLicense(),
status
));
}
StringBuilder violationsSection = new StringBuilder();
if (!report.getViolations().isEmpty()) {
violationsSection.append("<table><tr><th>Dependency</th><th>License</th><th>Severity</th></tr>");
for (LicenseComplianceChecker.LicenseViolation violation : report.getViolations()) {
violationsSection.append(String.format(
"<tr><td>%s</td><td>%s</td><td>%s</td></tr>",
violation.getDependency().toString(),
violation.getOriginalLicense(),
violation.getSeverity()
));
}
violationsSection.append("</table>");
} else {
violationsSection.append("<p>No violations found.</p>");
}
String html = String.format(htmlTemplate,
new Date(),
statusClass,
overallStatus,
report.getDependencies().size(),
report.getViolations().size(),
dependenciesRows.toString(),
violationsSection.toString()
);
Files.write(outputFile.toPath(), html.getBytes());
}
private String getDependencyStatus(LicenseComplianceChecker.DependencyLicenseInfo dep,
LicenseComplianceChecker.ComplianceReport report) {
boolean hasViolation = report.getViolations().stream()
.anyMatch(v -> v.getDependency().equals(dep.getDependency()));
if (hasViolation) {
return "NON-COMPLIANT";
} else if ("UNKNOWN".equals(dep.getNormalizedLicense())) {
return "WARNING";
} else {
return "COMPLIANT";
}
}
}
Best Practices and Integration
1. CI/CD Integration
package com.example.licensecheck.ci;
public class CICDIntegration {
public static void main(String[] args) {
LicenseComplianceChecker checker = new LicenseComplianceChecker();
LicenseReportGenerator reporter = new LicenseReportGenerator();
try {
Path projectDir = Paths.get(args.length > 0 ? args[0] : ".");
// Check license compliance
LicenseComplianceChecker.ComplianceReport report =
checker.checkProjectCompliance(projectDir);
// Generate reports
reporter.generateJsonReport(report, new File("license-report.json"));
reporter.generateHtmlReport(report, new File("license-report.html"));
reporter.generateCsvReport(report, new File("license-report.csv"));
// Exit with error code if non-compliant
if (!report.isCompliant()) {
System.err.println("License compliance check FAILED");
System.err.println("Violations found:");
for (LicenseComplianceChecker.LicenseViolation violation : report.getViolations()) {
System.err.println(" - " + violation);
}
System.exit(1);
} else {
System.out.println("License compliance check PASSED");
}
} catch (Exception e) {
System.err.println("Error during license compliance check: " + e.getMessage());
System.exit(2);
}
}
}
2. Configuration Management
package com.example.licensecheck.config;
import java.io.*;
import java.util.*;
public class LicenseCheckConfig {
private Set<String> allowedLicenses;
private Set<String> forbiddenLicenses;
private Map<String, String> licenseMapping;
private Map<String, String> licenseAliases;
private boolean failOnUnknown;
private boolean failOnWarning;
public LicenseCheckConfig() {
// Default configuration
this.allowedLicenses = new HashSet<>(Arrays.asList(
"Apache-2.0", "MIT", "BSD-2-Clause", "BSD-3-Clause",
"EPL-2.0", "LGPL-2.1", "LGPL-3.0", "ISC"
));
this.forbiddenLicenses = new HashSet<>(Arrays.asList(
"AGPL-3.0", "GPL-3.0", "SSPL-1.0"
));
this.failOnUnknown = true;
this.failOnWarning = false;
initializeMappings();
}
public static LicenseCheckConfig loadFromFile(File configFile) throws IOException {
LicenseCheckConfig config = new LicenseCheckConfig();
if (configFile.exists()) {
Properties props = new Properties();
try (FileInputStream fis = new FileInputStream(configFile)) {
props.load(fis);
}
// Load allowed licenses
String allowed = props.getProperty("allowed.licenses");
if (allowed != null) {
config.allowedLicenses = new HashSet<>(Arrays.asList(allowed.split(",")));
}
// Load forbidden licenses
String forbidden = props.getProperty("forbidden.licenses");
if (forbidden != null) {
config.forbiddenLicenses = new HashSet<>(Arrays.asList(forbidden.split(",")));
}
config.failOnUnknown = Boolean.parseBoolean(props.getProperty("fail.on.unknown", "true"));
config.failOnWarning = Boolean.parseBoolean(props.getProperty("fail.on.warning", "false"));
}
return config;
}
private void initializeMappings() {
licenseMapping = new HashMap<>();
licenseMapping.put("The Apache Software License, Version 2.0", "Apache-2.0");
licenseMapping.put("Apache License, Version 2.0", "Apache-2.0");
licenseMapping.put("MIT License", "MIT");
licenseMapping.put("BSD License", "BSD-2-Clause");
licenseAliases = new HashMap<>();
licenseAliases.put("ASL 2.0", "Apache-2.0");
licenseAliases.put("Apache 2", "Apache-2.0");
licenseAliases.put("Apache License 2.0", "Apache-2.0");
}
// Getters and setters
public Set<String> getAllowedLicenses() { return allowedLicenses; }
public Set<String> getForbiddenLicenses() { return forbiddenLicenses; }
public Map<String, String> getLicenseMapping() { return licenseMapping; }
public Map<String, String> getLicenseAliases() { return licenseAliases; }
public boolean isFailOnUnknown() { return failOnUnknown; }
public boolean isFailOnWarning() { return failOnWarning; }
}
This comprehensive license compliance checking system provides:
- Dependency extraction from build files
- Multi-source license detection
- License normalization and mapping
- Compatibility checking
- Comprehensive reporting in multiple formats
- CI/CD integration
- Configurable policies
The system can be integrated into your build process to automatically enforce license compliance policies and generate detailed reports for legal review.