001package org.eclipse.aether.internal.impl;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.LinkedHashSet;
028import java.util.List;
029import java.util.Set;
030import java.util.stream.Collectors;
031
032import javax.inject.Inject;
033import javax.inject.Named;
034import javax.inject.Singleton;
035
036import org.eclipse.aether.RepositorySystemSession;
037import org.eclipse.aether.artifact.Artifact;
038import org.eclipse.aether.internal.impl.checksum.DefaultChecksumAlgorithmFactorySelector;
039import org.eclipse.aether.metadata.Metadata;
040import org.eclipse.aether.repository.RemoteRepository;
041import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
042import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
043import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
044import org.eclipse.aether.spi.connector.layout.RepositoryLayoutFactory;
045import org.eclipse.aether.transfer.NoRepositoryLayoutException;
046import org.eclipse.aether.util.ConfigUtils;
047
048import static java.util.Objects.requireNonNull;
049
050/**
051 * Provides a Maven-2 repository layout for repositories with content type {@code "default"}.
052 */
053@Singleton
054@Named( "maven2" )
055public final class Maven2RepositoryLayoutFactory
056        implements RepositoryLayoutFactory
057{
058
059    public static final String CONFIG_PROP_CHECKSUMS_ALGORITHMS = "aether.checksums.algorithms";
060
061    private static final String DEFAULT_CHECKSUMS_ALGORITHMS = "SHA-1,MD5";
062
063    public static final String CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS =
064            "aether.checksums.omitChecksumsForExtensions";
065
066    private static final String DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS = ".asc";
067
068    private float priority;
069
070    private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
071
072    public float getPriority()
073    {
074        return priority;
075    }
076
077    /**
078     * Service locator ctor.
079     */
080    @Deprecated
081    public Maven2RepositoryLayoutFactory()
082    {
083        this( new DefaultChecksumAlgorithmFactorySelector() );
084    }
085
086    @Inject
087    public Maven2RepositoryLayoutFactory( ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector )
088    {
089        this.checksumAlgorithmFactorySelector = requireNonNull( checksumAlgorithmFactorySelector );
090    }
091
092    /**
093     * Sets the priority of this component.
094     *
095     * @param priority The priority.
096     * @return This component for chaining, never {@code null}.
097     */
098    public Maven2RepositoryLayoutFactory setPriority( float priority )
099    {
100        this.priority = priority;
101        return this;
102    }
103
104    public RepositoryLayout newInstance( RepositorySystemSession session, RemoteRepository repository )
105            throws NoRepositoryLayoutException
106    {
107        requireNonNull( session, "session cannot be null" );
108        requireNonNull( repository, "repository cannot be null" );
109        if ( !"default".equals( repository.getContentType() ) )
110        {
111            throw new NoRepositoryLayoutException( repository );
112        }
113        // ensure order and uniqueness of (potentially user set) algorithm list
114        LinkedHashSet<String> checksumsAlgorithmNames = Arrays.stream( ConfigUtils.getString(
115                        session, DEFAULT_CHECKSUMS_ALGORITHMS, CONFIG_PROP_CHECKSUMS_ALGORITHMS )
116                .split( "," )
117        ).filter( s -> s != null && !s.trim().isEmpty() ).collect( Collectors.toCollection( LinkedHashSet::new ) );
118
119        // validation: this loop implicitly validates the list above: selector will throw on unknown algorithm
120        List<ChecksumAlgorithmFactory> checksumsAlgorithms = new ArrayList<>( checksumsAlgorithmNames.size() );
121        for ( String checksumsAlgorithmName : checksumsAlgorithmNames )
122        {
123            checksumsAlgorithms.add( checksumAlgorithmFactorySelector.select( checksumsAlgorithmName ) );
124        }
125
126        // ensure uniqueness of (potentially user set) extension list
127        Set<String> omitChecksumsForExtensions = Arrays.stream( ConfigUtils.getString(
128                        session, DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS, CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS )
129                .split( "," )
130        ).filter( s -> s != null && !s.trim().isEmpty() ).collect( Collectors.toSet() );
131
132        // validation: enforce that all strings in this set are having leading dot
133        if ( omitChecksumsForExtensions.stream().anyMatch( s -> !s.startsWith( "." ) ) )
134        {
135            throw new IllegalArgumentException(
136                    String.format(
137                            "The configuration %s contains illegal values: %s (all entries must start with '.' (dot))",
138                            CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS,
139                            omitChecksumsForExtensions
140                    )
141            );
142        }
143
144        return new Maven2RepositoryLayout(
145                new ArrayList<>( checksumAlgorithmFactorySelector.getChecksumAlgorithmFactories() ),
146                checksumsAlgorithms,
147                omitChecksumsForExtensions
148        );
149    }
150
151    private static class Maven2RepositoryLayout
152            implements RepositoryLayout
153    {
154        private final List<ChecksumAlgorithmFactory> allChecksumAlgorithms;
155
156        private final List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms;
157
158        private final Set<String> extensionsWithoutChecksums;
159
160        private Maven2RepositoryLayout( List<ChecksumAlgorithmFactory> allChecksumAlgorithms,
161                                        List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms,
162                                        Set<String> extensionsWithoutChecksums )
163        {
164            this.allChecksumAlgorithms = Collections.unmodifiableList( allChecksumAlgorithms );
165            this.configuredChecksumAlgorithms = Collections.unmodifiableList( configuredChecksumAlgorithms );
166            this.extensionsWithoutChecksums = requireNonNull( extensionsWithoutChecksums );
167        }
168
169        private URI toUri( String path )
170        {
171            try
172            {
173                return new URI( null, null, path, null );
174            }
175            catch ( URISyntaxException e )
176            {
177                throw new IllegalStateException( e );
178            }
179        }
180
181        @Override
182        public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories()
183        {
184            return configuredChecksumAlgorithms;
185        }
186
187        @Override
188        public boolean hasChecksums( Artifact artifact )
189        {
190            String artifactExtension = artifact.getExtension(); // ie. pom.asc
191            for ( String extensionWithoutChecksums : extensionsWithoutChecksums )
192            {
193                if ( artifactExtension.endsWith( extensionWithoutChecksums ) )
194                {
195                    return false;
196                }
197            }
198            return true;
199        }
200
201        @Override
202        public URI getLocation( Artifact artifact, boolean upload )
203        {
204            StringBuilder path = new StringBuilder( 128 );
205
206            path.append( artifact.getGroupId().replace( '.', '/' ) ).append( '/' );
207
208            path.append( artifact.getArtifactId() ).append( '/' );
209
210            path.append( artifact.getBaseVersion() ).append( '/' );
211
212            path.append( artifact.getArtifactId() ).append( '-' ).append( artifact.getVersion() );
213
214            if ( artifact.getClassifier().length() > 0 )
215            {
216                path.append( '-' ).append( artifact.getClassifier() );
217            }
218
219            if ( artifact.getExtension().length() > 0 )
220            {
221                path.append( '.' ).append( artifact.getExtension() );
222            }
223
224            return toUri( path.toString() );
225        }
226
227        @Override
228        public URI getLocation( Metadata metadata, boolean upload )
229        {
230            StringBuilder path = new StringBuilder( 128 );
231
232            if ( metadata.getGroupId().length() > 0 )
233            {
234                path.append( metadata.getGroupId().replace( '.', '/' ) ).append( '/' );
235
236                if ( metadata.getArtifactId().length() > 0 )
237                {
238                    path.append( metadata.getArtifactId() ).append( '/' );
239
240                    if ( metadata.getVersion().length() > 0 )
241                    {
242                        path.append( metadata.getVersion() ).append( '/' );
243                    }
244                }
245            }
246
247            path.append( metadata.getType() );
248
249            return toUri( path.toString() );
250        }
251
252        @Override
253        public List<ChecksumLocation> getChecksumLocations( Artifact artifact, boolean upload, URI location )
254        {
255            if ( !hasChecksums( artifact ) || isChecksum( artifact.getExtension() ) )
256            {
257                return Collections.emptyList();
258            }
259            return getChecksumLocations( location );
260        }
261
262        @Override
263        public List<ChecksumLocation> getChecksumLocations( Metadata metadata, boolean upload, URI location )
264        {
265            return getChecksumLocations( location );
266        }
267
268        private List<ChecksumLocation> getChecksumLocations( URI location )
269        {
270            List<ChecksumLocation> checksumLocations = new ArrayList<>( configuredChecksumAlgorithms.size() );
271            for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : configuredChecksumAlgorithms )
272            {
273                checksumLocations.add( ChecksumLocation.forLocation( location, checksumAlgorithmFactory ) );
274            }
275            return checksumLocations;
276        }
277
278        private boolean isChecksum( String extension )
279        {
280            return allChecksumAlgorithms.stream().anyMatch( a -> extension.endsWith( "." + a.getFileExtension() ) );
281        }
282    }
283}