org.apache.maven.plugins.site.deploy.AbstractDeployMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of maven-site-plugin Show documentation
Show all versions of maven-site-plugin Show documentation
The Maven Site Plugin is a plugin that generates a site for the current project.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.maven.plugins.site.deploy;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.apache.maven.artifact.manager.WagonManager;
import org.apache.maven.doxia.site.decoration.inheritance.URIPathDescriptor;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.model.Site;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.site.AbstractSiteMojo;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecrypter;
import org.apache.maven.settings.crypto.SettingsDecryptionResult;
import org.apache.maven.wagon.CommandExecutionException;
import org.apache.maven.wagon.CommandExecutor;
import org.apache.maven.wagon.ConnectionException;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.UnsupportedProtocolException;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.authentication.AuthenticationException;
import org.apache.maven.wagon.authentication.AuthenticationInfo;
import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.observers.Debug;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.repository.Repository;
import org.codehaus.plexus.PlexusConstants;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.context.Context;
import org.codehaus.plexus.context.ContextException;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
import org.codehaus.plexus.util.StringUtils;
/**
* Abstract base class for deploy mojos.
* Since 2.3 this includes {@link SiteStageMojo} and {@link SiteStageDeployMojo}.
*
* @author ltheussl
* @since 2.3
*/
public abstract class AbstractDeployMojo extends AbstractSiteMojo implements Contextualizable {
/**
* Directory containing the generated project sites and report distributions.
*
* @since 2.3
*/
@Parameter(alias = "outputDirectory", defaultValue = "${project.reporting.outputDirectory}", required = true)
private File inputDirectory;
/**
* Whether to run the "chmod" command on the remote site after the deploy.
* Defaults to "true".
*
* @since 2.1
*/
@Parameter(property = "maven.site.chmod", defaultValue = "true")
private boolean chmod;
/**
* The mode used by the "chmod" command. Only used if chmod = true.
* Defaults to "g+w,a+rX".
*
* @since 2.1
*/
@Parameter(property = "maven.site.chmod.mode", defaultValue = "g+w,a+rX")
private String chmodMode;
/**
* The options used by the "chmod" command. Only used if chmod = true.
* Defaults to "-Rf".
*
* @since 2.1
*/
@Parameter(property = "maven.site.chmod.options", defaultValue = "-Rf")
private String chmodOptions;
/**
* Set this to 'true' to skip site deployment.
*
* @since 3.0
*/
@Parameter(property = "maven.site.deploy.skip", defaultValue = "false")
private boolean skipDeploy;
/**
*/
@Component
private WagonManager wagonManager; // maven-compat
/**
* The current user system settings for use in Maven.
*/
@Parameter(defaultValue = "${settings}", readonly = true)
private Settings settings;
/**
* @since 3.0-beta-2
*/
@Parameter(defaultValue = "${session}", readonly = true)
protected MavenSession mavenSession;
private String topDistributionManagementSiteUrl;
private Site deploySite;
private PlexusContainer container;
/**
* {@inheritDoc}
*/
public void execute() throws MojoExecutionException {
if (skip && isDeploy()) {
getLog().info("maven.site.skip = true: Skipping site deployment");
return;
}
if (skipDeploy && isDeploy()) {
getLog().info("maven.site.deploy.skip = true: Skipping site deployment");
return;
}
deployTo(new Repository(getDeploySite().getId(), getDeploySite().getUrl()));
}
/**
* Make sure the given URL ends with a slash.
*
* @param url a String
* @return if url already ends with '/' it is returned unchanged.
* Otherwise a '/' character is appended.
*/
protected static String appendSlash(final String url) {
if (url.endsWith("/")) {
return url;
} else {
return url + "/";
}
}
/**
* Detect if the mojo is staging or deploying.
*
* @return true
if the mojo is for deploy and not staging (local or deploy)
*/
protected abstract boolean isDeploy();
/**
* Get the top distribution management site url, used for module relative path calculations.
* This should be a top-level URL, ie above modules and locale sub-directories. Each deploy mojo
* can tweak algorithm to determine this top site by implementing determineTopDistributionManagementSiteUrl().
*
* @return the site for deployment
* @throws MojoExecutionException in case of issue
* @see #determineTopDistributionManagementSiteUrl()
*/
protected String getTopDistributionManagementSiteUrl() throws MojoExecutionException {
if (topDistributionManagementSiteUrl == null) {
topDistributionManagementSiteUrl = determineTopDistributionManagementSiteUrl();
if (!isDeploy()) {
getLog().debug("distributionManagement.site.url relative path: " + getDeployModuleDirectory());
}
}
return topDistributionManagementSiteUrl;
}
protected abstract String determineTopDistributionManagementSiteUrl() throws MojoExecutionException;
/**
* Get the site used for deployment, with its id to look up credential settings and the target URL for the deploy.
* This should be a top-level URL, ie above modules and locale sub-directories. Each deploy mojo
* can tweak algorithm to determine this deploy site by implementing determineDeploySite().
*
* @return the site for deployment
* @throws MojoExecutionException in case of issue
* @see #determineDeploySite()
*/
protected Site getDeploySite() throws MojoExecutionException {
if (deploySite == null) {
deploySite = determineDeploySite();
}
return deploySite;
}
protected abstract Site determineDeploySite() throws MojoExecutionException;
/**
* Find the relative path between the distribution URLs of the top site and the current project.
*
* @return the relative path or "./" if the two URLs are the same.
* @throws MojoExecutionException in case of issue
*/
protected String getDeployModuleDirectory() throws MojoExecutionException {
String to = getSite(project).getUrl();
getLog().debug("Mapping url source calculation: ");
String from = getTopDistributionManagementSiteUrl();
String relative = siteTool.getRelativePath(to, from);
// SiteTool.getRelativePath() uses File.separatorChar,
// so we need to convert '\' to '/' in order for the URL to be valid for Windows users
relative = relative.replace('\\', '/');
return ("".equals(relative)) ? "./" : relative;
}
/**
* Use wagon to deploy the generated site to a given repository.
*
* @param repository the repository to deploy to.
* This needs to contain a valid, non-null {@link Repository#getId() id}
* to look up credentials for the deploy, and a valid, non-null
* {@link Repository#getUrl() scm url} to deploy to.
* @throws MojoExecutionException if the deploy fails.
*/
private void deployTo(final Repository repository) throws MojoExecutionException {
if (!inputDirectory.exists()) {
throw new MojoExecutionException("The site does not exist, please run site:site first");
}
if (getLog().isDebugEnabled()) {
getLog().debug("Deploying to '" + repository.getUrl() + "',\n Using credentials from server id '"
+ repository.getId() + "'");
}
deploy(inputDirectory, repository);
}
private void deploy(final File directory, final Repository repository) throws MojoExecutionException {
// TODO: work on moving this into the deployer like the other deploy methods
final Wagon wagon = getWagon(repository, wagonManager);
try {
SettingsDecrypter settingsDecrypter = container.lookup(SettingsDecrypter.class);
ProxyInfo proxyInfo = getProxy(repository, settingsDecrypter);
push(directory, repository, wagon, proxyInfo, getLocales(), getDeployModuleDirectory());
if (chmod) {
chmod(wagon, repository, chmodOptions, chmodMode);
}
} catch (ComponentLookupException cle) {
throw new MojoExecutionException("Unable to lookup SettingsDecrypter: " + cle.getMessage(), cle);
} finally {
try {
wagon.disconnect();
} catch (ConnectionException e) {
getLog().error("Error disconnecting wagon - ignored", e);
}
}
}
private Wagon getWagon(final Repository repository, final WagonManager manager) throws MojoExecutionException {
final Wagon wagon;
try {
wagon = manager.getWagon(repository);
} catch (UnsupportedProtocolException e) {
String shortMessage = "Unsupported protocol: '" + repository.getProtocol() + "' for site deployment to "
+ "distributionManagement.site.url=" + repository.getUrl() + ".";
String longMessage =
"\n" + shortMessage + "\n" + "Currently supported protocols are: " + getSupportedProtocols() + ".\n"
+ " Protocols may be added through wagon providers.\n" + " For more information, see "
+ "https://maven.apache.org/plugins/maven-site-plugin/examples/adding-deploy-protocol.html";
getLog().error(longMessage);
throw new MojoExecutionException(shortMessage);
} catch (TransferFailedException e) {
throw new MojoExecutionException("Unable to configure Wagon: '" + repository.getProtocol() + "'", e);
}
if (!wagon.supportsDirectoryCopy()) {
throw new MojoExecutionException(
"Wagon protocol '" + repository.getProtocol() + "' doesn't support directory copying");
}
return wagon;
}
private String getSupportedProtocols() {
try {
Set protocols = container.lookupMap(Wagon.class).keySet();
return StringUtils.join(protocols.iterator(), ", ");
} catch (ComponentLookupException e) {
// in the unexpected case there is a problem when instantiating a wagon provider
getLog().error(e);
}
return "";
}
private void push(
final File inputDirectory,
final Repository repository,
final Wagon wagon,
final ProxyInfo proxyInfo,
final List localesList,
final String relativeDir)
throws MojoExecutionException {
AuthenticationInfo authenticationInfo = wagonManager.getAuthenticationInfo(repository.getId());
getLog().debug("authenticationInfo with id '" + repository.getId() + "': "
+ ((authenticationInfo == null) ? "-" : authenticationInfo.getUserName()));
try {
if (getLog().isDebugEnabled()) {
Debug debug = new Debug();
wagon.addSessionListener(debug);
wagon.addTransferListener(debug);
}
if (proxyInfo != null) {
getLog().debug("connect with proxyInfo");
wagon.connect(repository, authenticationInfo, proxyInfo);
} else if (proxyInfo == null && authenticationInfo != null) {
getLog().debug("connect with authenticationInfo and without proxyInfo");
wagon.connect(repository, authenticationInfo);
} else {
getLog().debug("connect without authenticationInfo and without proxyInfo");
wagon.connect(repository);
}
getLog().info("Pushing " + inputDirectory);
// Default is first in the list
final Locale defaultLocale = localesList.get(0);
for (Locale locale : localesList) {
if (locale.equals(defaultLocale)) {
// TODO: this also uploads the non-default locales,
// is there a way to exclude directories in wagon?
getLog().info(" >>> to " + appendSlash(repository.getUrl()) + relativeDir);
wagon.putDirectory(inputDirectory, relativeDir);
} else {
getLog().info(" >>> to " + appendSlash(repository.getUrl()) + locale + "/" + relativeDir);
wagon.putDirectory(new File(inputDirectory, locale.toString()), locale + "/" + relativeDir);
}
}
} catch (ResourceDoesNotExistException
| TransferFailedException
| AuthorizationException
| ConnectionException
| AuthenticationException e) {
throw new MojoExecutionException("Error uploading site", e);
}
}
private static void chmod(
final Wagon wagon, final Repository repository, final String chmodOptions, final String chmodMode)
throws MojoExecutionException {
try {
if (wagon instanceof CommandExecutor) {
CommandExecutor exec = (CommandExecutor) wagon;
exec.executeCommand("chmod " + chmodOptions + " " + chmodMode + " " + repository.getBasedir());
}
// else ? silently ignore, FileWagon is not a CommandExecutor!
} catch (CommandExecutionException e) {
throw new MojoExecutionException("Error uploading site", e);
}
}
/**
* Get proxy information.
*
* Get the ProxyInfo
of the proxy associated with the host
* and the protocol
of the given repository
.
*
*
* Extract from
* J2SE Doc : Networking Properties - nonProxyHosts : "The value can be a list of hosts,
* each separated by a |, and in addition a wildcard character (*) can be used for matching"
*
*
* Defensively support comma (",") and semi colon (";") in addition to pipe ("|") as separator.
*
*
* @param repository the Repository to extract the ProxyInfo from
* @param wagonManager the WagonManager used to connect to the Repository
* @return a ProxyInfo object instantiated or null
if no matching proxy is found
*/
public static ProxyInfo getProxyInfo(Repository repository, WagonManager wagonManager) {
ProxyInfo proxyInfo = wagonManager.getProxy(repository.getProtocol());
if (proxyInfo == null) {
return null;
}
String host = repository.getHost();
String nonProxyHostsAsString = proxyInfo.getNonProxyHosts();
for (String nonProxyHost : StringUtils.split(nonProxyHostsAsString, ",;|")) {
if (StringUtils.contains(nonProxyHost, "*")) {
// Handle wildcard at the end, beginning or middle of the nonProxyHost
final int pos = nonProxyHost.indexOf('*');
String nonProxyHostPrefix = nonProxyHost.substring(0, pos);
String nonProxyHostSuffix = nonProxyHost.substring(pos + 1);
// prefix*
if (StringUtils.isNotEmpty(nonProxyHostPrefix)
&& host.startsWith(nonProxyHostPrefix)
&& StringUtils.isEmpty(nonProxyHostSuffix)) {
return null;
}
// *suffix
if (StringUtils.isEmpty(nonProxyHostPrefix)
&& StringUtils.isNotEmpty(nonProxyHostSuffix)
&& host.endsWith(nonProxyHostSuffix)) {
return null;
}
// prefix*suffix
if (StringUtils.isNotEmpty(nonProxyHostPrefix)
&& host.startsWith(nonProxyHostPrefix)
&& StringUtils.isNotEmpty(nonProxyHostSuffix)
&& host.endsWith(nonProxyHostSuffix)) {
return null;
}
} else if (host.equals(nonProxyHost)) {
return null;
}
}
return proxyInfo;
}
/**
* Get proxy information.
*
* @param repository the Repository to extract the ProxyInfo from
* @param settingsDecrypter settings password decrypter
* @return a ProxyInfo object instantiated or null
if no matching proxy is found.
*/
private ProxyInfo getProxy(Repository repository, SettingsDecrypter settingsDecrypter) {
String protocol = repository.getProtocol();
String url = repository.getUrl();
getLog().debug("repository protocol " + protocol);
String originalProtocol = protocol;
// olamy: hackish here protocol (wagon hint in fact !) is dav
// but the real protocol (transport layer) is http(s)
// and it's the one use in wagon to find the proxy arghhh
// so we will check both
if (StringUtils.equalsIgnoreCase("dav", protocol) && url.startsWith("dav:")) {
url = url.substring(4);
if (url.startsWith("http")) {
try {
URL urlSite = new URL(url);
protocol = urlSite.getProtocol();
getLog().debug("found dav protocol so transform to real transport protocol " + protocol);
} catch (MalformedURLException e) {
getLog().warn("fail to build URL with " + url);
}
}
} else {
getLog().debug("getProxy 'protocol': " + protocol);
}
if (mavenSession != null && protocol != null) {
MavenExecutionRequest request = mavenSession.getRequest();
if (request != null) {
List proxies = request.getProxies();
if (proxies != null) {
for (Proxy proxy : proxies) {
if (proxy.isActive()
&& (protocol.equalsIgnoreCase(proxy.getProtocol())
|| originalProtocol.equalsIgnoreCase(proxy.getProtocol()))) {
SettingsDecryptionResult result =
settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(proxy));
proxy = result.getProxy();
ProxyInfo proxyInfo = new ProxyInfo();
proxyInfo.setHost(proxy.getHost());
// so hackish for wagon the protocol is https for site dav:
// dav:https://dav.codehaus.org/mojo/
proxyInfo.setType(protocol); // proxy.getProtocol() );
proxyInfo.setPort(proxy.getPort());
proxyInfo.setNonProxyHosts(proxy.getNonProxyHosts());
proxyInfo.setUserName(proxy.getUsername());
proxyInfo.setPassword(proxy.getPassword());
getLog().debug("found proxyInfo "
+ ("host:port " + proxyInfo.getHost() + ":" + proxyInfo.getPort() + ", "
+ proxyInfo.getUserName()));
return proxyInfo;
}
}
}
}
}
getLog().debug("getProxy 'protocol': " + protocol + " no ProxyInfo found");
return null;
}
/**
* {@inheritDoc}
*/
public void contextualize(Context context) throws ContextException {
container = (PlexusContainer) context.get(PlexusConstants.PLEXUS_KEY);
}
/**
* Extract the distributionManagement site from the given MavenProject.
*
* @param project the MavenProject. Not null.
* @return the project site. Not null.
* Also site.getUrl() and site.getId() are guaranteed to be not null.
* @throws MojoExecutionException if any of the site info is missing.
*/
protected static Site getSite(final MavenProject project) throws MojoExecutionException {
final DistributionManagement distributionManagement = project.getDistributionManagement();
if (distributionManagement == null) {
throw new MojoExecutionException("Missing distribution management in project " + getFullName(project));
}
final Site site = distributionManagement.getSite();
if (site == null) {
throw new MojoExecutionException(
"Missing site information in the distribution management of the project " + getFullName(project));
}
if (site.getUrl() == null || site.getId() == null) {
throw new MojoExecutionException(
"Missing site data: specify url and id for project " + getFullName(project));
}
return site;
}
private static String getFullName(MavenProject project) {
return project.getName() + " (" + project.getGroupId() + ':' + project.getArtifactId() + ':'
+ project.getVersion() + ')';
}
/**
* Extract the distributionManagement site of the top level parent of the given MavenProject.
* This climbs up the project hierarchy and returns the site of the last project
* for which {@link #getSite(org.apache.maven.project.MavenProject)} returns a site that resides in the
* same site. Notice that it doesn't take into account if the parent is in the reactor or not.
*
* @param project the MavenProject. Not null
.
* @return the top level site. Not null
.
* Also site.getUrl() and site.getId() are guaranteed to be not null
.
* @throws MojoExecutionException if no site info is found in the tree.
* @see URIPathDescriptor#sameSite(java.net.URI)
*/
protected MavenProject getTopLevelProject(MavenProject project) throws MojoExecutionException {
Site site = getSite(project);
MavenProject parent = project;
while (parent.getParent() != null) {
MavenProject oldProject = parent;
// MSITE-585, MNG-1943
parent = parent.getParent();
Site oldSite = site;
try {
site = getSite(parent);
} catch (MojoExecutionException e) {
return oldProject;
}
// MSITE-600
URIPathDescriptor siteURI = new URIPathDescriptor(URIEncoder.encodeURI(site.getUrl()), "");
URIPathDescriptor oldSiteURI = new URIPathDescriptor(URIEncoder.encodeURI(oldSite.getUrl()), "");
if (!siteURI.sameSite(oldSiteURI.getBaseURI())) {
return oldProject;
}
}
return parent;
}
private static class URIEncoder {
private static final String MARK = "-_.!~*'()";
private static final String RESERVED = ";/?:@&=+$,";
private static String encodeURI(final String uriString) {
final char[] chars = uriString.toCharArray();
final StringBuilder uri = new StringBuilder(chars.length);
// MSITE-750: wagon dav: pseudo-protocol
if (uriString.startsWith("dav:http")) {
// transform dav:http to dav-http
chars[3] = '-';
}
for (char c : chars) {
if ((c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| MARK.indexOf(c) != -1
|| RESERVED.indexOf(c) != -1) {
uri.append(c);
} else {
uri.append('%');
uri.append(Integer.toHexString((int) c));
}
}
return uri.toString();
}
}
}