/** * Copyright (C) 2015 Orange * 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 com.francetelecom.clara.cloud.mvn.consumer.maven; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import javax.activation.DataHandler; import com.francetelecom.clara.cloud.mvn.consumer.aether.ProxyManager; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.compressors.CompressorOutputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.apache.commons.lang3.Validate; import org.eclipse.aether.util.repository.AuthenticationBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.deployment.DeployRequest; import org.eclipse.aether.deployment.DeployResult; import org.eclipse.aether.deployment.DeploymentException; import org.eclipse.aether.repository.Authentication; import org.eclipse.aether.repository.Proxy; import org.eclipse.aether.repository.ProxySelector; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.repository.RepositoryPolicy; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.util.artifact.SubArtifact; import org.eclipse.aether.util.repository.DefaultProxySelector; import com.francetelecom.clara.cloud.commons.MavenReference; import com.francetelecom.clara.cloud.commons.TechnicalException; import com.francetelecom.clara.cloud.mvn.consumer.FileRef; import com.francetelecom.clara.cloud.mvn.consumer.MvnConsumerConfigurer; import com.francetelecom.clara.cloud.mvn.consumer.aether.AetherConfigurer; import org.springframework.beans.factory.annotation.Autowired; public class MavenDeployer { /** * Implementation class for Maven Repository Data Access Object * * - manages remote repo acces (read and write) - manages local working * space, and maven local embedded projects builds. * * links : http://nexus.sonatype.org/nexus-test-harness.html * http://nexus.sonatype.org/oss-repository-hosting.html * * http://maven.apache.org/shared/index.html * * * https://docs.sonatype.org/display/AETHER/Home * * http://maven.apache.org/ref/3.0-beta-3/ * http://maven.apache.org/docs/3.0-beta-3/release-notes.html * * */ private static final Logger logger = LoggerFactory.getLogger(MavenDeployer.class); protected SettingsGenerator settingsGenerator; protected PomGenerator pomGenerator; protected AetherConfigurer aetherConfigurer; protected MvnConsumerConfigurer mvnConsumerConfigurer; protected File settingsFile; public MavenDeployer(MvnConsumerConfigurer mvnConsumerConfigurer) { this.mvnConsumerConfigurer = mvnConsumerConfigurer; } /** * Initial configuration * * @throws IOException */ public void init() throws Exception { logger.debug("creating settings.xml"); String settingsFilename = this.mvnConsumerConfigurer.getLocalWorkDir().getAbsolutePath() + "/.m2/settings.xml"; settingsFile = settingsGenerator.generateAndWrite(settingsFilename); logger.debug("created settings.xml as " + this.settingsFile.getAbsolutePath()); logger.debug("creating settings.xml end"); // added to check duration of // this method (take 1 // minute !?) } private MavenReference convertToMavenReference(Artifact artifact) { MavenReference result = new MavenReference(artifact.getGroupId(), artifact.getArtifactId(), artifact.getBaseVersion(), artifact.getExtension(), artifact.getClassifier()); return result.duplicateWithNull(); } private Artifact convertToArtifact(MavenReference mavenReference) { MavenReference mavenReferenceEmpty = mavenReference.duplicateWithEmpty(); Artifact artifact = new DefaultArtifact(mavenReferenceEmpty.getGroupId(), mavenReferenceEmpty.getArtifactId(), mavenReferenceEmpty.getClassifier(), mavenReferenceEmpty.getExtension(), mavenReferenceEmpty.getVersion()); return artifact; } /** * Deploy a Maven component reference * * @return */ public DeployResult deployFileset(MavenReference mavenReference, List<FileRef> fileset) { Validate.notNull(mavenReference, "Shoud not be null"); Validate.notNull(fileset, "Shoud not be null"); File projectDirectory = getProjectDirectory(mavenReference); return deploy(mavenReference, fileset, projectDirectory); } public DeployResult deployBin(MavenReference mavenReference, DataHandler binaryContent) { Validate.notNull(mavenReference, "Shoud not be null"); Validate.notNull(binaryContent, "Shoud not be null"); File projectDirectory = getProjectDirectory(mavenReference); File artifact = new File(projectDirectory, mavenReference.getArtifactName()); try { binaryContent.writeTo(new FileOutputStream(artifact)); } catch (IOException e) { logger.error("failure storing artifact stream " + mavenReference, e); throw new TechnicalException(e); } return install(mavenReference, artifact, projectDirectory); } private File getProjectDirectory(MavenReference mavenReference) { return new File(this.mvnConsumerConfigurer.getLocalWorkDir(), mavenReference.getGroupId() + "/" + mavenReference.getArtifactId()); } private DeployResult deploy(MavenReference mavenReference, List<FileRef> fileset, File projectDirectory) { try { File archive = createArchive(projectDirectory, mavenReference, fileset); return install(mavenReference, archive, projectDirectory); } catch (IOException e) { logger.error("failure creating archive from fileset " + mavenReference, e); throw new TechnicalException(e); } } private DeployResult install(MavenReference mavenReference, File archive, File projectDirectory) { try { Artifact artifactToDeploy = convertToArtifact(mavenReference).setFile(archive); Artifact pom = new SubArtifact(artifactToDeploy, null, "pom").setFile(pomGenerator.generatePom(projectDirectory, mavenReference)); RemoteRepository selectedRepository = selectRepositoryToDeploy(artifactToDeploy); DeployResult deploymentResult = deployUsingAether(artifactToDeploy, pom, selectedRepository); logger.debug(mavenReference + " has been deployed on " + selectedRepository + " - DeploymentResult: " + deploymentResult.toString()); return deploymentResult; // updateUrl(mavenReference); } catch (IOException e) { logger.error("failure creating mvn work dir for project " + mavenReference, e); throw new TechnicalException(e); } catch (DeploymentException e) { logger.error("deployment failed for " + mavenReference, e); throw new TechnicalException(e); } } private RemoteRepository selectRepositoryToDeploy(Artifact artifactToDeploy) { if (artifactToDeploy == null) { throw new IllegalArgumentException("artifactToDeploy should not be null"); } RemoteRepository.Builder snapRepoBuilder = new RemoteRepository.Builder("paas.push.snapshot.repo", "default", mvnConsumerConfigurer.getPushSnapshotRepositoryUrl()); RepositoryPolicy disabledRepo = null; snapRepoBuilder.setReleasePolicy(disabledRepo); Authentication snapshotRepositoryAuthen = new AuthenticationBuilder().addUsername(mvnConsumerConfigurer.getPushSnapshotRepositoryUser()).addPassword( mvnConsumerConfigurer.getPushSnapshotRepositoryPassword()).build(); snapRepoBuilder.setAuthentication(snapshotRepositoryAuthen); RemoteRepository.Builder releaseRepoBuilder = new RemoteRepository.Builder("paas.push.release.repo", "default", mvnConsumerConfigurer.getPushReleaseRepositoryUrl()); releaseRepoBuilder.setReleasePolicy(disabledRepo); Authentication releaseRepositoryAuthen = new AuthenticationBuilder().addUsername(mvnConsumerConfigurer.getPushReleaseRepositoryUser()).addPassword( mvnConsumerConfigurer.getPushReleaseRepositoryPassword()).build(); releaseRepoBuilder.setAuthentication(releaseRepositoryAuthen); RemoteRepository result; if (artifactToDeploy.isSnapshot()) { result = snapRepoBuilder.build(); } else { result = releaseRepoBuilder.build(); } return result; } private DeployResult deployUsingAether(Artifact artifact, Artifact pom, RemoteRepository deploymentRepository) throws DeploymentException { RepositorySystem system = aetherConfigurer.newRepositorySystem(); RepositorySystemSession session = aetherConfigurer.newSession(system, mvnConsumerConfigurer.getLocalM2Repo()); DeployRequest deployRequest = new DeployRequest(); deployRequest.addArtifact(artifact).addArtifact(pom); deployRequest.setRepository(deploymentRepository); DeployResult result = system.deploy(session, deployRequest); return result; } private File createArchive(File projectDirectory, MavenReference mavenReference, List<FileRef> fileset) throws IOException { File archive = new File(projectDirectory, mavenReference.getArtifactName()); if ("tar.gz".equals(mavenReference.getExtension())) { populateTgzArchive(archive, fileset); } else { populateJarArchive(archive, fileset); } return archive; } private File populateTgzArchive(File archive, List<FileRef> fileset) throws IOException { archive.getParentFile().mkdirs(); CompressorOutputStream zip = new GzipCompressorOutputStream(new FileOutputStream(archive)); TarArchiveOutputStream tar = new TarArchiveOutputStream(zip); for (FileRef fileRef : fileset) { TarArchiveEntry entry = new TarArchiveEntry(new File(fileRef.getRelativeLocation())); byte[] bytes = fileRef.getContent().getBytes(); entry.setSize(bytes.length); tar.putArchiveEntry(entry); tar.write(bytes); tar.closeArchiveEntry(); } tar.close(); return archive; } private File populateJarArchive(File archive, List<FileRef> fileset) throws IOException { archive.getParentFile().mkdirs(); JarOutputStream target = new JarOutputStream(new FileOutputStream(archive)); for (FileRef fileRef : fileset) { JarEntry jarAdd = new JarEntry(fileRef.getRelativeLocation()); target.putNextEntry(jarAdd); target.write(fileRef.getContent().getBytes()); } target.close(); return archive; } private URI convertDosPathToUri(String dosPath) throws URISyntaxException { return new URI(dosPath.replaceAll("\\\\", "/")); } public void setMvnConsumerConfigurer(MvnConsumerConfigurer mvnConsumerConfigurer) { this.mvnConsumerConfigurer = mvnConsumerConfigurer; } public void setSettingsGenerator(SettingsGenerator settingsGenerator) { this.settingsGenerator = settingsGenerator; } public void setPomGenerator(PomGenerator pomGenerator) { this.pomGenerator = pomGenerator; } public void setAetherConfigurer(AetherConfigurer aetherConfigurer) { this.aetherConfigurer = aetherConfigurer; } /** * Verify that artifact referenced by a given {@link MavenReference} is * available * * @param mavenRef * @return true if available, false if not */ boolean isArtifactAvailable(MavenReference mavenRef) { if (mavenRef.getAccessUrl() == null) return false; return isValidUrl(mavenRef.getAccessUrl()); } /** * Verify that a given url can be reached * * @param url * @return true if valid, false if not */ boolean isValidUrl(URL url) { logger.debug("Testing url: " + url); HttpURLConnection huc = null; int responseCode = 0; try { huc = openHttpUrlConnection(url); huc.setRequestMethod("HEAD"); // set a long enough timeout to be protected against slow // network/server response time huc.setReadTimeout(30000); huc.connect(); responseCode = huc.getResponseCode(); } catch (IOException e) { logger.error("unable to test url " + url, e); } finally { if (huc != null) huc.disconnect(); } boolean isValid = (responseCode == 200); if (!isValid) logger.warn("Http HEAD on Url " + url + " returns " + responseCode); return isValid; } /** * Wrap {@link URL#openConnection()} so that this method can be mocked in * tests */ HttpURLConnection openHttpUrlConnection(URL url) throws IOException { return (HttpURLConnection) url.openConnection(); } }