/* * Copyright (C) 2011 Everit Kft. (http://everit.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.everit.osgi.dev.maven; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.resolution.ArtifactRequest; import org.everit.osgi.dev.dist.util.DistConstants; import org.everit.osgi.dev.maven.analytics.GoogleAnalyticsTrackingService; import org.everit.osgi.dev.maven.analytics.GoogleAnalyticsTrackingServiceImpl; import org.everit.osgi.dev.maven.configuration.EOSGiArtifact; import org.everit.osgi.dev.maven.configuration.EnvironmentConfiguration; import org.everit.osgi.dev.maven.configuration.LaunchConfig; import org.everit.osgi.dev.maven.util.DistributableArtifact; import org.everit.osgi.dev.maven.util.PluginUtil; import org.everit.osgi.dev.maven.util.PredefinedRepoArtifactResolver; import org.osgi.framework.Constants; /** * Mojos that extend this class can use the environment information defined for the plugin. */ public abstract class AbstractEOSGiMojo extends AbstractMojo { /** * The name of the referer that means who execute goal (example: eosgi-maven-plugin or * eclipse-e4-plugin, ...). Default value is "eosgi-maven-plugin". */ @Parameter(property = "eosgi.analytics.referer", defaultValue = "eosgi-maven-plugin") protected String analyticsReferer; /** * The waiting time to send the analytics to Google Analytics server. */ @Parameter(property = "eosgi.analytics.waiting.time", defaultValue = "3000") private long analyticsWaitingTimeInMs; @Component protected ArtifactHandlerManager artifactHandlerManager; protected PredefinedRepoArtifactResolver artifactResolver; /** * Comma separated list of the id of the environments that should be processed. Default is * that * means all environments. */ @Parameter(name = "environmentId", property = DistConstants.PLUGIN_PROPERTY_ENVIRONMENT_ID, defaultValue = "*") protected String environmentIdsToProcess = "*"; /** * The environments on which the tests should run. */ @Parameter protected EnvironmentConfiguration[] environments; private EnvironmentConfiguration[] environmentsToProcess; @Parameter(property = "executedProject") protected MavenProject executedProject; /** * The configuration of the launched OSGi Container. */ @Parameter protected LaunchConfig launchConfig; @Parameter(defaultValue = "${mojoExecution}", readonly = true) protected MojoExecution mojo; /** * The Maven project. */ @Parameter(property = "project") protected MavenProject project; /** * The current repository/network configuration of Maven. */ @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) protected RepositorySystemSession repoSession; /** * The entry point to Aether, i.e. the component doing all the work. */ @Component protected RepositorySystem repoSystem; @Parameter(defaultValue = "${settings}", readonly = true) protected Settings settings; /** * Skip analytics tracking or not. That means send event statistics to Google Analytics or not. * Default value is <code>false</code> that means send statistics. */ @Parameter(property = "eosgi.analytics.skip", defaultValue = "false") private boolean skipAnalytics; private EOSGiArtifact convertMavenToEOSGiArtifact(final Artifact artifact) { EOSGiArtifact eosgiArtifact = new EOSGiArtifact(); StringBuilder gav = new StringBuilder(artifact.getGroupId()).append(":").append(artifact.getArtifactId()); String extension = artifact.getArtifactHandler().getExtension(); String classifier = artifact.getClassifier(); if ("".equals(classifier)) { classifier = null; } if (!"jar".equals(extension) || classifier != null) { gav.append(':').append(extension); } if (classifier != null) { gav.append(':').append(classifier); } gav.append(':').append(artifact.getVersion()); eosgiArtifact.setCoordinates(gav.toString()); return eosgiArtifact; } /** * Appends the artifacts of the project to the distributable artifact map. * * @return A map where the key is the GAV and the value is the distributable artifact. */ protected Map<String, DistributableArtifact> createDistributableArtifactsByGAVFromProjectDeps() { List<Artifact> availableArtifacts = new ArrayList<>(project.getArtifacts()); Map<String, DistributableArtifact> distributableArtifacts = new HashMap<>(); if (executedProject != null) { availableArtifacts.add(executedProject.getArtifact()); } else { availableArtifacts.add(project.getArtifact()); } for (Artifact artifact : availableArtifacts) { if (!Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) { EOSGiArtifact eosgiArtifact = convertMavenToEOSGiArtifact(artifact); DistributableArtifact processedArtifact = processArtifact(eosgiArtifact, artifact.getFile()); distributableArtifacts.put(processedArtifact.coordinates, processedArtifact); } } return distributableArtifacts; } protected abstract void doExecute() throws MojoExecutionException, MojoFailureException; @Override public final void execute() throws MojoExecutionException, MojoFailureException { artifactResolver = new PredefinedRepoArtifactResolver(repoSystem, repoSession, project.getRemoteProjectRepositories(), getLog()); MojoDescriptor mojoDescriptor = mojo.getMojoDescriptor(); String goalName = mojoDescriptor.getGoal(); long eventId = GoogleAnalyticsTrackingService.DEFAULT_EVENT_ID; String pluginVersion = this.getClass().getPackage().getImplementationVersion(); GoogleAnalyticsTrackingService googleAnalyticsTrackingService = new GoogleAnalyticsTrackingServiceImpl(analyticsWaitingTimeInMs, skipAnalytics(), pluginVersion, getLog()); try { eventId = googleAnalyticsTrackingService.sendEvent(analyticsReferer, goalName); doExecute(); } finally { googleAnalyticsTrackingService.waitForEventSending(eventId); } } /** * Getting the processed artifacts of the project. The artifact list is calculated each time when * the function is called therefore the developer should not call it inside an iteration. * * @param environmentConfiguration * The configuration of the environment that the artifact list is generated for. * @param projectDistributableDependencies * The dependencies of the project as a GAV-DistributableArtifact map. * @return The list of dependencies that are OSGI bundles but do not have the scope "provided" * @throws MojoExecutionException * if anything happens */ protected Collection<DistributableArtifact> generateDistributableArtifactsForEnvironment( final EnvironmentConfiguration environmentConfiguration, final Map<String, DistributableArtifact> projectDistributableDependencies) throws MojoExecutionException { Collection<DistributableArtifact> distributableArtifacts = new LinkedHashSet<>(projectDistributableDependencies.values()); List<EOSGiArtifact> environmentArtifacts = environmentConfiguration.getArtifacts(); if (environmentArtifacts == null) { return distributableArtifacts; } for (EOSGiArtifact eosgiArtifact : environmentArtifacts) { // Remove if available in project dependencies DistributableArtifact projectDA = projectDistributableDependencies.get(eosgiArtifact.getCoordinates()); if (projectDA != null) { distributableArtifacts.remove(projectDA); } // And add to the environment dependencies org.eclipse.aether.artifact.Artifact resolvedArtifact = resolveArtifact(new DefaultArtifact(eosgiArtifact.getCoordinates())); DistributableArtifact distributableArtifact = processArtifact(eosgiArtifact, resolvedArtifact.getFile()); distributableArtifacts.add(distributableArtifact); } return distributableArtifacts; } /** * Returns the default environment. This method is called when there is no environment configured * for the plugin. * * @return The default environment. */ protected EnvironmentConfiguration getDefaultEnvironment() { getLog().info("There is no environment specified in the project. Creating " + DistConstants.DEFAULT_ENVIRONMENT_ID + " environment with default settings"); EnvironmentConfiguration defaultEnvironment = new EnvironmentConfiguration(); defaultEnvironment.setId(DistConstants.DEFAULT_ENVIRONMENT_ID); defaultEnvironment.setFramework(DistConstants.DEFAULT_ENVIRONMENT_FRAMEWORK); return defaultEnvironment; } /** * Returns the environments that are configured for this plugin or the default environment * configuration if no environments are configured. * * @return The list of environments that are defined for the plugin. */ protected EnvironmentConfiguration[] getEnvironments() { if ((environments == null) || (environments.length == 0)) { environments = new EnvironmentConfiguration[] { getDefaultEnvironment() }; } return environments; } /** * Getting an array of the environment configurations that should be processed based on the value * of the {@link #environmentIdsToProcess} parameter. The value, that is returned, is calculated * the first time the function is called. * * @return The array of environment ids that should be processed. */ protected EnvironmentConfiguration[] getEnvironmentsToProcess() { if (environmentsToProcess != null) { return environmentsToProcess; } if ("*".equals(environmentIdsToProcess)) { environmentsToProcess = getEnvironments(); } else { String[] environmentIdArray = environmentIdsToProcess.trim().split(","); EnvironmentConfiguration[] tmpEnvironments = getEnvironments(); List<EnvironmentConfiguration> result = new ArrayList<>(); for (EnvironmentConfiguration tmpEnvironment : tmpEnvironments) { boolean found = false; int j = 0; int n = environmentIdArray.length; while (!found && (j < n)) { if (environmentIdArray[j].equals(tmpEnvironment.getId())) { found = true; result.add(tmpEnvironment); } j++; } } environmentsToProcess = result.toArray(new EnvironmentConfiguration[result.size()]); } return environmentsToProcess; } /** * Checking if an artifact is an OSGI bundle and if yes, appends its properties. An artifact is an * OSGI bundle if the MANIFEST.MF file inside contains a Bundle-SymbolicName. * * @param eosgiArtifact * The artifact with optional additional information. * @param artifactFile * The resolved file of the artifact. * @return A {@link DistributableArtifact} with the Bundle-SymbolicName and a Bundle-Version. * Bundle-Version comes from MANIFEST.MF but if Bundle-Version is not available there the * default 0.0.0 version is provided. */ public DistributableArtifact processArtifact( final EOSGiArtifact eosgiArtifact, final File artifactFile) { DistributableArtifact distributableArtifact = new DistributableArtifact(); distributableArtifact.coordinates = eosgiArtifact.getCoordinates(); distributableArtifact.downloadURL = eosgiArtifact.getDownloadURL(); distributableArtifact.targetFile = eosgiArtifact.getTargetFile(); distributableArtifact.targetFolder = eosgiArtifact.getTargetFolder(); distributableArtifact.properties = eosgiArtifact.getProperties(); distributableArtifact.file = artifactFile; if (distributableArtifact.properties == null) { distributableArtifact.properties = new HashMap<>(); } if ((artifactFile == null) || !artifactFile.exists() || !artifactFile.getName().endsWith(".jar")) { return distributableArtifact; } Manifest manifest = null; try (JarFile jarFile = new JarFile(artifactFile)) { manifest = jarFile.getManifest(); if (manifest == null) { return distributableArtifact; } Attributes mainAttributes = manifest.getMainAttributes(); String symbolicName = mainAttributes.getValue(Constants.BUNDLE_SYMBOLICNAME); String version = mainAttributes.getValue(Constants.BUNDLE_VERSION); if ((symbolicName != null) && (version != null)) { int semicolonIndex = symbolicName.indexOf(';'); if (semicolonIndex >= 0) { symbolicName = symbolicName.substring(0, semicolonIndex); } version = PluginUtil.normalizeVersion(version); String fragmentHost = mainAttributes.getValue(Constants.FRAGMENT_HOST); String importPackage = mainAttributes.getValue(Constants.IMPORT_PACKAGE); String exportPackage = mainAttributes.getValue(Constants.EXPORT_PACKAGE); Map<String, String> properties = distributableArtifact.properties; properties.put("bundle.symbolicName", symbolicName); properties.put("bundle.version", version); putIfNotNull(properties, "bundle.fragmentHost", fragmentHost); putIfNotNull(properties, "bundle.importPackage", importPackage); putIfNotNull(properties, "bundle.exportPackage", exportPackage); } return distributableArtifact; } catch (IOException e) { throw new RuntimeException(e); } } private void putIfNotNull(final Map<String, String> properties, final String key, final String value) { if (key != null) { properties.put(key, value); } } /** * Resolves an artifact so its file will be available. * * @param artifact * The artifact that should be resolved. * @return The Aether resolved artifact. * @throws MojoExecutionException * if something wrong happens. */ protected org.eclipse.aether.artifact.Artifact resolveArtifact( final org.eclipse.aether.artifact.Artifact artifact) throws MojoExecutionException { ArtifactRequest artifactRequest = new ArtifactRequest(); artifactRequest.setArtifact(artifact); return artifactResolver.resolve(artifactRequest); } public void setEnvironmentId(final String environmentId) { environmentIdsToProcess = environmentId; } private boolean skipAnalytics() { return skipAnalytics || settings.isOffline(); } }