Java tutorial
/** * ========================================================================================== * = JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION = * ========================================================================================== * * http://www.jahia.com * * Copyright (C) 2002-2018 Jahia Solutions Group SA. All rights reserved. * * THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES: * 1/GPL OR 2/JSEL * * 1/ GPL * ================================================================================== * * IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * 2/ JSEL - Commercial and Supported Versions of the program * =================================================================================== * * IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * Alternatively, commercial and supported versions of the program - also known as * Enterprise Distributions - must be used in accordance with the terms and conditions * contained in a separate written agreement between you and Jahia Solutions Group SA. * * If you are unsure which license is appropriate for your use, * please contact the sales department at sales@jahia.com. */ package org.jahia.utils.maven.plugin.osgi; import aQute.bnd.header.Attrs; import aQute.bnd.osgi.*; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.felix.bundleplugin.BundlePlugin; import org.apache.felix.bundleplugin.DependencyEmbedder; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.jahia.utils.maven.plugin.SLF4JLoggerToMojoLogBridge; import org.jahia.utils.maven.plugin.support.AetherHelper; import org.jahia.utils.maven.plugin.support.AetherHelperFactory; import org.jahia.utils.maven.plugin.support.ArtifactProcessor; import org.jahia.utils.maven.plugin.support.MavenAetherHelperUtils; import org.jahia.utils.osgi.BundleUtils; import org.jahia.utils.osgi.ManifestValueClause; import org.jahia.utils.osgi.PackageUtils; import org.jahia.utils.osgi.PropertyFileUtils; import org.jahia.utils.osgi.parsers.PackageInfo; import org.jahia.utils.osgi.parsers.Parsers; import org.jahia.utils.osgi.parsers.ParsingContext; import org.slf4j.Logger; import java.io.*; import java.util.*; import java.util.jar.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A maven goal to scan the project for package dependencies, useful for building OSGi Import-Package * Manifest header. * <p/> * This goal is currently capable of scanning: * - TLD files in dependencies and the project * - JSP for page import and Taglib references (tag files are not supported yet) * - Drools rule definition imports * - JBPM Workflow definition files * - Spring context files * - JCR CND content definition files for node type definition and references * - Groovy files * * @goal dependencies * @requiresDependencyResolution test * @requiresDependencyCollection test * @todo add support for JSP tag files, more... */ public class DependenciesMojo extends BundlePlugin { private static final String OSGI_CAPABILITY_MODULE_DEPENDENCIES_KEY = "moduleIdentifier"; private static final String OSGI_CAPABILITY_MODULE_DEPENDENCIES = "com.jahia.modules.dependencies"; protected static final Set<String> SUPPORTED_FILE_EXTENSIONS_TO_SCAN = new HashSet<String>( Arrays.asList("jsp", "jspf", "tag", "tagf", "cnd", "drl", "xml", "groovy")); protected static final Set<String> DEPENDENCIES_SCAN_PACKAGING = new HashSet<String>( Arrays.asList("jar", "war")); protected static final Set<String> DEPENDENCIES_SCAN_SCOPES = new HashSet<String>( Arrays.asList(Artifact.SCOPE_PROVIDED, Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME)); private AetherHelper aetherHelper; /** * @component * @required * @readonly */ protected PlexusContainer container; /** * The current build session instance. * * @parameter expression="${session}" * @required * @readonly */ protected MavenSession mavenSession; /** * @parameter expression="${project}" * @readonly * @required */ protected MavenProject project; /** * @parameter */ protected List<String> artifactExcludes = new ArrayList<String>(); /** * @parameter */ protected List<String> scanDirectories = new ArrayList<String>(); /** * @parameter */ protected List<String> excludeFromDirectoryScan = new ArrayList<String>(); /** * @parameter */ protected List<String> excludedJarEntries; protected List<Pattern> excludedJarEntryPatterns; /** * @parameter default-value="${project.build.outputDirectory}" */ protected String projectOutputDirectory; /** * The directory for the generated JAR. * * @parameter expression="${project.build.directory}" * @required */ protected String projectBuildDirectory; /** * @parameter */ protected File propertiesInputFile; /** * @parameter */ protected File propertiesOutputFile; /** * @parameter default-value="org.osgi.framework.system.capabilities.extra" */ protected String systemExtraCapabilitiesPropertyName = "org.osgi.framework.system.capabilities.extra"; /** * @parameter default-value="true" */ protected boolean contentDefinitionCapabilitiesActivated = true; /** * @parameter default-value="" expression="${jahia.modules.importPackage}" */ protected String existingImports = ""; /** * @parameter expression="${user.home}/.m2/dependency-cache"; */ protected String dependencyParsingCacheDirectory = null; /** * @parameter default-value="true" */ protected boolean jahiaDependsCapabilitiesActivated = true; /** * @parameter default-value="," */ protected String jahiaDependsCapabilitiesPrefix = ","; protected List<Pattern> artifactExclusionPatterns = new ArrayList<Pattern>(); protected Logger logger = new SLF4JLoggerToMojoLogBridge(getLog()); protected ParsingContextCache parsingContextCache; protected Collection<String> inlinedPaths = new LinkedHashSet<String>(); protected Collection<Artifact> embeddedArtifacts = new LinkedHashSet<Artifact>(); protected AetherHelper getAetherHelper() throws MojoExecutionException { if (aetherHelper == null) { aetherHelper = AetherHelperFactory.create(container, project, mavenSession, getLog()); } return aetherHelper; } @Override public void setLog(Log log) { super.setLog(log); logger = new SLF4JLoggerToMojoLogBridge(log); } @Override public void execute() throws MojoExecutionException { setBuildDirectory(projectBuildDirectory); setOutputDirectory(new File(projectOutputDirectory)); if (project.getGroupId().equals("org.jahia.modules") && project.getArtifactId().equals("jahia-modules") || !"jar".equals(project.getPackaging()) && !"bundle".equals(project.getPackaging()) && !"war".equals(project.getPackaging())) { return; } if (excludeFromDirectoryScan == null || excludeFromDirectoryScan.size() == 0) { excludeFromDirectoryScan.add("**/legacyMappings/*"); } long startTime = System.currentTimeMillis(); ParsingContext projectParsingContext = new ParsingContext( MavenAetherHelperUtils.getCoords(project.getArtifact()), 0, 0, project.getArtifactId(), project.getBasedir().getPath(), project.getVersion(), null); parsingContextCache = new ParsingContextCache(new File(dependencyParsingCacheDirectory), null); Map<String, String> originalInstructions = new LinkedHashMap<String, String>(); if (project.getPlugin("org.apache.felix:maven-bundle-plugin") != null) { try { Xpp3Dom felixBundlePluginConfiguration = (Xpp3Dom) project .getPlugin("org.apache.felix:maven-bundle-plugin").getConfiguration(); Xpp3Dom instructionsDom = felixBundlePluginConfiguration.getChild("instructions"); for (Xpp3Dom instructionChild : instructionsDom.getChildren()) { originalInstructions.put(instructionChild.getName(), instructionChild.getValue()); } excludeDependencies = felixBundlePluginConfiguration.getChild("excludeDependencies").getValue(); } catch (Exception e) { // no overrides } Properties properties = new Properties(); try { Builder builder = getOSGiBuilder(project, originalInstructions, properties, getClasspath(project)); resolveEmbeddedDependencies(project, builder); } catch (Exception e) { throw new MojoExecutionException("Error trying to process bundle plugin instructions", e); } } else { // we are not in a bundle project if (project.getPackaging().equals("war")) { for (Artifact artifact : project.getArtifacts()) { if (!artifact.getScope().toLowerCase().equals("provided") && !artifact.getScope().toLowerCase().equals("test")) { // artifact will be embedded in WAR embeddedArtifacts.add(artifact); } } } } List<PackageInfo> existingPackageImports = getExistingImportPackages(projectParsingContext); projectParsingContext.addAllPackageImports(existingPackageImports); buildExclusionPatterns(); long timer = System.currentTimeMillis(); try { scanClassesBuildDirectory(projectParsingContext); getLog().info("Scanned classes directory in " + (System.currentTimeMillis() - timer) + " ms. Found " + projectParsingContext.getLocalPackages().size() + " project packages."); timer = System.currentTimeMillis(); int scanned = scanDependencies(projectParsingContext); getLog().info("Scanned " + scanned + " project dependencies in " + (System.currentTimeMillis() - timer) + " ms. Currently we have " + projectParsingContext.getLocalPackages().size() + " project packages."); } catch (IOException e) { throw new MojoExecutionException("Error while scanning dependencies", e); } catch (DependencyResolutionRequiredException e) { throw new MojoExecutionException("Error while scanning dependencies", e); } if (scanDirectories.isEmpty()) { scanDirectories.add(project.getBasedir() + "/src/main/resources"); scanDirectories.add(project.getBasedir() + "/src/main/import"); scanDirectories.add(project.getBasedir() + "/src/main/webapp"); } timer = System.currentTimeMillis(); for (String scanDirectory : scanDirectories) { File scanDirectoryFile = new File(scanDirectory); if (!scanDirectoryFile.exists()) { getLog().debug("Couldn't find directory " + scanDirectoryFile + ", skipping !"); continue; } try { getLog().info("Scanning resource directory " + scanDirectoryFile + "..."); processDirectoryTlds(scanDirectoryFile, project.getVersion(), projectParsingContext); processDirectory(scanDirectoryFile, false, project.getVersion(), projectParsingContext); } catch (IOException e) { throw new MojoExecutionException("Error processing resource directory " + scanDirectoryFile, e); } } getLog().info("Scanned resource directories in " + (System.currentTimeMillis() - timer) + " ms. Currently we have " + projectParsingContext.getLocalPackages().size() + " project packages."); if (getLog().isDebugEnabled()) { getLog().debug("Found project packages (potential exports) :"); for (PackageInfo projectPackage : projectParsingContext.getLocalPackages()) { getLog().debug(" " + projectPackage); } } projectParsingContext.postProcess(); SortedSet<PackageInfo> childLocalPackagesToRemoveFromImport = projectParsingContext .getChildrenLocalPackagesToRemoveFromImports(); for (PackageInfo childLocalPackageToRemove : childLocalPackagesToRemoveFromImport) { PackageUtils.removeMatchingVersions(projectParsingContext.getPackageImports(), childLocalPackageToRemove); } if (projectParsingContext.getUnresolvedTaglibUris().size() > 0) { for (Map.Entry<String, Set<String>> unresolvedUrisForJsp : projectParsingContext .getUnresolvedTaglibUris().entrySet()) { for (String unresolvedUriForJsp : unresolvedUrisForJsp.getValue()) { getLogger().warn("JSP " + unresolvedUrisForJsp.getKey() + " has a reference to taglib " + unresolvedUriForJsp + " that is not in the project's dependencies !"); } } } StringBuilder generatedPackageBuffer = new StringBuilder(256); int i = 0; Map<String, String> importOverrides = getPackageImportOverrides(); if (importOverrides != null) { getLog().info("Considering provided Import-Package-Override: " + StringUtils.join(importOverrides.values(), ", ")); } Set<String> uniquePackageImports = new TreeSet<String>(); for (PackageInfo packageImport : projectParsingContext.getPackageImports()) { String packageImportName = null; if (importOverrides != null && importOverrides.containsKey(packageImport.getName())) { packageImportName = importOverrides.get(packageImport.getName()); } else { packageImportName = packageImport.toString(false); } if (uniquePackageImports.contains(packageImport.getName())) { continue; } generatedPackageBuffer.append(packageImportName); if (i < projectParsingContext.getPackageImports().size() - 1) { generatedPackageBuffer.append(",\n"); } uniquePackageImports.add(packageImport.getName()); i++; } getLog().info( "Generated " + projectParsingContext.getPackageImports().size() + " package imports for project."); getLog().info("Found referenced tag library URIs (from JSPs) :"); for (String taglibUri : projectParsingContext.getTaglibUris()) { boolean foundInDependencies = projectParsingContext.getTaglibPackages().containsKey(taglibUri); String foundMessage = ""; if (!foundInDependencies) { foundMessage = "NOT FOUND"; } if (foundInDependencies) { boolean externalTagLib = projectParsingContext.getExternalTaglibs().get(taglibUri); if (externalTagLib) { foundMessage += " provided"; } } getLog().info(" " + taglibUri + " " + foundMessage); } StringBuilder contentTypeDefinitionsBuffer = new StringBuilder(256); if (projectParsingContext.getContentTypeDefinitions().size() > 0) { contentTypeDefinitionsBuffer.append("com.jahia.services.content; nodetypes:List<String>=\""); i = 0; for (String contentTypeName : projectParsingContext.getContentTypeDefinitions()) { contentTypeDefinitionsBuffer.append(contentTypeName); if (i < projectParsingContext.getContentTypeDefinitions().size() - 1) { contentTypeDefinitionsBuffer.append(","); } i++; } contentTypeDefinitionsBuffer.append("\""); } if (contentDefinitionCapabilitiesActivated) { getLog().info("Found " + projectParsingContext.getContentTypeDefinitions().size() + " new content node type definitions in project."); getLog().debug("Provide-Capability: " + contentTypeDefinitionsBuffer.toString()); project.getProperties().put("jahia.plugin.providedNodeTypes", contentTypeDefinitionsBuffer.toString()); } else { // we set an empty property so that Maven will not fail the build with a non-existing property project.getProperties().put("jahia.plugin.providedNodeTypes", ""); } StringBuilder contentTypeReferencesBuffer = new StringBuilder(); if (projectParsingContext.getContentTypeReferences().size() > 0) { i = 0; for (String contentTypeReference : projectParsingContext.getContentTypeReferences()) { contentTypeReferencesBuffer.append("com.jahia.services.content; filter:=\"(nodetypes=") .append(contentTypeReference).append(")\""); if (i < projectParsingContext.getContentTypeReferences().size() - 1) { contentTypeReferencesBuffer.append(","); } i++; } } if (contentDefinitionCapabilitiesActivated) { getLog().info("Found " + projectParsingContext.getContentTypeReferences().size() + " content node type definitions referenced in project."); getLog().debug("Require-Capability: " + contentTypeReferencesBuffer.toString()); project.getProperties().put("jahia.plugin.requiredNodeTypes", contentTypeReferencesBuffer.toString()); } else { // we set an empty property so that Maven will not fail the build with a non-existing property project.getProperties().put("jahia.plugin.requiredNodeTypes", ""); } project.getProperties().put("jahia.plugin.requiredModulesCapabilities", ""); project.getProperties().put("jahia.plugin.providedModulesCapabilities", ""); if (jahiaDependsCapabilitiesActivated) { try { if (originalInstructions.containsKey("Jahia-Depends") && !ArrayUtils.contains( StringUtils.split(originalInstructions.get("_removeheaders"), ", \n"), "Jahia-Depends")) { getLog().info("Building OSGi capabilities for Jahia module dependencies..."); StringBuilder jahiaDependsRequireCapabilities = new StringBuilder(); jahiaDependsRequireCapabilities.append(jahiaDependsCapabilitiesPrefix); String jahiaDependsValue = originalInstructions.get("Jahia-Depends"); if (StringUtils.isNotEmpty(jahiaDependsValue)) { String[] jahiaDependsArray = jahiaDependsValue.split(","); String skipRequireCapabilityValue = originalInstructions .get("Jahia-Depends-Skip-Require-Capability"); Set<String> jahiaDependenciesToSkip = StringUtils.isNotEmpty(skipRequireCapabilityValue) ? new HashSet<String>( Arrays.asList(StringUtils.split(skipRequireCapabilityValue, ", \n"))) : Collections.<String>emptySet(); int counter = 0; for (String jahiaDependsEntry : jahiaDependsArray) { jahiaDependsEntry = jahiaDependsEntry.trim(); if (jahiaDependsEntry.length() == 0 || jahiaDependenciesToSkip.contains(jahiaDependsEntry)) { continue; } jahiaDependsRequireCapabilities .append(OSGI_CAPABILITY_MODULE_DEPENDENCIES + "; filter:=\"(" + OSGI_CAPABILITY_MODULE_DEPENDENCIES_KEY + "=") .append(jahiaDependsEntry).append(")\""); if (counter < jahiaDependsArray.length - 1) { jahiaDependsRequireCapabilities.append(","); } counter++; } project.getProperties().put("jahia.plugin.requiredModulesCapabilities", jahiaDependsRequireCapabilities.toString()); } } StringBuilder cap = new StringBuilder(); cap.append(jahiaDependsCapabilitiesPrefix).append(OSGI_CAPABILITY_MODULE_DEPENDENCIES + "; " + OSGI_CAPABILITY_MODULE_DEPENDENCIES_KEY + "=\"").append(project.getArtifactId()) .append("\""); if (project.getName() != null) { cap.append(", " + OSGI_CAPABILITY_MODULE_DEPENDENCIES + "; " + OSGI_CAPABILITY_MODULE_DEPENDENCIES_KEY + "=\"").append(project.getName()) .append("\""); } project.getProperties().put("jahia.plugin.providedModulesCapabilities", cap.toString()); } catch (Exception e) { getLog().error("Error generating capabilities from Jahia-Depends", e); } } String generatedPackageList = generatedPackageBuffer.toString(); project.getProperties().put("jahia.plugin.projectPackageImport", generatedPackageList); getLog().debug("Set project property jahia.plugin.projectPackageImport to package import list value: "); getLog().debug(generatedPackageList); if (propertiesOutputFile != null) { String[] extraCapabilitiesPropertyValue = new String[] { contentTypeDefinitionsBuffer.toString() }; try { PropertyFileUtils.updatePropertyFile(propertiesInputFile, propertiesOutputFile, systemExtraCapabilitiesPropertyName, extraCapabilitiesPropertyValue, new SLF4JLoggerToMojoLogBridge(getLog())); } catch (IOException e) { getLog().warn("Error saving extra system capabilities to file " + propertiesOutputFile); } } getLog().info("Took " + (System.currentTimeMillis() - startTime) + " ms for the dependencies analysis"); } public List<PackageInfo> getExistingImportPackages(ParsingContext projectParsingContext) throws MojoExecutionException { List<PackageInfo> existingPackageImports = new ArrayList<PackageInfo>(); if (existingImports != null) { try { List<ManifestValueClause> existingImportValueClauses = BundleUtils .getHeaderClauses("Import-Package", existingImports); for (ManifestValueClause existingImportValueClause : existingImportValueClauses) { String clauseVersion = existingImportValueClause.getAttributes().get("version"); String clauseResolution = existingImportValueClause.getDirectives().get("resolution"); boolean optionalClause = false; if ("optional".equals(clauseResolution)) { optionalClause = true; } for (String existingImportPath : existingImportValueClause.getPaths()) { existingPackageImports.add(new PackageInfo(existingImportPath, clauseVersion, optionalClause, "Maven plugin configuration", projectParsingContext)); } } } catch (IOException e) { throw new MojoExecutionException("Error while parsing existing import clauses", e); } /* String[] existingImportsArray = StringUtils.split(existingImports, ", \n\r"); for (String existingImport : existingImportsArray) { projectParsingContext.addPackageImport(new PackageInfo(existingImport, null, false, "Maven plugin configuration", projectParsingContext)); } */ } return existingPackageImports; } private Map<String, String> getPackageImportOverrides() { Map<String, String> overrides = null; String importPackageOverride = null; try { importPackageOverride = ((Xpp3Dom) project.getPlugin("org.apache.felix:maven-bundle-plugin") .getConfiguration()).getChild("instructions").getChild("Import-Package-Override").getValue(); } catch (Exception e) { // no overrides } if (StringUtils.isNotEmpty(importPackageOverride)) { overrides = new HashMap<String, String>(); for (String token : StringUtils.split(importPackageOverride, ",\n\r")) { token = token.trim(); if (token.length() > 0) { overrides.put(token.contains(";") ? StringUtils.substringBefore(token, ";").trim() : token, token); } } } return overrides != null && !overrides.isEmpty() ? overrides : null; } protected void scanClassesBuildDirectory(ParsingContext parsingContext) throws IOException, DependencyResolutionRequiredException, MojoExecutionException { File outputDirectoryFile = new File(projectOutputDirectory); if (!outputDirectoryFile.exists()) { getLog().warn("Couldn't scan project output directory " + outputDirectoryFile + " because it doesn't exist !"); return; } getLog().info("Scanning project build directory " + outputDirectoryFile.getCanonicalPath()); DirectoryScanner ds = new DirectoryScanner(); String[] excludes = { "META-INF/**", "OSGI-INF/**", "OSGI-OPT/**", "WEB-INF/**" }; ds.setExcludes(excludes); ds.setBasedir(outputDirectoryFile); ds.setCaseSensitive(true); ds.scan(); String[] includedFiles = ds.getIncludedFiles(); for (String includedFile : includedFiles) { processLocalPackageEntry(includedFile, File.separator, outputDirectoryFile.getCanonicalPath(), project.getVersion(), false, parsingContext); } } protected void buildExclusionPatterns() { if (artifactExcludes != null) { for (String artifactExclude : artifactExcludes) { int colonPos = artifactExclude.indexOf(":"); String groupPattern = ".*"; String artifactPattern; if (colonPos > -1) { groupPattern = artifactExclude.substring(0, colonPos); artifactPattern = artifactExclude.substring(colonPos + 1); } else { artifactPattern = artifactExclude; } groupPattern = groupPattern.replaceAll("\\.", "\\\\."); groupPattern = groupPattern.replaceAll("\\*", ".*"); artifactPattern = artifactPattern.replaceAll("\\.", "\\\\."); artifactPattern = artifactPattern.replaceAll("\\*", ".*"); artifactExclusionPatterns.add(Pattern.compile(groupPattern + ":" + artifactPattern)); } } if (artifactExclusionPatterns.size() > 0) { getLog().info("Configured " + artifactExclusionPatterns.size() + " artifact exclusions for scanning project dependencies: " + Arrays.toString(artifactExclusionPatterns.toArray())); } else { getLog().info("No artifact exclusions specified. Will scan all related dependencies of the project."); } excludedJarEntryPatterns = new LinkedList<Pattern>(); excludedJarEntryPatterns.add(Pattern.compile(".*/legacyDefinitions/.*\\.cnd")); if (excludedJarEntries != null && excludedJarEntries.size() > 0) { for (String p : excludedJarEntries) { excludedJarEntryPatterns.add(Pattern.compile(p.trim())); } } } protected int scanDependencies(final ParsingContext projectParsingContext) throws IOException, MojoExecutionException { getLog().info("Scanning project dependencies..."); int scanned = 0; for (Artifact artifact : project.getDependencyArtifacts()) { if (artifact.isOptional()) { getLog().info("Scanning optional dependency " + artifact + "..."); } if (!DEPENDENCIES_SCAN_PACKAGING.contains(artifact.getType()) || !DEPENDENCIES_SCAN_SCOPES.contains(artifact.getScope()) || isExcludedFromScan(artifact)) { continue; } final int scannedCopy = scanned; final DependenciesMojo dependenciesMojo = this; getAetherHelper().processArtifactAndDependencies(artifact, artifact.isOptional(), new ArtifactProcessor() { @Override public boolean isExternal(Artifact artifact) { for (String inlinedPath : inlinedPaths) { if (inlinedPath.startsWith(artifact.getFile().getPath())) { return false; } } for (Artifact embeddedArtifact : embeddedArtifacts) { if (artifact.equals(embeddedArtifact)) { return false; } } return true; } @Override public ParsingContext enterArtifact(Artifact artifact, boolean optional, boolean external, ParsingContext parentParsingContext, String logPrefix, int depth) throws MojoExecutionException { try { return dependenciesMojo.startProcessingArtifact(projectParsingContext, scannedCopy, artifact, external, optional, parentParsingContext, logPrefix, depth); } catch (IOException e) { throw new MojoExecutionException("Error processing artifact " + artifact, e); } } @Override public boolean exitArtifact(Artifact artifact, boolean optional, boolean external, String logPrefix, ParsingContext parsingContext, int depth) throws MojoExecutionException { return dependenciesMojo.endProcessingArtifact(projectParsingContext, artifact, external, logPrefix, parsingContext, depth); } }, artifact.getArtifactHandler(), projectParsingContext); scanned++; } projectParsingContext.removeLocalPackagesFromImports(); return scanned; } protected ParsingContext startProcessingArtifact(ParsingContext projectParsingContext, int scanned, Artifact artifact, boolean externalDependency, boolean optional, ParsingContext parentParsingContext, String logPrefix, int depth) throws MojoExecutionException, IOException { ParsingContext parsingContext = parsingContextCache.get(artifact); if (parsingContext == null) { parsingContext = new ParsingContext(MavenAetherHelperUtils.getCoords(artifact), artifact.getFile().lastModified(), artifact.getFile().length(), artifact.getFile().getName(), artifact.getFile().getPath(), artifact.getVersion(), parentParsingContext); long timer = System.currentTimeMillis(); int scannedInJar = scanJar(artifact.getFile(), externalDependency, "war".equals(artifact.getType()) ? "WEB-INF/classes/" : "", artifact.getVersion(), artifact.isOptional(), parsingContext, logPrefix); long took = System.currentTimeMillis() - timer; if (getLog().isInfoEnabled() && (scannedInJar > 0)) { getLog().info(logPrefix + "Processed " + scannedInJar + ((scanned == 1) ? " entry" : " entries") + " in " + (externalDependency ? "external" : "") + "dependency " + artifact + " in " + took + " ms"); } } if (parentParsingContext != null) { parentParsingContext.addChildJarParsingContext(parsingContext); } if (optional) { parsingContext.setOptional(true); } else { parsingContext.setOptional(false); } if (externalDependency) { parsingContext.setExternal(true); } else { parsingContext.setExternal(false); } return parsingContext; } protected boolean endProcessingArtifact(ParsingContext projectParsingContext, Artifact artifact, boolean externalDependency, String logPrefix, ParsingContext parsingContext, int depth) { if (artifact == null) { getLog().warn(logPrefix + ": Artifact is null, will not put parsed JAR context " + parsingContext + " in cache !"); return false; } if (!artifact.getFile().getPath().equals(parsingContext.getFilePath())) { getLog().warn(logPrefix + ": Artifact file path (" + artifact.getFile().getPath() + ") and jarParsingContext file path (" + parsingContext.getFilePath() + ") do not match, will not put parsed JAR context " + parsingContext + " in cache !"); return false; } /* if (artifact.isOptional() && !externalDependency) { projectParsingContext.addAllPackageImports(parsingContext.getLocalPackages(), true); } */ parsingContext.postProcess(); if (!parsingContext.isInCache()) { parsingContextCache.put(artifact, parsingContext); } return true; } protected boolean isExcludedFromScan(Artifact artifact) { String id = StringUtils.substringBeforeLast(artifact.toString(), ":"); for (Pattern exclusionPattern : artifactExclusionPatterns) { id = artifact.getGroupId() + ":" + artifact.getArtifactId(); Matcher exclusionMatcher = exclusionPattern.matcher(id); if (exclusionMatcher.matches()) { getLog().info("Ignoring artifact as the exclusion matched for " + id); return true; } } return false; } private int scanJar(File jarFile, boolean externalDependency, String packageDirectory, String version, boolean optional, ParsingContext parsingContext, String logPrefix) throws IOException { int scanned = 0; if (jarFile.isDirectory()) { getLog().debug(logPrefix + "Processing dependency directory " + jarFile + "..."); processDirectoryTlds(jarFile, version, parsingContext); processDirectory(jarFile, false, version, parsingContext); return scanned; } JarInputStream jarInputStream = new JarInputStream(new FileInputStream(jarFile)); try { JarEntry jarEntry = null; getLog().debug(logPrefix + "Processing JAR file " + jarFile + "..."); if (processJarManifest(jarFile, parsingContext, jarInputStream)) { getLog().debug(logPrefix + "Used OSGi bundle manifest information, but scanning for additional resources (taglibs, CNDs, etc)... "); } scanned = processJarInputStream(jarFile.getPath(), externalDependency, packageDirectory, version, optional, parsingContext, logPrefix, scanned, jarInputStream); } finally { jarInputStream.close(); } if (parsingContext.getBundleClassPath().size() > 0) { getLog().debug(logPrefix + "Processing embedded dependencies..."); JarFile jar = new JarFile(jarFile); for (String embeddedJar : parsingContext.getBundleClassPath()) { if (".".equals(embeddedJar)) { continue; } JarEntry jarEntry = jar.getJarEntry(embeddedJar); if (jarEntry != null) { getLog().debug(logPrefix + "Processing embedded JAR..." + jarEntry); InputStream jarEntryInputStream = jar.getInputStream(jarEntry); ByteArrayOutputStream entryOutputStream = new ByteArrayOutputStream(); IOUtils.copy(jarEntryInputStream, entryOutputStream); JarInputStream entryJarInputStream = new JarInputStream( new ByteArrayInputStream(entryOutputStream.toByteArray())); processJarInputStream(jarFile.getPath() + "!" + jarEntry, externalDependency, packageDirectory, version, optional, parsingContext, logPrefix, scanned, entryJarInputStream); IOUtils.closeQuietly(jarEntryInputStream); IOUtils.closeQuietly(entryJarInputStream); } else { getLog().warn(logPrefix + "Couldn't find embedded JAR to parse " + embeddedJar + " in JAR " + jarFile); } } } if (parsingContext.getAdditionalFilesToParse().size() > 0) { getLog().debug(logPrefix + "Processing additional files to parse..."); JarFile jar = new JarFile(jarFile); for (String fileToParse : parsingContext.getAdditionalFilesToParse()) { JarEntry jarEntry = jar.getJarEntry(fileToParse); if (jarEntry != null) { InputStream jarEntryInputStream = jar.getInputStream(jarEntry); ByteArrayOutputStream entryOutputStream = new ByteArrayOutputStream(); IOUtils.copy(jarEntryInputStream, entryOutputStream); if (processNonTldFile(jarEntry.getName(), new ByteArrayInputStream(entryOutputStream.toByteArray()), jarFile.getPath(), optional, version, parsingContext)) { scanned++; } IOUtils.closeQuietly(jarEntryInputStream); } else { getLog().warn(logPrefix + "Couldn't find additional file to parse " + fileToParse + " in JAR " + jarFile); } } parsingContext.clearAdditionalFilesToParse(); } return scanned; } protected int processJarInputStream(String jarFilePath, boolean externalDependency, String packageDirectory, String version, boolean optional, ParsingContext parsingContext, String logPrefix, int scanned, JarInputStream jarInputStream) throws IOException { JarEntry jarEntry; while ((jarEntry = jarInputStream.getNextJarEntry()) != null) { if (jarEntry.isDirectory()) { continue; } String entryName = jarEntry.getName(); if (entryName.startsWith(packageDirectory)) { String packageName = entryName.substring(packageDirectory.length()); if (!packageName.contains("/.")) { processLocalPackageEntry(packageName, "/", jarFilePath, version, optional, parsingContext); } } if (excludeJarEntry(entryName)) { continue; } ByteArrayOutputStream entryOutputStream = new ByteArrayOutputStream(); IOUtils.copy(jarInputStream, entryOutputStream); if (Parsers.getInstance().canParseForPhase(0, jarEntry.getName())) { getLog().debug(logPrefix + " scanning JAR entry: " + jarEntry.getName()); Parsers.getInstance().parse(0, jarEntry.getName(), new ByteArrayInputStream(entryOutputStream.toByteArray()), jarFilePath, externalDependency, optional, version, getLogger(), parsingContext); scanned++; } if (Parsers.getInstance().canParseForPhase(1, jarEntry.getName())) { getLog().debug(logPrefix + " scanning JAR entry: " + jarEntry.getName()); if (processNonTldFile(jarEntry.getName(), new ByteArrayInputStream(entryOutputStream.toByteArray()), jarFilePath, optional, version, parsingContext)) { scanned++; } } } return scanned; } private boolean processJarManifest(File jarFile, ParsingContext parsingContext, JarInputStream jarInputStream) throws IOException { boolean processedBundleHeadersSuccessfully = false; Manifest jarManifest = jarInputStream.getManifest(); if (jarManifest != null && jarManifest.getMainAttributes() != null) { Attributes mainAttributes = jarManifest.getMainAttributes(); String bundleSymbolicName = mainAttributes.getValue("Bundle-SymbolicName"); if (bundleSymbolicName != null) { String bundleVersion = mainAttributes.getValue("Bundle-Version"); String bundleClassPath = mainAttributes.getValue("Bundle-ClassPath"); if (bundleClassPath != null && !".".equals(bundleClassPath.trim())) { String[] bundleClassPathEntries = bundleClassPath.split(","); for (String bundleClassPathEntry : bundleClassPathEntries) { parsingContext.getBundleClassPath().add(bundleClassPathEntry.trim()); } } getLog().debug("OSGi bundle detected with symbolic name=" + bundleSymbolicName + " version=" + bundleVersion); parsingContext.setOsgiBundle(true); parsingContext.setVersion(bundleVersion); String importPackageHeaderValue = mainAttributes.getValue("Import-Package"); String exportPackageHeaderValue = mainAttributes.getValue("Export-Package"); String ignorePackageHeaderValue = mainAttributes.getValue("Ignore-Package"); if (importPackageHeaderValue != null) { List<ManifestValueClause> importPackageClauses = BundleUtils.getHeaderClauses("Import-Package", importPackageHeaderValue); for (ManifestValueClause importPackageClause : importPackageClauses) { for (String importPackagePath : importPackageClause.getPaths()) { String clauseVersion = importPackageClause.getAttributes().get("version"); String clauseResolution = importPackageClause.getDirectives().get("resolution"); boolean optionalClause = false; if ("optional".equals(clauseResolution)) { optionalClause = true; } PackageInfo importPackageInfo = new PackageInfo(importPackagePath, clauseVersion, optionalClause, jarFile.getPath(), parsingContext); parsingContext.addPackageImport(importPackageInfo); } } } if (exportPackageHeaderValue != null) { List<ManifestValueClause> exportPackageClauses = BundleUtils.getHeaderClauses("Export-Package", exportPackageHeaderValue); for (ManifestValueClause exportPackageClause : exportPackageClauses) { for (String importPackagePath : exportPackageClause.getPaths()) { String clauseVersion = exportPackageClause.getAttributes().get("version"); PackageInfo exportPackageInfo = new PackageInfo(importPackagePath, clauseVersion, false, jarFile.getPath(), parsingContext); parsingContext.addPackageExport(exportPackageInfo); } } } if (ignorePackageHeaderValue != null) { List<ManifestValueClause> ignorePackageClauses = BundleUtils.getHeaderClauses("Ignore-Package", ignorePackageHeaderValue); for (ManifestValueClause ignorePackageClause : ignorePackageClauses) { for (String importPackagePath : ignorePackageClause.getPaths()) { String clauseVersion = ignorePackageClause.getAttributes().get("version"); boolean optionalClause = true; PackageInfo ignorePackageInfo = new PackageInfo(importPackagePath, clauseVersion, optionalClause, jarFile.getPath(), parsingContext); parsingContext.addPackageImport(ignorePackageInfo); } } } processedBundleHeadersSuccessfully = true; } } return processedBundleHeadersSuccessfully; } private void processLocalPackageEntry(String entryName, String fileSeparator, String entryParent, String version, boolean optional, ParsingContext parsingContext) { int lastSlash = entryName.lastIndexOf(fileSeparator); if (lastSlash == -1) { return; } String entryPackage = StringUtils.replace(entryName.substring(0, lastSlash), fileSeparator, "."); if (StringUtils.isNotEmpty(entryPackage) && !parsingContext.getLocalPackages().contains(new PackageInfo(entryPackage)) && !entryPackage.startsWith("META-INF") && !entryPackage.startsWith("OSGI-INF") && !entryPackage.startsWith("OSGI-OPT") && !entryPackage.startsWith("WEB-INF")) { PackageInfo packageInfo = new PackageInfo(entryPackage, version, optional, entryParent + "/" + entryName, parsingContext); parsingContext.addLocalPackage(packageInfo); if (!parsingContext.isOsgiBundle()) { parsingContext.addPackageExport(packageInfo); } } } private boolean excludeJarEntry(String entryName) { if (excludedJarEntryPatterns != null) { for (Pattern p : excludedJarEntryPatterns) { if (p.matcher(entryName).matches()) { getLog().debug("Matched JAR entry exclusion pattern for entry " + entryName); return true; } } } return false; } private void processDirectoryTlds(File directoryFile, String version, ParsingContext parsingContext) throws IOException { DirectoryScanner ds = new DirectoryScanner(); String[] excludes = excludeFromDirectoryScan.toArray(new String[excludeFromDirectoryScan.size()]); ds.setExcludes(excludes); ds.setIncludes(new String[] { "**/*.tld" }); ds.setBasedir(directoryFile); ds.setCaseSensitive(true); ds.scan(); String[] includedFiles = ds.getIncludedFiles(); for (String includedFile : includedFiles) { Parsers.getInstance().parse(0, includedFile, new BufferedInputStream(new FileInputStream(new File(directoryFile, includedFile))), directoryFile.getPath(), false, false, version, getLogger(), parsingContext); } } private void processDirectory(File directoryFile, boolean optional, String version, ParsingContext parsingContext) throws IOException { DirectoryScanner ds = new DirectoryScanner(); String[] excludes = excludeFromDirectoryScan.toArray(new String[excludeFromDirectoryScan.size()]); ds.setExcludes(excludes); ds.setBasedir(directoryFile); ds.setCaseSensitive(true); ds.scan(); String[] includedFiles = ds.getIncludedFiles(); for (String includedFile : includedFiles) { String ext = FileUtils.getExtension(includedFile).toLowerCase(); if (!SUPPORTED_FILE_EXTENSIONS_TO_SCAN.contains(ext)) { continue; } InputStream fileInputStream = null; try { fileInputStream = new BufferedInputStream( new FileInputStream(new File(directoryFile, includedFile))); processNonTldFile(includedFile, fileInputStream, directoryFile.getPath(), optional, version, parsingContext); } finally { IOUtils.closeQuietly(fileInputStream); } } } private boolean processNonTldFile(String fileName, InputStream inputStream, String fileParent, boolean optional, String version, ParsingContext parsingContext) throws IOException { String ext = FileUtils.getExtension(fileName).toLowerCase(); if (!SUPPORTED_FILE_EXTENSIONS_TO_SCAN.contains(ext)) { return false; } return Parsers.getInstance().parse(1, fileName, inputStream, fileParent, false, optional, version, getLogger(), parsingContext); } private Logger getLogger() { return logger; } protected void resolveEmbeddedDependencies(MavenProject currentProject, Builder builder) throws Exception { if (currentProject.getBasedir() != null) { // update BND instructions to add included Maven resources includeMavenResources(currentProject, builder, getLog()); // calculate default export/private settings based on sources addLocalPackages(new File(projectOutputDirectory), builder); // tell BND where the current project source resides addMavenSourcePath(currentProject, builder, getLog()); } // update BND instructions to embed selected Maven dependencies Collection<Artifact> embeddableArtifacts = getEmbeddableArtifacts(currentProject, builder); DependencyEmbedder dependencyEmbedder = new DependencyEmbedder(getLog(), embeddableArtifacts); dependencyEmbedder.processHeaders(builder); inlinedPaths = dependencyEmbedder.getInlinedPaths(); embeddedArtifacts = dependencyEmbedder.getEmbeddedArtifacts(); } /* code copied from BundlePlugin because resources were private */ private static final String LOCAL_PACKAGES = "{local-packages}"; private static void addLocalPackages(File outputDirectory, Analyzer analyzer) throws IOException { Packages packages = new Packages(); if (outputDirectory != null && outputDirectory.isDirectory()) { // scan classes directory for potential packages DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir(outputDirectory); scanner.setIncludes(new String[] { "**/*.class" }); scanner.addDefaultExcludes(); scanner.scan(); String[] paths = scanner.getIncludedFiles(); for (int i = 0; i < paths.length; i++) { packages.put(analyzer.getPackageRef(getPackageName(paths[i]))); } } Packages exportedPkgs = new Packages(); Packages privatePkgs = new Packages(); boolean noprivatePackages = "!*".equals(analyzer.getProperty(Analyzer.PRIVATE_PACKAGE)); for (Descriptors.PackageRef pkg : packages.keySet()) { // mark all source packages as private by default (can be overridden by export list) privatePkgs.put(pkg); // we can't export the default package (".") and we shouldn't export internal packages String fqn = pkg.getFQN(); if (noprivatePackages || !(".".equals(fqn) || fqn.contains(".internal") || fqn.contains(".impl"))) { exportedPkgs.put(pkg); } } Properties properties = analyzer.getProperties(); String exported = properties.getProperty(Analyzer.EXPORT_PACKAGE); if (exported == null) { if (!properties.containsKey(Analyzer.EXPORT_CONTENTS)) { // no -exportcontents overriding the exports, so use our computed list for (Attrs attrs : exportedPkgs.values()) { attrs.put(Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first"); } properties.setProperty(Analyzer.EXPORT_PACKAGE, Processor.printClauses(exportedPkgs)); } else { // leave Export-Package empty (but non-null) as we have -exportcontents properties.setProperty(Analyzer.EXPORT_PACKAGE, ""); } } else if (exported.indexOf(LOCAL_PACKAGES) >= 0) { String newExported = org.codehaus.plexus.util.StringUtils.replace(exported, LOCAL_PACKAGES, Processor.printClauses(exportedPkgs)); properties.setProperty(Analyzer.EXPORT_PACKAGE, newExported); } String internal = properties.getProperty(Analyzer.PRIVATE_PACKAGE); if (internal == null) { if (!privatePkgs.isEmpty()) { for (Attrs attrs : privatePkgs.values()) { attrs.put(Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first"); } properties.setProperty(Analyzer.PRIVATE_PACKAGE, Processor.printClauses(privatePkgs)); } else { // if there are really no private packages then use "!*" as this will keep the Bnd Tool happy properties.setProperty(Analyzer.PRIVATE_PACKAGE, "!*"); } } else if (internal.indexOf(LOCAL_PACKAGES) >= 0) { String newInternal = org.codehaus.plexus.util.StringUtils.replace(internal, LOCAL_PACKAGES, Processor.printClauses(privatePkgs)); properties.setProperty(Analyzer.PRIVATE_PACKAGE, newInternal); } } private static String getPackageName(String filename) { int n = filename.lastIndexOf(File.separatorChar); return n < 0 ? "." : filename.substring(0, n).replace(File.separatorChar, '.'); } }