/** * 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; import com.francetelecom.clara.cloud.commons.MavenReference; import com.francetelecom.clara.cloud.commons.TechnicalException; import com.francetelecom.clara.cloud.mvn.consumer.aether.AetherConfigurer; import com.francetelecom.clara.cloud.mvn.consumer.aether.ProxyManager; import com.francetelecom.clara.cloud.mvn.consumer.maven.MavenDeployer; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.Artifact; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.internal.impl.EnhancedLocalRepositoryManagerFactory; import org.eclipse.aether.internal.impl.Maven2RepositoryLayoutFactory; import org.eclipse.aether.repository.*; import org.eclipse.aether.repository.Proxy; import org.eclipse.aether.repository.ProxySelector; import org.eclipse.aether.resolution.ArtifactRequest; import org.eclipse.aether.resolution.ArtifactResolutionException; import org.eclipse.aether.resolution.ArtifactResult; import org.eclipse.aether.transfer.NoRepositoryLayoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.io.File; import java.io.IOException; import java.net.*; import java.util.ArrayList; import java.util.List; //import org.eclipse.aether.util.layout.MavenDefaultLayout; //import org.eclipse.aether.util.layout.RepositoryLayout; /* import org.apache.maven.shared.runtime.DefaultMavenRuntime; import org.apache.maven.shared.runtime.MavenProjectProperties; import org.apache.maven.shared.runtime.MavenRuntime; import org.apache.maven.shared.runtime.MavenRuntimeException; */ /** * Implementation class for Maven Repository Data Access Object * <p> * - manages remote repo acces (read and write) - manages local working space, * and maven local embedded projects builds. * <p> * <p> * <p> * links : http://nexus.sonatype.org/nexus-test-harness.html * http://nexus.sonatype.org/oss-repository-hosting.html * <p> * http://maven.apache.org/shared/index.html * <p> * <p> * https://docs.sonatype.org/display/AETHER/Home * <p> * http://maven.apache.org/ref/3.0.5/ * http://maven.apache.org/docs/3.0.5/release-notes.html */ public class MvnRepoDaoImpl implements MvnRepoDao { private static Logger logger = LoggerFactory.getLogger(MvnRepoDaoImpl.class); protected AetherConfigurer aetherConfigurer; protected MvnConsumerConfigurer mvnConsumerConfigurer; protected MavenDeployer mavenDeployer; private ProxySelector mvnProxySelector; private final RepositoryPolicy disabledRepoPolicy = new RepositoryPolicy(false, null, null); @Autowired private ProxyManager proxyManager; public MvnRepoDaoImpl() { } /** * Initial configuration * * @throws IOException */ public void init() throws Exception { XTrustProvider.install(); logger.debug("creating settings.xml"); mvnProxySelector = proxyManager.selectProxies(); logger.debug("creating settings.xml end"); } private List<RemoteRepository> initRemoteRepositories() { List<RemoteRepository> remoteRepositories = new ArrayList<RemoteRepository>(); List<RemoteRepository.Builder> builders = new ArrayList<RemoteRepository.Builder>(); List<RemoteRepository> pullRemoteRepo = this.mvnConsumerConfigurer.getPullRemoteRepo(); for (RemoteRepository aPullRemoteRepository : pullRemoteRepo) { builders.add(new RemoteRepository.Builder(aPullRemoteRepository)); } // Add this to support proxy configuration for (RemoteRepository.Builder builder : builders) { logger.debug("Processing builder {}",builder); if (mvnProxySelector != null) { logger.debug("Proxy selector {} found for builder {}",mvnProxySelector,builder); RemoteRepository remoteRepo = builder.build(); Proxy proxy = mvnProxySelector.getProxy(remoteRepo); if (proxy != null) { logger.debug("[repo:{}] => setProxy({})", remoteRepo.getHost(), proxy.toString()); builder.setProxy(proxy); } } remoteRepositories.add(builder.build()); } return remoteRepositories; } 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; } @Override public MavenReference resolveUrl(MavenReference mavenReference) { MavenReference mavenRef = new MavenReference(mavenReference); updateUrl(mavenRef); if (!isArtifactAvailable(mavenRef)) { throw new MavenReferenceResolutionException(mavenRef, "artifact is not available at " + mavenRef.getAccessUrl()); } return mavenRef; } protected void updateUrl(MavenReference mavenRef) { logger.debug("Resolving maven reference using all repositories for " + mavenRef); Artifact artifactToFind = convertToArtifact(mavenRef); try { RepositorySystem aetherRepoSystem = aetherConfigurer.newRepositorySystem(); RepositorySystemSession aetherRepoSession = aetherConfigurer.newSession(aetherRepoSystem, this.mvnConsumerConfigurer.getLocalM2Repo()); ArtifactRequest resolveArtifactRequest = new ArtifactRequest(artifactToFind, initRemoteRepositories(), null); ArtifactResult artifactResultFound = aetherRepoSystem.resolveArtifact(aetherRepoSession, resolveArtifactRequest); ArtifactRepository artifactDeploymentRepository = artifactResultFound.getRepository(); URI artifactResolvedUri; if (artifactDeploymentRepository instanceof RemoteRepository) { RemoteRepository remoteRepository = (RemoteRepository )artifactDeploymentRepository; artifactResolvedUri = getArtifactUriFrom(remoteRepository, aetherRepoSession, artifactResultFound); } else if (artifactDeploymentRepository instanceof LocalRepository) { LocalRepository localRepository = (LocalRepository)artifactDeploymentRepository; artifactResolvedUri = getArtifactUriFrom(localRepository, aetherRepoSession, artifactResultFound); } else { throw new TechnicalException("Unsupported Maven Repository type: " + artifactDeploymentRepository.getClass().getSimpleName()); } mavenRef.setAccessUrl(artifactResolvedUri.toURL()); } catch (MalformedURLException e) { logger.error("URL for " + mavenRef.getArtifactId() + " is malformed", e); throw new TechnicalException("Error in Maven repository URL", e); } catch (URISyntaxException e) { logger.error("URL for " + mavenRef.getArtifactId() + " is malformed", e); throw new TechnicalException("Error in Maven repository URL", e); } catch (ArtifactResolutionException e) { // log level is warning (not error) as it might be a 'normal' that // maven references are incorrect logger.warn(e.getMessage()); String message = e.getCause() == null ? "no cause" : e.getCause().getMessage(); logger.warn("Error cause : " +message); throw new MavenReferenceResolutionException(mavenRef, e); } logger.debug("Resolved maven reference: " + mavenRef); } private URI getArtifactUriFrom(LocalRepository localRepository, RepositorySystemSession aetherRepoSession, ArtifactResult artifactResultFound) throws URISyntaxException { try { final LocalRepositoryManager localRepositoryManager = new EnhancedLocalRepositoryManagerFactory().newInstance(aetherRepoSession, localRepository); return new URI(localRepositoryManager.getPathForLocalArtifact(artifactResultFound.getArtifact())); } catch (NoLocalRepositoryManagerException e) { logger.error("NoLocalRepositoryManagerException", e); throw new TechnicalException("Invalid Maven repo layout (NoLocalRepositoryManagerException)", e); } } private URI getArtifactUriFrom(RemoteRepository remoteRepository, RepositorySystemSession aetherRepoSession, ArtifactResult artifactResultFound) throws URISyntaxException { URI repositoryUri = new URI(remoteRepository.getUrl()); try { URI artifactUri = new Maven2RepositoryLayoutFactory().newInstance(aetherRepoSession, remoteRepository).getLocation(artifactResultFound.getArtifact(), false); return repositoryUri.resolve(artifactUri); } catch (NoRepositoryLayoutException e) { logger.error("NoRepositoryLayoutException", e); throw new TechnicalException("Invalid Maven repo layout (NoRepositoryLayoutException)", e); } } public boolean isUsingProxyForPullRepo() { boolean result = false; RemoteRepository repository = mvnConsumerConfigurer.getPullPrimaryRepository(); if (mvnProxySelector != null && mvnProxySelector.getProxy(repository) != null) { result = true; } return result; } /** * resolve an artifact by searching it into repositories using Aether API * * @param artifactToFind * @return * @throws ArtifactResolutionException */ protected ArtifactResult resolveArtifact(Artifact artifactToFind) throws ArtifactResolutionException { RepositorySystem aetherRepoSystem = aetherConfigurer.newRepositorySystem(); RepositorySystemSession aetherRepoSession = aetherConfigurer.newSession(aetherRepoSystem, this.mvnConsumerConfigurer.getLocalM2Repo()); ArtifactRequest resolveArtifactRequest = new ArtifactRequest(artifactToFind, initRemoteRepositories(), null); ArtifactResult artifactResultFound = aetherRepoSystem.resolveArtifact(aetherRepoSession, resolveArtifactRequest); return artifactResultFound; } /** * try to resolve a maven reference and return corresponding file in local * repository mavenReference url is not updated */ @Override public File getFileFromLocalRepository(MavenReference mavenRef) { Artifact artifactToFind = convertToArtifact(mavenRef); ArtifactResult result; try { result = resolveArtifact(artifactToFind); } catch (ArtifactResolutionException e) { throw new MavenReferenceResolutionException(mavenRef, e); } Artifact artifact = result.getArtifact(); if (artifact == null) throw new MavenReferenceResolutionException(mavenRef, "artifact is null"); return artifact.getFile(); } private URI convertDosPathToUri(String dosPath) throws URISyntaxException { return new URI(dosPath.replaceAll("\\\\", "/")); } public void setMvnConsumerConfigurer(MvnConsumerConfigurer mvnConsumerConfigurer) { this.mvnConsumerConfigurer = mvnConsumerConfigurer; } public void setMavenDeployer(MavenDeployer mavenDeployer) { this.mavenDeployer = mavenDeployer; } 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(); } @Override public void deployFileset(MavenReference gav, ArrayList<FileRef> fileSet) { this.mavenDeployer.deployFileset(gav, fileSet); updateUrl(gav); } }