diff --git a/artifact/plugin.jelly b/artifact/plugin.jelly index 9c6cc49d..f469c251 100644 --- a/artifact/plugin.jelly +++ b/artifact/plugin.jelly @@ -69,6 +69,7 @@ project="${project}" type="${type}" artifact="${artifact}" + artifactIdOverride="${artifactIdOverride}" typeHandler="${typeHandler}" /> @@ -97,6 +98,7 @@ project="${project}" type="${type}" artifact="${artifact}" + artifactIdOverride="${artifactIdOverride}" typeHandler="${typeHandler}" /> diff --git a/artifact/src/main/org/apache/maven/artifact/deployer/DeployBean.java b/artifact/src/main/org/apache/maven/artifact/deployer/DeployBean.java index f8b6aac9..a9d57da1 100644 --- a/artifact/src/main/org/apache/maven/artifact/deployer/DeployBean.java +++ b/artifact/src/main/org/apache/maven/artifact/deployer/DeployBean.java @@ -19,32 +19,31 @@ package org.apache.maven.artifact.deployer; import org.apache.maven.MavenException; import org.apache.maven.project.Project; -import org.apache.maven.repository.ArtifactTypeHandler; -import org.apache.maven.repository.DefaultArtifactTypeHandler; /** - * + * * The Bean which provides access to Artifact Deployement API * from jelly scripts. - * - * @author Michal Maczka + * + * @author Michal Maczka * @version $Id$ */ public class DeployBean { - private ArtifactDeployer artifactDeployer; + private NamedArtifactDeployer artifactDeployer; private Project project; private String artifact; private String type; - private ArtifactTypeHandler typeHandler; + private String artifactIdOverride; + private NamedArtifactTypeHandler typeHandler; public DeployBean() - { - artifactDeployer = new DefaultArtifactDeployer(); + { + artifactDeployer = new NamedArtifactDeployer(); } - public ArtifactTypeHandler getTypeHandler() + public NamedArtifactTypeHandler getTypeHandler() { return typeHandler; } @@ -52,7 +51,7 @@ public class DeployBean /** * @param typeHandler */ - public void setTypeHandler(ArtifactTypeHandler typeHandler) + public void setTypeHandler(NamedArtifactTypeHandler typeHandler) { this.typeHandler = typeHandler; } @@ -97,8 +96,23 @@ public class DeployBean } /** - * - * @throws MavenException + * @return String + */ + public String getArtifactIdOverride() + { + return artifactIdOverride; + } + + /** + * @param newIdOverride The new id. + */ + public void setArtifactIdOverride(String newIdOverride) + { + this.artifactIdOverride = newIdOverride; + } + + /** + * @throws MavenException MavenException */ protected void checkAttributes() throws MavenException { @@ -117,12 +131,16 @@ public class DeployBean } if (typeHandler == null) { - typeHandler = new DefaultArtifactTypeHandler(); + typeHandler = new NamedArtifactTypeHandler(); + if ( artifactIdOverride != null ) + { + typeHandler.setArtifactId( artifactIdOverride ); + } } } /** - * + * @throws MavenException MavenException */ public void deploy() throws MavenException { @@ -131,7 +149,7 @@ public class DeployBean } /** - * + * @throws MavenException MavenException */ public void deploySnapshot() throws MavenException { @@ -140,7 +158,7 @@ public class DeployBean } /** - * + * @throws MavenException MavenException */ public void install() throws MavenException { @@ -149,7 +167,7 @@ public class DeployBean } /** - * + * @throws MavenException MavenException */ public void installSnapshot() throws MavenException { diff --git a/artifact/src/main/org/apache/maven/artifact/deployer/NamedArtifactDeployer.java b/artifact/src/main/org/apache/maven/artifact/deployer/NamedArtifactDeployer.java new file mode 100644 index 00000000..5bfbc37d --- /dev/null +++ b/artifact/src/main/org/apache/maven/artifact/deployer/NamedArtifactDeployer.java @@ -0,0 +1,507 @@ +package org.apache.maven.artifact.deployer; + +/* ==================================================================== + * Copyright 2001-2004 The Apache Software Foundation. + * + * 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. + * ==================================================================== + */ + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.maven.MavenConstants; +import org.apache.maven.MavenException; +import org.apache.maven.artifact.PomRewriter; +import org.apache.maven.project.Project; +import org.apache.maven.wagon.ConnectionException; +import org.apache.maven.wagon.ResourceDoesNotExistException; +import org.apache.maven.wagon.TransferFailedException; +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.events.TransferListener; +import org.apache.maven.wagon.observers.ChecksumObserver; +import org.apache.maven.wagon.providers.file.FileWagon; +import org.apache.maven.wagon.providers.ftp.FtpWagon; +import org.apache.maven.wagon.providers.http.HttpWagon; +import org.apache.maven.wagon.providers.ssh.ScpWagon; +import org.apache.maven.wagon.providers.ssh.SftpWagon; +import org.apache.maven.wagon.providers.sshext.ScpExternalWagon; +import org.apache.maven.wagon.repository.Repository; +import org.codehaus.plexus.util.FileUtils; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.security.NoSuchAlgorithmException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +/** + * Default implementation of Artifact Deployer interface. + * + * @author Lukas Theussl + */ +public class NamedArtifactDeployer extends DefaultArtifactDeployer +{ + + private static final String POM_TYPE = "pom"; + + /** Date/time stamp which is appended to snapshot filenames. */ + private static final String SNAPSHOT_FORMAT = "yyyyMMdd.HHmmss"; + + private String snapshotSignature; + + private static final Log LOG = LogFactory.getLog( NamedArtifactDeployer.class ); + + /** + * @see ArtifactDeployer#deploy(String, String, Project, NamedArtifactTypeHandler) + */ + public void deploy( String artifact, String type, Project project, NamedArtifactTypeHandler handler ) + throws MavenException + { + handleDeploy( type, project, artifact, handler, project.getCurrentVersion() ); + } + + /** + * @see DefaultArtifactDeployer#deploySnapshot(String, String, Project, NamedArtifactTypeHandler) + */ + public void deploySnapshot( String artifact, String type, Project project, NamedArtifactTypeHandler handler ) + throws MavenException + { + handleDeploy( type, project, artifact, handler, MavenConstants.SNAPSHOT_SIGNIFIER ); + } + + private void handleDeploy( String type, Project project, String artifact, NamedArtifactTypeHandler handler, + String version ) + throws MavenException + { + File file; + if ( POM_TYPE.equals( type ) ) + { + file = PomRewriter.getRewrittenPom( project ); + } + else + { + file = getFileForArtifact( artifact ); + } + + // do not deploy POM twice + if ( !POM_TYPE.equals( type ) ) + { + doDeploy( PomRewriter.getRewrittenPom( project ), project, handler, version, POM_TYPE ); + } + + doDeploy( file, project, handler, version, type ); + + snapshotSignature = null; + + } + + /** + * @see ArtifactDeployer#install(String, String, Project, NamedArtifactTypeHandler) + */ + public void install( String artifact, String type, Project project, NamedArtifactTypeHandler handler ) + throws MavenException + { + handleInstall( type, project, artifact, handler, project.getCurrentVersion() ); + } + + /** + * @see ArtifactDeployer#installSnapshot(String, String, Project, NamedArtifactTypeHandler) + */ + public void installSnapshot( String artifact, String type, Project project, NamedArtifactTypeHandler handler ) + throws MavenException + { + handleInstall( type, project, artifact, handler, MavenConstants.SNAPSHOT_SIGNIFIER ); + } + + private void handleInstall( String type, Project project, String artifact, NamedArtifactTypeHandler handler, + String version ) + throws MavenException + { + File file; + if ( POM_TYPE.equals( type ) ) + { + file = PomRewriter.getRewrittenPom( project ); + } + else + { + file = getFileForArtifact( artifact ); + } + + doInstall( file, type, project, version, handler ); + + // do not install twice + if ( !POM_TYPE.equals( type ) ) + { + doInstall( PomRewriter.getRewrittenPom( project ), POM_TYPE, project, version, handler ); + } + } + + /** + * Install given file in local repository + * + * @param file the artifact file to install + * @param type The type of the artiafct + * @param project The project + * @param version String denominating the version of the artifact + * @throws MavenException MavenException + */ + private void doInstall( File file, String type, Project project, String version, NamedArtifactTypeHandler handler ) + throws MavenException + { + try + { + Repository repository = new Repository( "local", "file:" + project.getContext().getMavenRepoLocal() ); + String repositoryPath = handler.constructRepositoryFullPath( type, project, version ); + deployFile( repository, file, repositoryPath, project ); + } + catch ( Exception e ) + { + String msg = "Cannot install file: '" + file + "'. Reason: " + e.getMessage(); + throw new MavenException( msg, e ); + } + } + + private String findSshIdentity() + { + String key = findSshIdentity( System.getProperty( "user.home" ) ); + if ( key != null ) + { + return key; + } + if ( !( System.getProperty( "user.home" ).equals( System.getProperty( "user.home.env" ) ) ) ) + { + key = findSshIdentity( System.getProperty( "user.home.env" ) ); + if ( key != null ) + { + return key; + } + } + LOG.warn( "Unable to locate identity id_rsa, id_dsa or identity - set maven.repo.default.privatekey" ); + return null; + } + + private String findSshIdentity( String home ) + { + if ( home == null ) + { + return null; + } + File sshHome = new File( home, ".ssh" ); + LOG.debug( "Looking for SSH keys in " + sshHome ); + File key = new File( sshHome, "id_dsa" ); + if ( key.exists() ) + { + LOG.debug( "found " + key ); + return key.getAbsolutePath(); + } + key = new File( sshHome, "id_rsa" ); + if ( key.exists() ) + { + LOG.debug( "found " + key ); + return key.getAbsolutePath(); + } + key = new File( sshHome, "identity" ); + if ( key.exists() ) + { + LOG.debug( "found " + key ); + return key.getAbsolutePath(); + } + return null; + } + + private void doDeploy( File file, Project project, NamedArtifactTypeHandler handler, String version, String type ) + throws MavenException + { + List srcFiles = new ArrayList( 3 ); + List destFiles = new ArrayList( 3 ); + + srcFiles.add( file ); + destFiles.add( handler.constructRepositoryFullPath( type, project, version ) ); + + if ( version.indexOf( MavenConstants.SNAPSHOT_SIGNIFIER ) >= 0 ) + { + String signature = getSnapshotSignature(); + String v = StringUtils.replace( version, MavenConstants.SNAPSHOT_SIGNIFIER, signature ); + + File snapshotVersionFile = createSnapshotVersionFile( file, v, project, type, handler ); + + String snapshotVersionsFilename = handler.constructRepositoryDirectoryPath( type, project ) + + handler.getArtifactId() + "-snapshot-version"; + + srcFiles.add( snapshotVersionFile ); + destFiles.add( snapshotVersionsFilename ); + + String deployTimestamp = + (String) project.getContext().getVariable( "maven.artifact.deploy.timestamps" ); + if ( deployTimestamp.equals("true") ) + { + srcFiles.add( file ); + destFiles.add( handler.constructRepositoryFullPath( type, project, v ) ); + } + } + + // trick add special values to context for default repository; + + String repoStr = (String) project.getContext().getVariable( "maven.repo.list" ); + + if ( repoStr == null || repoStr.trim().length() == 0 ) + { + String central = project.getDistributionSite(); + String centralDirectory = project.getDistributionDirectory(); + if ( central == null || central.trim().length() == 0 ) + { + central = (String) project.getContext().getVariable( "maven.repo.central" ); + centralDirectory = (String) project.getContext().getVariable( "maven.repo.central.directory" ); + } + if ( central != null && central.trim().length() > 0 ) + { + repoStr = "default"; + project.getContext().setVariable( "maven.repo.default", "scp://" + central ); + if ( project.getContext().getVariable( "maven.repo.default.privatekey" ) == null ) + { + project.getContext().setVariable( "maven.repo.default.privatekey", findSshIdentity() ); + } + if ( project.getContext().getVariable( "maven.repo.default.passphrase" ) == null ) + { + LOG.warn( "WARNING: assuming empty passphrase. Specify maven.repo.default.passphrase if needed" ); + project.getContext().setVariable( "maven.repo.default.passphrase", "" ); + } + project.getContext().setVariable( "maven.repo.default.directory", centralDirectory ); + project.getContext().setVariable( "maven.repo.default.username", + project.getContext().getVariable( "maven.username" ) ); + project.getContext().setVariable( "maven.repo.default.group", + project.getContext().getVariable( "maven.remote.group" ) ); + } + } + + String[] repos = StringUtils.split( repoStr, "," ); + + LOG.info( "Will deploy to " + repos.length + " repository(ies): " + repoStr ); + boolean success = false; + for ( int i = 0; i < repos.length; i++ ) + { + + String repo = repos[i].trim(); + LOG.info( "Deploying to repository: " + repo ); + Repository repository = RepositoryBuilder.getRepository( project, repo ); + AuthenticationInfo authenticationInfo = RepositoryBuilder.getAuthenticationInfo( project, repo ); + try + { + deployFiles( repository, srcFiles, destFiles, authenticationInfo, project ); + success = true; + } + catch ( Exception e ) + { + String msg = "Failed to deploy to: " + repository.getId() + " Reason: " + e; + LOG.warn( msg, e ); + // deploy to next repository + continue; + } + + } + if ( !success ) + { + throw new MavenException( "Unable to deploy to any repositories" ); + } + } + + private void deployFile( Repository repository, File src, String dest, Project project ) + throws ResourceDoesNotExistException, MalformedURLException, NoSuchAlgorithmException, TransferFailedException, + ConnectionException, AuthenticationException, AuthorizationException, MavenException + { + deployFiles( repository, Collections.singletonList( src ), Collections.singletonList( dest ), null, project ); + } + + private void deployFiles( Repository repository, List srcFiles, List destFiles, + AuthenticationInfo authenticationInfo, Project project ) + throws ConnectionException, AuthenticationException, ResourceDoesNotExistException, TransferFailedException, + AuthorizationException, MalformedURLException, NoSuchAlgorithmException, MavenException + { + + if ( srcFiles.size() != destFiles.size() ) + { + String msg = "Lengths of the lists should be the same"; + throw new IllegalArgumentException( msg ); + } + + Wagon wagon = getWagon( repository.getProtocol(), project, repository.getId() ); + + TransferListener uploadMonitor = new UploadMeter(); + + Map checksums = new HashMap( 2 ); + + ChecksumObserver observer = new ChecksumObserver( "MD5" ); + checksums.put( "md5", observer ); + wagon.addTransferListener( observer ); + observer = new ChecksumObserver( "SHA-1" ); + checksums.put( "sha1", observer ); + wagon.addTransferListener( observer ); + + try + { + wagon.connect( repository, authenticationInfo ); + Iterator srcIterator = srcFiles.iterator(); + Iterator destIterator = destFiles.iterator(); + while ( srcIterator.hasNext() ) + { + wagon.addTransferListener( uploadMonitor ); + + File srcFile = (File) srcIterator.next(); + String destFile = (String) destIterator.next(); + wagon.put( srcFile, destFile ); + + wagon.removeTransferListener( uploadMonitor ); + + Map sums = new HashMap( 2 ); + for ( Iterator i = checksums.keySet().iterator(); i.hasNext(); ) + { + // store first - a later put will modify them + String extension = (String) i.next(); + observer = (ChecksumObserver) checksums.get( extension ); + sums.put( extension, observer.getActualChecksum() ); + } + + for ( Iterator i = checksums.keySet().iterator(); i.hasNext(); ) + { + String extension = (String) i.next(); + + // TODO: shouldn't need a file intermediatary - improve wagon to take a stream + File temp = File.createTempFile( "maven-artifact", null ); + temp.deleteOnExit(); + FileUtils.fileWrite( temp.getAbsolutePath(), (String) sums.get( extension ) ); + + wagon.put( temp, destFile + "." + extension ); + } + } + } + catch ( IOException e ) + { + throw new MavenException( "Error creating temporary file to transfer checksums", e ); + } + finally + { + try + { + wagon.disconnect(); + } + catch ( Exception e ) + { + LOG.error( "Error cleaning up from the deployer", e ); + } + } + } + + private Wagon getWagon( String protocol, Project project, String id ) + throws MalformedURLException + { + if ( protocol.equals( "http" ) ) + { + return new HttpWagon(); + } + else if ( protocol.equals( "ftp" ) ) + { + FtpWagon ftpWagon = new FtpWagon(); + RepositoryBuilder.configureFtpWagon( project, id, ftpWagon ); + return ftpWagon; + } + else if ( protocol.equals( "sftp" ) ) + { + return new SftpWagon(); + } + else if ( protocol.equals( "file" ) ) + { + return new FileWagon(); + } + else if ( protocol.equals( "scp" ) ) + { + return new ScpWagon(); + } + else if ( protocol.equals( "scpexe" ) ) + { + ScpExternalWagon scpExternalWagon = new ScpExternalWagon(); + RepositoryBuilder.configureSshExternalWagon( project, id, scpExternalWagon ); + return scpExternalWagon; + } + else + { + throw new MalformedURLException( "Unknown Wagon protocol: " + protocol ); + } + } + + private String getSnapshotSignature() + { + if ( snapshotSignature == null ) + { + DateFormat fmt = new SimpleDateFormat( SNAPSHOT_FORMAT ); + fmt.setTimeZone( TimeZone.getTimeZone( "GMT" ) ); + snapshotSignature = fmt.format( new Date() ); + } + return snapshotSignature; + } + + private File getFileForArtifact( String artifact ) + throws MavenException + { + File file = new File( artifact ); + if ( !file.exists() ) + { + String msg = "Artifact file: '" + artifact + "' must exist"; + throw new MavenException( msg ); + } + if ( !file.canRead() ) + { + String msg = "Artifact file: '" + artifact + "' must be readable"; + throw new MavenException( msg ); + } + if ( file.isDirectory() ) + { + String msg = "Artifact file: '" + artifact + "' must not be a directory"; + throw new MavenException( msg ); + } + return file.getAbsoluteFile(); + } + + // Create a file which contains timestamp of the latest snapshot + private File createSnapshotVersionFile( File artifact, String snapshotVersion, + Project project, String type, NamedArtifactTypeHandler handler ) + throws MavenException + { + File file = null; + String filename = handler.getArtifactId() + "-" + type + "-snapshot-version"; + try + { + file = new File( artifact.getParent(), filename ); + FileUtils.fileWrite( file.getAbsolutePath(), snapshotVersion ); + file.deleteOnExit(); + } + catch ( Exception e ) + { + throw new MavenException( "Cannot create snapshot-version file:" + file ); + } + return file; + } + +} diff --git a/artifact/src/main/org/apache/maven/artifact/deployer/NamedArtifactTypeHandler.java b/artifact/src/main/org/apache/maven/artifact/deployer/NamedArtifactTypeHandler.java new file mode 100644 index 00000000..8d137438 --- /dev/null +++ b/artifact/src/main/org/apache/maven/artifact/deployer/NamedArtifactTypeHandler.java @@ -0,0 +1,90 @@ +package org.apache.maven.artifact.deployer; + +/* ==================================================================== + * Copyright 2001-2004 The Apache Software Foundation. + * + * 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. + * ==================================================================== + */ + +import org.apache.maven.project.Project; +import org.apache.maven.repository.DefaultArtifactTypeHandler; + +/** + * This handler allows the default artifactId to be overridden. + * + * @author Neil Crow + */ +public class NamedArtifactTypeHandler extends DefaultArtifactTypeHandler +{ + /** The artifactId. */ + private String artifactId = null; + + /** + * @return String + */ + public String getArtifactId() + { + return artifactId; + } + + /** + * @param newId - The artifactId which overides the pom default. + */ + public void setArtifactId( String newId ) + { + this.artifactId = newId; + } + + + /** + * Map an artifact to a repository path. + * + * @param project the project for the artifact + * @param type The type of the artifact + * @param version The version of the artifact (may be a snapshot) + * @return the path + */ + public String constructRepositoryFullPath( String type, + Project project, String version ) + { + if ( artifactId == null ) + { + artifactId = project.getArtifactId(); + } + StringBuffer path = + new StringBuffer( constructRepositoryDirectoryPath( type, project ) ); + path.append( artifactId ); + path.append( "-" ); + path.append( version ); + path.append( extensionForType( type) ); + return path.toString(); + } + + /** plugin, ejb and uberjar should provide their own implementation. + * @param type type + * @return extension + */ + private String extensionForType(String type) + { + if (type.equals("uberjar") || type.equals("ejb") || type.equals("plugin")) + { + return ".jar"; + } + else + { + return "." + type; + } + } + +} diff --git a/artifact/xdocs/changes.xml b/artifact/xdocs/changes.xml index d11a1645..f96f7c15 100644 --- a/artifact/xdocs/changes.xml +++ b/artifact/xdocs/changes.xml @@ -25,6 +25,7 @@ + New attribute artifactIdOverride that allows to specify custom names for deployed/installed artifacts. When deploying a snapshot, jar and pom have different timestamped version. Make deploying a timestamped SNAPSHOT artifact configurable. Update dependencies to match ones in maven 1.1 core and to unify them between plugins. The following dependencies are updated : diff --git a/artifact/xdocs/tags.xml b/artifact/xdocs/tags.xml index 6acbbd53..d0498cbc 100644 --- a/artifact/xdocs/tags.xml +++ b/artifact/xdocs/tags.xml @@ -80,6 +80,14 @@ The path to the artifact file No + + artifactIdOverride + + Name to be used for the artifact + (default is ${pom.id} ) + + Yes + type The type of the artifact @@ -190,6 +198,14 @@ The path to the artifact file No + + artifactIdOverride + + Name to be used for the artifact + (default is ${pom.id} ) + + Yes + type The type of the artifact