google-site-verification: google61fe8ba583a51912.html
License Compliance Checking in Java

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:

  1. Dependency extraction from build files
  2. Multi-source license detection
  3. License normalization and mapping
  4. Compatibility checking
  5. Comprehensive reporting in multiple formats
  6. CI/CD integration
  7. Configurable policies

The system can be integrated into your build process to automatically enforce license compliance policies and generate detailed reports for legal review.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper