/amps-maven-plugin/src/main/java/com/atlassian/maven/plugins/amps/product/AbstractProductHandler.java
Java | 1026 lines | 665 code | 111 blank | 250 comment | 78 complexity | 5914b55f1e66f5fd9b80aa458c7100e4 MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
- package com.atlassian.maven.plugins.amps.product;
- import com.atlassian.maven.plugins.amps.MavenContext;
- import com.atlassian.maven.plugins.amps.MavenGoals;
- import com.atlassian.maven.plugins.amps.Node;
- import com.atlassian.maven.plugins.amps.Product;
- import com.atlassian.maven.plugins.amps.ProductArtifact;
- import com.atlassian.maven.plugins.amps.license.LicenseInstaller;
- import com.atlassian.maven.plugins.amps.util.ArtifactResolutionException;
- import com.atlassian.maven.plugins.amps.util.ConfigFileUtils.Replacement;
- import com.atlassian.maven.plugins.amps.util.JvmArgsFix;
- import com.atlassian.maven.plugins.amps.util.ProjectUtils;
- import com.atlassian.maven.plugins.amps.util.ZipUtils;
- import com.google.common.annotations.VisibleForTesting;
- import com.google.common.collect.ImmutableMap;
- import com.google.common.collect.ImmutableSet;
- import org.apache.maven.artifact.Artifact;
- import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
- import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
- import org.apache.maven.artifact.resolver.ArtifactResolver;
- import org.apache.maven.plugin.MojoExecutionException;
- import org.apache.maven.plugin.logging.Log;
- import org.apache.maven.project.MavenProject;
- import org.apache.maven.repository.RepositorySystem;
- import org.twdata.maven.mojoexecutor.MojoExecutor;
- import javax.annotation.Nonnull;
- import javax.annotation.Nullable;
- import java.io.File;
- import java.io.IOException;
- import java.io.UnsupportedEncodingException;
- import java.net.InetAddress;
- import java.net.UnknownHostException;
- import java.nio.charset.StandardCharsets;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.Optional;
- import java.util.Properties;
- import java.util.Set;
- import java.util.regex.Pattern;
- import static com.atlassian.maven.plugins.amps.product.AmpsDefaults.DEFAULT_CONTAINER;
- import static com.atlassian.maven.plugins.amps.product.JavaModulePackage.fromJavaBaseModule;
- import static com.atlassian.maven.plugins.amps.product.JavaModulePackage.fromJavaRmiModule;
- import static com.atlassian.maven.plugins.amps.product.ProductHandlerFactory.JIRA;
- import static com.atlassian.maven.plugins.amps.util.ConfigFileUtils.Replacement.onlyWhenUnzipping;
- import static com.atlassian.maven.plugins.amps.util.ConfigFileUtils.replace;
- import static com.atlassian.maven.plugins.amps.util.FileUtils.copyDirectory;
- import static com.atlassian.maven.plugins.amps.util.FileUtils.doesFileNameMatchArtifact;
- import static com.atlassian.maven.plugins.amps.util.FileUtils.makeDirectories;
- import static com.atlassian.maven.plugins.amps.util.FileUtils.makeDirectory;
- import static com.atlassian.maven.plugins.amps.util.ProjectUtils.createDirectory;
- import static com.atlassian.maven.plugins.amps.util.ProjectUtils.firstNotNull;
- import static com.atlassian.maven.plugins.amps.util.ZipUtils.unzip;
- import static com.atlassian.maven.plugins.amps.util.ZipUtils.zipChildren;
- import static java.lang.String.join;
- import static java.net.URLEncoder.encode;
- import static java.nio.file.Files.delete;
- import static java.util.Arrays.stream;
- import static java.util.Collections.emptyList;
- import static java.util.Collections.singletonList;
- import static java.util.Collections.sort;
- import static java.util.Objects.requireNonNull;
- import static java.util.Optional.empty;
- import static java.util.stream.Collectors.joining;
- import static org.apache.commons.io.FileUtils.copyFile;
- import static org.apache.commons.io.FileUtils.deleteDirectory;
- import static org.apache.commons.io.FileUtils.deleteQuietly;
- import static org.apache.commons.io.FileUtils.iterateFiles;
- import static org.apache.commons.io.FileUtils.listFiles;
- import static org.apache.commons.io.FileUtils.moveDirectory;
- import static org.apache.commons.lang3.StringUtils.isBlank;
- import static org.apache.commons.lang3.StringUtils.isNotBlank;
- import static org.apache.maven.artifact.Artifact.LATEST_VERSION;
- import static org.apache.maven.artifact.Artifact.RELEASE_VERSION;
- /**
- * Convenient superclass for {@link ProductHandler} implementations.
- *
- * Incorporates code previously located in its deleted superclass {@code AmpsProductHandler}.
- */
- public abstract class AbstractProductHandler implements ProductHandler {
- private static final Map<String, Map<String, GroupArtifactPair>> APPLICATION_KEYS =
- ImmutableMap.of(
- JIRA, ImmutableMap.of(
- "jira-software", new GroupArtifactPair("com.atlassian.jira", "jira-software-application"),
- "jira-servicedesk", new GroupArtifactPair("com.atlassian.servicedesk", "jira-servicedesk-application")));
- private static final ProductArtifact LICENSE_BACKDOOR_PLUGIN =
- new ProductArtifact("com.atlassian.platform", "license-backdoor-plugin", "1.0.2");
- private static final String TOMCAT_LIB_DIR = "WEB-INF/lib";
- protected static final Set<JavaModulePackage> ADD_OPENS_FOR_TOMCAT = ImmutableSet.of(
- fromJavaBaseModule("java.lang"),
- fromJavaBaseModule("java.io"),
- fromJavaRmiModule("sun.rmi.transport"));
- protected static final Set<JavaModulePackage> ADD_OPENS_FOR_FELIX = ImmutableSet.of(
- fromJavaBaseModule("java.lang"),
- fromJavaBaseModule("java.net"),
- fromJavaBaseModule("sun.net.www.protocol.jar"),
- fromJavaBaseModule("sun.net.www.protocol.file"),
- fromJavaBaseModule("sun.net.www.protocol.http"),
- fromJavaBaseModule("sun.net.www.protocol.https"));
- /**
- * Encodes a String for a Properties file. Escapes the ':' and '=' characters.
- *
- * @param raw the raw properties string to encode
- * @return the properties-encoded version of the input string
- */
- @VisibleForTesting
- static String propertiesEncode(final String raw) {
- if (raw == null) {
- return null;
- }
- // AS: this looks like a bug to me - check the unit test I wrote for this method.
- // However it only affects some replacements that allegedly apply only to Fecru,
- // and there's no sign of the replaced strings in the Fecru project, so in reality
- // it might not be affecting anything. Such escaping would only be necessary in a
- // property key anyway (not in a property value).
- return raw
- .replaceAll(":", "\\:")
- .replaceAll("=", "\\=");
- }
- protected final Log log;
- protected final MavenContext context;
- protected final MavenGoals goals;
- protected final MavenProject project;
- protected final RepositorySystem repositorySystem;
- private final ApplicationMapper applicationMapper;
- private final ArtifactResolver artifactResolver;
- private final PluginProvider pluginProvider;
- protected AbstractProductHandler(final MavenContext context, final MavenGoals goals,
- final PluginProvider pluginProvider, final RepositorySystem repositorySystem,
- final ArtifactResolver artifactResolver) {
- this.applicationMapper = new ApplicationMapper(APPLICATION_KEYS);
- this.artifactResolver = requireNonNull(artifactResolver);
- this.context = requireNonNull(context);
- this.goals = requireNonNull(goals);
- this.log = requireNonNull(context.getLog());
- this.pluginProvider = requireNonNull(pluginProvider);
- this.project = requireNonNull(context.getProject());
- this.repositorySystem = requireNonNull(repositorySystem);
- }
- /**
- * Copies and creates a zip file of the previous run's home directory minus any installed plugins.
- *
- * @param homeDirectory The path to the previous run's home directory.
- * @param targetZip The path to the final zip file.
- * @param product The product
- * @since 3.1
- */
- public void createHomeZip(
- @Nonnull final File homeDirectory, @Nonnull final File targetZip, @Nonnull final Product product)
- throws MojoExecutionException {
- if (!homeDirectory.exists()) {
- final String homePath = homeDirectory.getAbsolutePath();
- context.getLog().info("home directory doesn't exist, skipping. [" + homePath + "]");
- return;
- }
- try {
- // The zip has /someRootFolder/{productId}-home/
- final File appDir = getBaseDirectory(product);
- final File tmpDir = new File(appDir, "tmp-resources");
- final File homeSnapshot = new File(tmpDir, "generated-home");
- final String entryBase = "generated-resources/" + product.getId() + "-home";
- if (homeSnapshot.exists()) {
- deleteDirectory(homeSnapshot);
- }
- makeDirectories(homeSnapshot);
- copyDirectory(homeDirectory, homeSnapshot, true);
- cleanupProductHomeForZip(product, homeSnapshot);
- ZipUtils.zipDir(targetZip, homeSnapshot, entryBase);
- } catch (IOException e) {
- throw new IllegalStateException("Error zipping home directory", e);
- }
- }
- /**
- * Prepares the home directory for snapshotting. This implementation:
- * <ul>
- * <li>removes all unnecessary files</li>
- * <li>performs product-specific clean-up</li>
- * <li>reverses the replacements made earlier in various files, as per {@link #getReplacements(Product, int)}</li>
- * <ul>
- *
- * @param product the product
- * @param snapshotDir an image of the home which will be zipped. This is not the working home, so you're free to
- * remove files and parametrise them.
- * @throws MojoExecutionException if there's a build error
- * @throws IOException if there's an I/O error
- */
- protected void cleanupProductHomeForZip(@Nonnull final Product product, @Nonnull final File snapshotDir)
- throws MojoExecutionException, IOException {
- try {
- // we want to get rid of the plugins folders.
- // Not used by: fisheye, confluence - Used by: crowd, bamboo, jira
- deleteDirectory(new File(snapshotDir, "plugins"));
- // Not used by: fisheye, jira - Used by: confluence, crowd, bamboo
- deleteDirectory(new File(snapshotDir, "bundled-plugins"));
- // Delete the test resources ZIP file, i.e. the homeZip that was used when we started AMPS
- getTestResourcesArtifact().ifPresent(testResourcesArtifact -> {
- String originalHomeZip = testResourcesArtifact.getArtifactId() + ".zip";
- deleteQuietly(new File(snapshotDir, originalHomeZip));
- });
- undoReplacements(product, snapshotDir);
- } catch (IOException ioe) {
- throw new MojoExecutionException("Could not delete home/plugins/ and /home/bundled-plugins/", ioe);
- }
- }
- private void undoReplacements(final Product product, final File snapshotDir) throws MojoExecutionException {
- final List<Replacement> replacements = getReplacements(product, 0);
- sort(replacements);
- final List<File> files = getConfigFiles(product, snapshotDir);
- replace(files, replacements, true);
- }
- /**
- * Extracts the given product and its home, prepares both and starts the product.
- *
- * @param product the product to start
- * @return the node(s) running the product
- */
- @Nonnull
- @Override
- public final List<Node> start(@Nonnull final Product product) throws MojoExecutionException {
- final List<File> homeDirs = extractAndProcessHomeDirectories(product);
- final File extractedApp = extractApplication(product);
- final File finalApp = addArtifactsAndOverrides(product, homeDirs, extractedApp);
- addOverridesFromProductPom(product);
- // Ask for the system properties (from the ProductHandler and from the pom.xml)
- final List<Map<String, String>> systemProperties = mergeSystemProperties(product);
- final List<Node> nodes = startProduct(product, finalApp, systemProperties);
- if (useBackdoorToInstallLicense()) {
- new LicenseInstaller(log).installLicense(product, nodes.get(0).getWebPort());
- }
- return nodes;
- }
- /**
- * Indicates whether this product handler uses the license backdoor plugin to install any user-configured license.
- * This implementation returns {@code true}. Subclasses should return {@code false} if they install the license
- * some other way, for example by editing the product's configuration files.
- *
- * @return see description
- */
- protected boolean useBackdoorToInstallLicense() {
- return true;
- }
- /**
- * Overrides the product context with properties inherited from the product POM. This implementation does nothing.
- *
- * @param product the product
- * @throws MojoExecutionException thrown during creating effective POM
- */
- protected void addOverridesFromProductPom(Product product) throws MojoExecutionException {
- }
- @Override
- @Nonnull
- public String getDefaultContainerId() {
- return DEFAULT_CONTAINER;
- }
- @Override
- @Nonnull
- public String getDefaultContainerId(@Nonnull final Product product) throws MojoExecutionException {
- return ProductContainerVersionMapper.containerForProductVersion(getId(), resolveVersion(product));
- }
- private String resolveVersion(final Product product) throws MojoExecutionException {
- String version = product.getVersion();
- if (isBlank(version)) {
- version = RELEASE_VERSION;
- }
- if (RELEASE_VERSION.equals(version) || LATEST_VERSION.equals(version)) {
- ProductArtifact productArtifact = getArtifact();
- Artifact warArtifact = repositorySystem.createProjectArtifact(
- productArtifact.getGroupId(), productArtifact.getArtifactId(), version);
- version = product.getArtifactRetriever().getLatestStableVersion(warArtifact);
- }
- return version;
- }
- @Override
- @Nonnull
- public String getDefaultContextPath() {
- return "/" + getId();
- }
- @Override
- @Nonnull
- public List<File> getHomeDirectories(@Nonnull final Product product) {
- final List<Node> nodes = product.getNodes();
- if (isBlank(product.getDataHome())) {
- // No dataHome configured => use the default home location(s)
- final List<File> homeDirectories = new ArrayList<>();
- for (int nodeIndex = 0; nodeIndex < nodes.size(); nodeIndex++) {
- // For backward-compatibility with single-node operation, use no suffix when only one node
- final String directorySuffix = nodeIndex == 0 ? "" : "-" + nodeIndex;
- homeDirectories.add(new File(getBaseDirectory(product), "home" + directorySuffix));
- }
- return homeDirectories;
- }
- switch (nodes.size()) {
- case 0:
- throw new IllegalStateException("Must be at least one node");
- case 1:
- // Single node => use the configured product-level home directory
- return singletonList(new File(product.getDataHome()));
- default:
- throw new UnsupportedOperationException(
- "You cannot specify a custom <dataHome> directory when multiple nodes are being started");
- }
- }
- private List<File> extractAndProcessHomeDirectories(final Product product) throws MojoExecutionException {
- final List<File> homeDirectories = getHomeDirectories(product);
- final List<Node> nodes = product.getNodes();
- // Check whether the user specified the home directory
- if (nodes.size() == 1 && isNotBlank(product.getDataHome())) {
- // We're in single-node mode; use the one provided home as-is
- return homeDirectories;
- }
- final File productHomeData = getProductHomeData(product);
- if (productHomeData != null) {
- for (int i = 0; i < nodes.size(); i++) {
- final File homeDirectory = homeDirectories.get(i); // should be the same size as node list
- extractAndProcessHomeDirectory(product, productHomeData, homeDirectory, i);
- }
- }
- return homeDirectories;
- }
- private void extractAndProcessHomeDirectory(
- final Product product, final File productHomeData, final File homeDirectory, final int nodeIndex)
- throws MojoExecutionException {
- if (!homeDirectory.exists()) {
- extractProductHomeData(productHomeData, homeDirectory, product);
- makeDirectory(homeDirectory);
- processHomeDirectory(product, nodeIndex, homeDirectory);
- }
- overrideAndPatchHomeDir(homeDirectory, product);
- }
- @Nullable
- private File getProductHomeData(final Product product) throws MojoExecutionException {
- final String dataPath = product.getDataPath();
- if (isNotBlank(dataPath)) {
- // Use custom data path (to a ZIP or directory)
- final File dataPathAsFile = new File(dataPath);
- if (dataPathAsFile.exists()) {
- return dataPathAsFile;
- }
- throw new MojoExecutionException("Unable to use custom test resources set by <dataPath>. '" +
- dataPathAsFile.getAbsolutePath() + "' does not exist");
- }
- File productHomeZip = null;
- // No custom data path, so we use the default
- final Optional<ProductArtifact> maybeTestResourcesArtifact = getTestResourcesArtifact();
- if (maybeTestResourcesArtifact.isPresent()) {
- final ProductArtifact testResourcesArtifact = maybeTestResourcesArtifact.get();
- // Make sure we have the latest if needed
- if (isBlank(product.getDataVersion()) || RELEASE_VERSION.equals(product.getDataVersion()) ||
- LATEST_VERSION.equals(product.getDataVersion())) {
- setLatestDataVersion(product, testResourcesArtifact);
- }
- final ProductArtifact testResources = new ProductArtifact(
- testResourcesArtifact.getGroupId(), testResourcesArtifact.getArtifactId(), product.getDataVersion());
- productHomeZip = goals.copyZip(
- getBaseDirectory(product), testResources, testResources.getArtifactId() + ".zip");
- }
- return productHomeZip;
- }
- private void setLatestDataVersion(final Product product, final ProductArtifact testResourcesArtifact)
- throws MojoExecutionException {
- log.info("determining latest stable data version...");
- final Artifact dataArtifact = repositorySystem.createProjectArtifact(
- testResourcesArtifact.getGroupId(), testResourcesArtifact.getArtifactId(), product.getDataVersion());
- final String stableVersion = product.getArtifactRetriever().getLatestStableVersion(dataArtifact);
- log.info("using latest stable data version: " + stableVersion);
- testResourcesArtifact.setVersion(stableVersion);
- product.setDataVersion(stableVersion);
- }
- private void extractProductHomeData(final File productHomeData, final File homeDir, final Product product)
- throws MojoExecutionException {
- final File tmpDir = new File(getBaseDirectory(product), "tmp-resources");
- makeDirectory(tmpDir);
- try {
- if (productHomeData.isFile()) {
- final File tmp = new File(getBaseDirectory(product), product.getId() + "-home");
- unzip(productHomeData, tmpDir.getPath());
- final File rootDir = getRootDir(tmpDir, product);
- copyDirectory(rootDir, getBaseDirectory(product), true);
- moveDirectory(tmp, homeDir);
- } else if (productHomeData.isDirectory()) {
- copyDirectory(productHomeData, homeDir, true);
- }
- } catch (final IOException ex) {
- throw new MojoExecutionException("Unable to copy home directory", ex);
- }
- }
- private void overrideAndPatchHomeDir(final File homeDir, final Product product) throws MojoExecutionException {
- File srcDir = null;
- String overridesPath = product.getDataOverridesPath();
- if (isNotBlank(overridesPath)) {
- srcDir = new File(overridesPath);
- if (!srcDir.isDirectory()) {
- srcDir = new File(project.getBasedir(), overridesPath);
- }
- }
- if (srcDir == null || !srcDir.isDirectory()) {
- srcDir = new File(project.getBasedir(), "src/test/resources/" + product.getInstanceId() + "-home");
- }
- try {
- if (srcDir.exists() && homeDir.exists()) {
- copyDirectory(srcDir, homeDir, false);
- }
- } catch (IOException e) {
- throw new MojoExecutionException("Unable to override files using " + srcDir.getAbsolutePath(), e);
- }
- }
- /**
- * Returns the root directory of the test data for the given product. This implementation expects the given
- * directory to contain a single sub-directory, and returns it as the root directory.
- *
- * @param tmpDir the directory in which to look for the root directory
- * @param product the product being run
- * @return the test data root directory
- * @throws MojoExecutionException if there's not exactly one entry in the given directory
- * @throws IOException if an I/O error occurs
- */
- @Nonnull
- protected File getRootDir(final File tmpDir, final Product product) throws MojoExecutionException, IOException {
- final File[] topLevelFiles = tmpDir.listFiles();
- if (topLevelFiles == null) {
- throw new MojoExecutionException("Could not read files in " + tmpDir);
- }
- switch (topLevelFiles.length) {
- case 0:
- throw new MojoExecutionException("No files in " + tmpDir);
- case 1:
- return topLevelFiles[0]; // happy path
- default:
- final String filenames = stream(topLevelFiles)
- .map(File::getName)
- .collect(joining(", "));
- throw new MojoExecutionException(
- "Expected a single top-level directory in test resources, but found: " + filenames);
- }
- }
- /**
- * Takes 'app' (the file of the application - either .war or the exploded directory),
- * adds the artifacts, then returns the 'app'.
- *
- * @param product the product
- * @param homeDirs the product's home directory(s)
- * @param productFile the product directory or artifact
- * @return if {@code app} was a dir, returns it; if {@code app} was a WAR, returns that
- * @throws MojoExecutionException if something goes wrong
- */
- private File addArtifactsAndOverrides(final Product product, final List<File> homeDirs, final File productFile)
- throws MojoExecutionException {
- try {
- final File productDir;
- if (productFile.isFile()) {
- productDir = new File(getBaseDirectory(product), "webapp");
- if (!productDir.exists()) {
- unzip(productFile, productDir.getAbsolutePath());
- }
- } else {
- productDir = productFile;
- }
- for (final File homeDir : homeDirs) {
- addArtifacts(product, homeDir, productDir);
- overrideWarFiles(product, homeDir, productDir);
- }
- if (productFile.isFile()) {
- final File warFile = new File(productFile.getParentFile(), getId() + ".war");
- zipChildren(warFile, productDir);
- return warFile;
- } else {
- return productDir;
- }
- } catch (final Exception e) {
- throw new MojoExecutionException(e.getMessage(), e);
- }
- }
- private void overrideWarFiles(final Product product, final File homeDir, final File appDir)
- throws MojoExecutionException {
- try {
- addOverrides(appDir, product);
- customiseInstance(product, homeDir, appDir);
- fixJvmArgs(product);
- } catch (IOException e) {
- throw new MojoExecutionException(
- "Unable to override WAR files using src/test/resources/" + product.getInstanceId() + "-app", e);
- }
- }
- /**
- * Each product handler can add specific operations on the application's home and war.
- * By default no operation is performed in this hook.
- *
- * @param product the product
- * @param homeDir the home directory
- * @param explodedWarDir the directory containing the product's exploded WAR
- * @throws MojoExecutionException if customisation fails
- */
- protected void customiseInstance(Product product, File homeDir, File explodedWarDir) throws MojoExecutionException {
- // No operation by default
- }
- /**
- * Fix jvmArgs, providing necessary defaults.
- *
- * @param product product for which to fix jvmArgs
- */
- protected void fixJvmArgs(final Product product) {
- final String jvmArgs = JvmArgsFix.defaults()
- .apply(product.getJvmArgs());
- product.setJvmArgs(jvmArgs);
- }
- private void addArtifacts(final Product product, final File homeDir, final File appDir)
- throws IOException, MojoExecutionException {
- File pluginsDir = getUserInstalledPluginsDirectory(product, appDir, homeDir).orElse(null);
- File bundledPluginsDir = new File(getBaseDirectory(product), "bundled-plugins");
- makeDirectory(bundledPluginsDir);
- // add bundled plugins
- final File bundledPluginsFile = getBundledPluginPath(product, appDir);
- if (bundledPluginsFile.exists()) {
- if (bundledPluginsFile.isDirectory()) {
- bundledPluginsDir = bundledPluginsFile;
- } else {
- unzip(bundledPluginsFile, bundledPluginsDir.getPath());
- }
- }
- if (isStaticPlugin()) {
- if (!supportsStaticPlugins()) {
- throw new MojoExecutionException("According to your atlassian-plugin.xml file, this plugin is not " +
- "atlassian-plugins version 2. This app currently only supports atlassian-plugins " +
- "version 2.");
- }
- pluginsDir = new File(appDir, "WEB-INF/lib");
- }
- if (pluginsDir == null) {
- pluginsDir = bundledPluginsDir;
- }
- createDirectory(pluginsDir);
- // add this plugin itself, if enabled
- if (Boolean.TRUE.equals(product.isInstallPlugin())) {
- addThisPluginToDirectory(pluginsDir);
- addTestPluginToDirectory(pluginsDir);
- }
- // add plugins2 plugins if necessary
- if (!isStaticPlugin()) {
- addArtifactsToDirectory(pluginProvider.provide(product), pluginsDir);
- }
- // add lib artifacts
- addArtifactsToDirectory(product.getLibArtifacts(), new File(appDir, getLibArtifactTargetDir()));
- // add plugins provided by applications
- final List<ProductArtifact> applications = applicationMapper.provideApplications(product);
- extractApplicationPlugins(applications, pluginsDir);
- final List<ProductArtifact> plugins = new ArrayList<>();
- plugins.addAll(product.getBundledArtifacts());
- plugins.addAll(getAdditionalPlugins(product));
- if (product.hasUserConfiguredLicense() && useBackdoorToInstallLicense()) {
- // Install the license backdoor, so that we can replace the default license with the user-provided one
- plugins.add(LICENSE_BACKDOOR_PLUGIN);
- }
- addArtifactsToDirectory(plugins, bundledPluginsDir);
- final String[] bundledPlugins = bundledPluginsDir.list();
- if (bundledPlugins != null && bundledPlugins.length > 0 && !bundledPluginsFile.isDirectory()) {
- zipChildren(bundledPluginsFile, bundledPluginsDir);
- }
- if (product.getLog4jProperties() != null) {
- final Optional<String> log4jPropertiesPath = getLog4jPropertiesPath();
- if (log4jPropertiesPath.isPresent()) { // no lambda because exception thrown
- copyFile(product.getLog4jProperties(), new File(appDir, log4jPropertiesPath.get()));
- }
- }
- }
- /**
- * Hook for subclasses to read/modify the contents of the home directory before starting the product.
- *
- * This implementation makes all the replacements specified by {@link #getReplacements} to the configuration files
- * specified by {@link #getConfigFiles(Product, File)}.
- *
- * @param product the product being started
- * @param nodeIndex the zero-based index of the node being started
- * @param homeDir the node's home directory
- */
- protected void processHomeDirectory(final Product product, final int nodeIndex, final File homeDir)
- throws MojoExecutionException {
- replace(getConfigFiles(product, homeDir), getReplacements(product, nodeIndex), false);
- }
- /**
- * List the configuration files. Used when doing a snapshot to reopen on another
- * machine, with different port, context path, path, instanceId
- * <p/>
- * Files returned by this method are guaranteed to be reversed when creating the home zip.
- *
- * @param product the product
- * @param snapshotDir A snapshot equivalent to the home in most cases. It is a copy of the folder returned by
- * {@link #getSnapshotDirectories(Product)}
- * @return a mutable list of files
- */
- @Nonnull
- protected List<File> getConfigFiles(@Nonnull final Product product, @Nonnull final File snapshotDir) {
- return new ArrayList<>();
- }
- @Nonnull
- @Override
- public File getBaseDirectory(@Nonnull final Product ctx) {
- return ProjectUtils.createDirectory(new File(project.getBuild().getDirectory(), ctx.getInstanceId()));
- }
- /**
- * Lists parameters which must be replaced in the configuration files of the home directory.
- * <p/>
- * Replacements returned by this method are guaranteed to be reversed when creating the home zip.
- *
- * @param product the product
- * @param nodeIndex the zero-based node index
- * @return a mutable list of replacements
- */
- @Nonnull
- protected List<Replacement> getReplacements(@Nonnull final Product product, final int nodeIndex) {
- // Standard replacements:
- final List<Replacement> replacements = new ArrayList<>();
- final String buildDirectory = project.getBuild().getDirectory();
- final String baseDirectory = getBaseDirectory(product).getAbsolutePath();
- final Optional<File> homeDirectory = getOnlyHomeDirectory(product);
- replacements.add(new Replacement("%PROJECT_BUILD_DIR%", buildDirectory));
- replacements.add(new Replacement("%PRODUCT_BASE_DIR%", baseDirectory));
- homeDirectory.ifPresent(homeDir ->
- replacements.add(new Replacement("%PRODUCT_HOME_DIR%", homeDir.getAbsolutePath())));
- // These replacements are especially for Fecru, but there's no reason not to find them in other config files
- replacements.add(replaceDirectory("%PROJECT_BUILD_DIR_URL_ENCODED%", buildDirectory));
- replacements.add(replaceDirectory("%PRODUCT_BASE_DIR_URL_ENCODED%", baseDirectory));
- homeDirectory.ifPresent(file ->
- replacements.add(replaceDirectory("%PRODUCT_HOME_DIR_URL_ENCODED%", file.getAbsolutePath())));
- replacements.add(onlyWhenUnzipping("localhost", product.getServer()));
- try {
- final String localHostName = InetAddress.getLocalHost().getHostName();
- replacements.add(new Replacement("%LOCAL_HOST_NAME%", localHostName));
- } catch (UnknownHostException e) {
- // If we can't get the local computer's hostname, it's probable no product could,
- // so we don't need to search-replace the value.
- }
- return replacements;
- }
- private static Replacement replaceDirectory(final String placeholder, final String directory) {
- final String encoding = StandardCharsets.UTF_8.name();
- try {
- return new Replacement(placeholder, encode(propertiesEncode(directory), encoding));
- } catch (UnsupportedEncodingException e) {
- throw new IllegalStateException(encoding + " should be supported on any JVM", e);
- }
- }
- private Optional<File> getOnlyHomeDirectory(final Product product) {
- final List<File> homeDirectories = getHomeDirectories(product);
- if (homeDirectories.size() == 1) {
- return Optional.of(homeDirectories.get(0));
- }
- return empty();
- }
- @Nonnull
- @Override
- public final List<File> getSnapshotDirectories(@Nonnull Product product) {
- return getHomeDirectories(product);
- }
- /**
- * Extracts (or copies) the program files for the given product.
- *
- * @param product the product for which to extract the program files
- * @return the extracted/copied file or directory
- * @throws MojoExecutionException if execution fails
- */
- @Nonnull
- protected abstract File extractApplication(Product product) throws MojoExecutionException;
- /**
- * Starts the given product.
- *
- * @param product the product to start
- * @param productFile the product's own root directory or artifact
- * @param systemProperties any system properties to be passed to the product (one map per node)
- * @return the node(s) on which the product is running; a non-empty list
- * @throws MojoExecutionException if the operation fails
- */
- @Nonnull
- protected abstract List<Node> startProduct(
- Product product, File productFile, List<Map<String, String>> systemProperties)
- throws MojoExecutionException;
- /**
- * Indicates whether this product supports static plugins.
- *
- * @return see description
- */
- protected abstract boolean supportsStaticPlugins();
- /**
- * Returns the bundled plugin path for the given product.
- *
- * @param product the product
- * @param productDir the directory in which the product is installed
- * @return see description
- */
- @Nonnull
- protected abstract File getBundledPluginPath(Product product, File productDir);
- /**
- * Returns the directory in which user-installed plugins should be placed.
- *
- * @param product the product
- * @param webappDir the product's webapp directory
- * @param homeDir the home directory
- * @return empty for no such directory
- */
- @Nonnull
- protected abstract Optional<File> getUserInstalledPluginsDirectory(Product product, File webappDir, File homeDir);
- /**
- * Hook for product handlers to specify additional plugins to be loaded when the product starts. This implementation
- * returns an empty list.
- *
- * @param product the product to receive the plugins
- * @return any additional plugins
- * @throws MojoExecutionException if execution fails
- */
- @Nonnull
- protected List<ProductArtifact> getAdditionalPlugins(final Product product) throws MojoExecutionException {
- return emptyList();
- }
- /**
- * Returns the path to the product's {@code log4j.properties} file.
- *
- * @return empty if there isn't one
- */
- @Nonnull
- protected Optional<String> getLog4jPropertiesPath() {
- return empty();
- }
- /**
- * Indicates whether the current Maven project is a static plugin.
- *
- * @return see description
- * @throws IOException if there is an I/O error
- */
- protected boolean isStaticPlugin() throws IOException {
- final File atlassianPluginXml = new File(project.getBasedir(), "src/main/resources/atlassian-plugin.xml");
- if (atlassianPluginXml.exists()) {
- final String text = org.apache.commons.io.FileUtils.readFileToString(atlassianPluginXml, StandardCharsets.UTF_8);
- return !text.contains("pluginsVersion=\"2\"") && !text.contains("plugins-version=\"2\"");
- } else {
- // probably an osgi bundle
- return false;
- }
- }
- private void addThisPluginToDirectory(final File targetDir) throws IOException {
- final File thisPlugin = getPluginFile();
- if (thisPlugin.exists()) {
- // remove any existing version
- final Iterator<File> files = iterateFiles(targetDir, null, false);
- while (files.hasNext()) {
- final File file = files.next();
- if (doesFileNameMatchArtifact(file.getName(), project.getArtifactId())) {
- delete(file.toPath());
- }
- }
- // add the plugin jar to the directory
- copyFile(thisPlugin, new File(targetDir, thisPlugin.getName()));
- } else {
- log.info("No plugin in the current project - " + thisPlugin.getAbsolutePath());
- }
- }
- private void addTestPluginToDirectory(final File targetDir) throws IOException {
- final File testPluginFile = getTestPluginFile();
- if (testPluginFile.exists()) {
- // add the test plugin jar to the directory
- copyFile(testPluginFile, new File(targetDir, testPluginFile.getName()));
- }
- }
- private File getPluginFile() {
- return new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + ".jar");
- }
- private File getTestPluginFile() {
- return new File(project.getBuild().getDirectory(), project.getBuild().getFinalName() + "-tests.jar");
- }
- private void addArtifactsToDirectory(final List<ProductArtifact> artifacts, final File pluginsDir)
- throws MojoExecutionException {
- // copy the all the plugins we want in the webapp, first removing plugins from the webapp that we want to update
- if (!artifacts.isEmpty() && pluginsDir.isDirectory()) {
- listFiles(pluginsDir, null, false).stream()
- .filter(File::isFile)
- .filter(file -> fileMatchesAnyArtifact(file, artifacts))
- .forEach(org.apache.commons.io.FileUtils::deleteQuietly);
- goals.copyPlugins(pluginsDir, artifacts);
- }
- }
- private boolean fileMatchesAnyArtifact(final File file, final Collection<ProductArtifact> artifacts) {
- return artifacts.stream()
- .map(ProductArtifact::getArtifactId)
- .anyMatch(artifactId -> doesFileNameMatchArtifact(file.getName(), artifactId));
- }
- private void extractApplicationPlugins(final List<ProductArtifact> applications, final File bundledPluginsDir)
- throws IOException {
- for (final ProductArtifact application : applications) {
- final File artifact = resolveApplicationArtifact(application).getFile();
- log.info("Extracting " + artifact.getAbsolutePath() + " into " + bundledPluginsDir.getAbsolutePath());
- unzip(artifact, bundledPluginsDir.getAbsolutePath(), 0, true, Pattern.compile(".*\\.jar"));
- log.debug("Extracted.");
- }
- }
- private Artifact resolveApplicationArtifact(final ProductArtifact application) {
- final Artifact artifact = repositorySystem.createArtifact(application.getGroupId(), application.getArtifactId(),
- application.getVersion(), "compile", "obr");
- final ArtifactResolutionResult resolutionResult = resolve(artifact);
- if (resolutionResult.isSuccess()) {
- return artifact;
- }
- throw new ArtifactResolutionException(resolutionResult);
- }
- private ArtifactResolutionResult resolve(final Artifact artifact) {
- final ArtifactResolutionRequest artifactResolutionRequest = new ArtifactResolutionRequest();
- artifactResolutionRequest.setArtifact(artifact);
- final MojoExecutor.ExecutionEnvironment executionEnvironment = context.getExecutionEnvironment();
- artifactResolutionRequest.setLocalRepository(executionEnvironment.getMavenSession().getLocalRepository());
- artifactResolutionRequest.setRemoteRepositories(
- executionEnvironment.getMavenProject().getRemoteArtifactRepositories());
- return artifactResolver.resolve(artifactResolutionRequest);
- }
- private void addOverrides(final File appDir, final Product product) throws IOException {
- final File srcDir = new File(project.getBasedir(), "src/test/resources/" + product.getInstanceId() + "-app");
- if (srcDir.exists() && appDir.exists()) {
- copyDirectory(srcDir, appDir, true);
- }
- }
- /**
- * Merges the properties: pom.xml overrides {@code AbstractProductHandlerMojo#setDefaultValues}, which overrides the
- * Product Handler.
- *
- * @param product the Product
- * @return the list of complete system property maps (one map per product node)
- */
- @Nonnull
- protected final List<Map<String, String>> mergeSystemProperties(final Product product) {
- final List<Map<String, String>> mergedPropertyMaps = new ArrayList<>();
- final int nodeCount = product.getNodes().size();
- for (int i = 0; i < nodeCount; i++) {
- mergedPropertyMaps.add(mergeSystemProperties(product, i));
- }
- return mergedPropertyMaps;
- }
- private Map<String, String> mergeSystemProperties(final Product product, final int nodeIndex) {
- // Start from the base properties
- final Map<String, String> properties = new HashMap<>(getSystemProperties(product, nodeIndex));
- // Set the JARs to be skipped when scanning for TLDs and web fragments (read from context.xml by Tomcat)
- properties.put("jarsToSkip", getJarsToSkipWhenScanningForTldsAndWebFragments());
- // Enter the System Property Variables from product/node context, overwriting duplicates
- properties.putAll(product.getSystemPropertiesForNode(nodeIndex));
- // Overwrite the default system properties with user input arguments
- final Properties userProperties = context.getExecutionEnvironment().getMavenSession().getUserProperties();
- userProperties.forEach((key, value) -> properties.put((String) key, (String) value));
- return properties;
- }
- private String getJarsToSkipWhenScanningForTldsAndWebFragments() {
- final Set<String> jarsToSkip = new HashSet<>();
- jarsToSkip.add("${tomcat.util.scan.StandardJarScanFilter.jarsToSkip}"); // the Tomcat default
- jarsToSkip.addAll(getExtraJarsToSkipWhenScanningForTldsAndWebFragments());
- return join(",", jarsToSkip);
- }
- /**
- * Products should override this method in order to avoid scanning JARs known not to contain TLDs or web fragments.
- * This implementation returns an empty, immutable list.
- *
- * @return a list of any extra JAR name patterns to skip, e.g. "foo*.jar"
- */
- @Nonnull
- protected Collection<String> getExtraJarsToSkipWhenScanningForTldsAndWebFragments() {
- return emptyList();
- }
- /**
- * Returns any system properties that this Product Handler wants to pass to the given node.
- *
- * @param product the product being started
- * @param nodeIndex the zero-based index of the node being started
- * @return a list of property maps, one map per node
- */
- @Nonnull
- protected abstract Map<String, String> getSystemProperties(Product product, int nodeIndex);
- /**
- * Returns the directory into which JARs listed in {@code <libArtifacts>} are copied.
- *
- * This implementation returns {@value #TOMCAT_LIB_DIR}.
- *
- * @return the directory where lib artifacts should be written
- */
- @Nonnull
- protected String getLibArtifactTargetDir() {
- return TOMCAT_LIB_DIR;
- }
- /**
- * Sets the versions of the arguments to the latest stable version of the product.
- *
- * @param product the product whose version to set
- * @param productArtifact the artifact whose version to set
- * @throws MojoExecutionException if execution fails
- */
- protected final void setLatestStableVersion(final Product product, final ProductArtifact productArtifact)
- throws MojoExecutionException {
- log.info("determining latest stable product version...");
- final Artifact warArtifact = repositorySystem.createProjectArtifact(
- productArtifact.getGroupId(), productArtifact.getArtifactId(), productArtifact.getVersion());
- final String stableVersion = product.getArtifactRetriever().getLatestStableVersion(warArtifact);
- log.info("using latest stable product version: " + stableVersion);
- productArtifact.setVersion(stableVersion);
- product.setVersion(stableVersion);
- }
- /**
- * Returns the artifact for the given product.
- *
- * @param product the product for which to create the artifact
- * @return a new artifact
- */
- @Nonnull
- protected final ProductArtifact getArtifact(final Product product) {
- final ProductArtifact defaultArtifact = getArtifact();
- return new ProductArtifact(
- firstNotNull(product.getGroupId(), defaultArtifact.getGroupId()),
- firstNotNull(product.getArtifactId(), defaultArtifact.getArtifactId()),
- firstNotNull(product.getVersion(), defaultArtifact.getVersion()));
- }
- }