com.redhat.red.offliner.alist.PomArtifactListReader.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.red.offliner.alist.PomArtifactListReader.java

Source

/**
 * Copyright (C) 2015 Red Hat, Inc. (jcasey@redhat.com)
 *
 * 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.redhat.red.offliner.alist;

import com.redhat.red.offliner.OfflinerException;
import com.redhat.red.offliner.model.ArtifactList;
import com.redhat.red.offliner.util.UrlUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.MavenArtifactRepository;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.Repository;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.repository.DefaultMirrorSelector;
import org.apache.maven.repository.MirrorSelector;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.io.xpp3.SettingsXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Artifact list paths reader that consumes pom files. It reads all dependencies and constructs paths from them.
 *
 * Supported files are those ending with .pom or named pom.xml.
 */
public class PomArtifactListReader implements ArtifactListReader {

    public static final String DEFAULT_TYPE_MAPPING_RES = "type-mapping.properties";

    private File settingsXml;

    private CredentialsProvider creds;

    private Map<String, TypeMapping> typeMapping;

    public PomArtifactListReader(final File settingsXml, final String typeMappingFile,
            final CredentialsProvider creds) {
        this.settingsXml = settingsXml;
        this.creds = creds;

        Properties props = new Properties();
        if (StringUtils.isEmpty(typeMappingFile)) {
            try (InputStream mappingStream = getClass().getClassLoader()
                    .getResourceAsStream(DEFAULT_TYPE_MAPPING_RES)) {
                props.load(mappingStream);
            } catch (IOException ex) {
                throw new IllegalStateException("Failed to load Maven type mapping from default properties", ex);
            }
        } else {
            try (InputStream mappingStream = new FileInputStream(typeMappingFile)) {
                props.load(mappingStream);
            } catch (IOException ex) {
                throw new IllegalStateException(
                        "Failed to load Maven type mapping provided properties file " + typeMappingFile, ex);
            }
        }
        this.typeMapping = new HashMap<>(props.size());

        Pattern p = Pattern.compile("([^:]+)(?::(.+))?");
        for (Map.Entry<Object, Object> entry : props.entrySet()) {
            String type = (String) entry.getKey();

            String value = (String) entry.getValue();
            Matcher m = p.matcher(value);
            if (!m.matches()) {
                throw new IllegalArgumentException(
                        "The type mapping string \"" + typeMappingFile + "\" has a wrong format.");
            }
            String extension = m.group(1);
            if (m.groupCount() == 2) {
                String classifier = m.group(2);
                this.typeMapping.put(type, new TypeMapping(extension, classifier));
            } else {
                this.typeMapping.put(type, new TypeMapping(extension));
            }
        }
    }

    @Override
    public ArtifactList readPaths(final File file) throws IOException, OfflinerException {
        Model model;
        try (Reader reader = new FileReader(file)) {
            model = new MavenXpp3Reader().read(reader);
        } catch (XmlPullParserException ex) {
            throw new OfflinerException("Failed to parse the source pom: %s. Invalid XML: %s", ex, file,
                    ex.getMessage());
        }

        Set<String> paths = new LinkedHashSet<>();
        for (Dependency dep : model.getDependencies()) {
            String dirPath = String.format("%s/%s/%s", dep.getGroupId().replace('.', '/'), dep.getArtifactId(),
                    dep.getVersion());
            String extension = dep.getType();
            String classifier = dep.getClassifier();
            if (typeMapping.containsKey(extension)) {
                TypeMapping tm = typeMapping.get(extension);
                extension = tm.getExtension();
                classifier = tm.getClassifier();
            }

            if (!"pom".equals(extension)) {
                paths.add(String.format("%s/%s-%s.pom", dirPath, dep.getArtifactId(), dep.getVersion()));
            }

            String path;
            if (StringUtils.isEmpty(classifier)) {
                path = String.format("%s/%s-%s.%s", dirPath, dep.getArtifactId(), dep.getVersion(), extension);
            } else {
                path = String.format("%s/%s-%s-%s.%s", dirPath, dep.getArtifactId(), dep.getVersion(), classifier,
                        extension);
            }

            paths.add(path);
        }

        if (model.getBuild() != null) {
            List<Plugin> plugins = model.getBuild().getPlugins();
            for (Plugin dep : plugins) {
                String prefix = String.format("%s/%s/%s/%s-%s.", dep.getGroupId().replace('.', '/'),
                        dep.getArtifactId(), dep.getVersion(), dep.getArtifactId(), dep.getVersion());
                paths.add(String.format("%spom", prefix));
                paths.add(String.format("%sjar", prefix));
            }
        }

        List<String> repoUrls = new ArrayList<>();

        List<Repository> repositories = model.getRepositories();
        if (repositories != null) {
            processSettingsXml(repositories);

            for (Repository repository : repositories) {
                repoUrls.add(repository.getUrl());
            }

        }

        ArtifactList result = new ArtifactList(new ArrayList<String>(paths), repoUrls, null);
        return result;
    }

    /**
     * Processes informations contained in provided settings.xml. Uses repository URLs from speicifed mirrors and uses
     * the authentication info cantained in servers section.
     *
     * @param repositories the repository list
     * @throws IOException in case the settings.xml file cannot be found or read
     */
    private void processSettingsXml(List<Repository> repositories) throws IOException, OfflinerException {
        if (settingsXml != null) {
            Settings settings;
            try (Reader reader = new FileReader(settingsXml)) {
                settings = new SettingsXpp3Reader().read(reader);
            } catch (XmlPullParserException ex) {
                throw new OfflinerException("Failed to parse: %s. Invalid XML: %s", ex, settingsXml,
                        ex.getMessage());
            }

            processMirrors(settings, repositories);
            processCredentials(settings, repositories);
        }
    }

    /**
     * Applies mirrors from the settings.xml on the {@code repositories}. Read mirrors replace the original repositories
     * in provided repository list.
     *
     * @param settings settings.xml contents
     * @param repositories list of repositories read from pom
     */
    private void processMirrors(final Settings settings, final List<Repository> repositories) {
        List<Mirror> mirrors = settings.getMirrors();
        MirrorSelector mirrorSelector = new DefaultMirrorSelector();
        DefaultRepositoryLayout layout = new DefaultRepositoryLayout();
        for (Repository repository : new ArrayList<>(repositories)) {
            ArtifactRepository artRepository = new MavenArtifactRepository();
            artRepository.setId(repository.getId());
            // TODO read the layout from the original repository
            artRepository.setLayout(layout);
            Mirror mirror = mirrorSelector.getMirror(artRepository, mirrors);
            if (mirror != null) {
                Repository mirrorRepository = new Repository();
                mirrorRepository.setId(mirror.getId());
                mirrorRepository.setLayout(mirror.getLayout());
                mirrorRepository.setReleases(repository.getReleases());
                mirrorRepository.setSnapshots(repository.getSnapshots());
                mirrorRepository.setUrl(mirror.getUrl());
                Collections.replaceAll(repositories, repository, mirrorRepository);
            }
        }
    }

    /**
     * Read server credentials from the settings.xml. Reads only credentials for servers contained in the
     * {@code repositories}.
     *
     * @param settings settings.xml contents
     * @param repositories the repository list
     */
    private void processCredentials(final Settings settings, final List<Repository> repositories)
            throws OfflinerException {
        Map<String, Repository> repoMap = new HashMap<>();
        for (Repository repository : repositories) {
            repoMap.put(repository.getId(), repository);
        }

        List<Server> servers = settings.getServers();
        for (Server server : servers) {
            if (repoMap.containsKey(server.getId())) {
                String username = server.getUsername();
                Credentials credentials = null;
                if (username != null) {
                    credentials = new UsernamePasswordCredentials(username, server.getPassword());
                }

                // TODO add certificate-based authentication option

                if (credentials != null) {
                    Repository repository = repoMap.get(server.getId());
                    URL url;
                    try {
                        url = new URL(repository.getUrl());
                    } catch (MalformedURLException ex) {
                        throw new OfflinerException("Repository URL \"%s\" could not be parsed.", ex,
                                repository.getUrl());
                    }
                    AuthScope as = new AuthScope(url.getHost(), UrlUtils.getPort(url));
                    creds.setCredentials(as, credentials);
                }
            }
        }
    }

    @Override
    public boolean supports(final File file) {
        String filename = file.getName();
        return "pom.xml".equals(filename) || filename.endsWith(".pom");
    }

    private static class TypeMapping {

        private final String extension;

        private final String classifier;

        public TypeMapping(final String extension, final String classifer) {
            this.extension = extension;
            this.classifier = classifer;
        }

        public TypeMapping(final String extension) {
            this(extension, null);
        }

        public String getExtension() {
            return extension;
        }

        public String getClassifier() {
            return classifier;
        }

    }

}