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.List;
028import java.util.Set;
029import java.util.stream.Collectors;
030
031import javax.inject.Inject;
032import javax.inject.Named;
033import javax.inject.Singleton;
034
035import org.eclipse.aether.RepositorySystemSession;
036import org.eclipse.aether.artifact.Artifact;
037import org.eclipse.aether.internal.impl.checksum.DefaultChecksumAlgorithmFactorySelector;
038import org.eclipse.aether.metadata.Metadata;
039import org.eclipse.aether.repository.RemoteRepository;
040import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
041import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
042import org.eclipse.aether.spi.connector.layout.RepositoryLayout;
043import org.eclipse.aether.spi.connector.layout.RepositoryLayoutFactory;
044import org.eclipse.aether.transfer.NoRepositoryLayoutException;
045import org.eclipse.aether.util.ConfigUtils;
046
047import static java.util.Objects.requireNonNull;
048
049/**
050 * Provides a Maven-2 repository layout for repositories with content type {@code "default"}.
051 */
052@Singleton
053@Named( "maven2" )
054public final class Maven2RepositoryLayoutFactory
055        implements RepositoryLayoutFactory
056{
057
058    public static final String CONFIG_PROP_CHECKSUMS_ALGORITHMS = "aether.checksums.algorithms";
059
060    private static final String DEFAULT_CHECKSUMS_ALGORITHMS = "SHA-1,MD5";
061
062    public static final String CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS =
063            "aether.checksums.omitChecksumsForExtensions";
064
065    private static final String DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS = ".asc";
066
067    private float priority;
068
069    private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
070
071    public float getPriority()
072    {
073        return priority;
074    }
075
076    /**
077     * Service locator ctor.
078     */
079    @Deprecated
080    public Maven2RepositoryLayoutFactory()
081    {
082        this( new DefaultChecksumAlgorithmFactorySelector() );
083    }
084
085    @Inject
086    public Maven2RepositoryLayoutFactory( ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector )
087    {
088        this.checksumAlgorithmFactorySelector = requireNonNull( checksumAlgorithmFactorySelector );
089    }
090
091    /**
092     * Sets the priority of this component.
093     *
094     * @param priority The priority.
095     * @return This component for chaining, never {@code null}.
096     */
097    public Maven2RepositoryLayoutFactory setPriority( float priority )
098    {
099        this.priority = priority;
100        return this;
101    }
102
103    public RepositoryLayout newInstance( RepositorySystemSession session, RemoteRepository repository )
104            throws NoRepositoryLayoutException
105    {
106        requireNonNull( session, "session cannot be null" );
107        requireNonNull( repository, "repository cannot be null" );
108        if ( !"default".equals( repository.getContentType() ) )
109        {
110            throw new NoRepositoryLayoutException( repository );
111        }
112
113        List<ChecksumAlgorithmFactory> checksumsAlgorithms = checksumAlgorithmFactorySelector.selectList(
114                ConfigUtils.parseCommaSeparatedUniqueNames( ConfigUtils.getString(
115                        session, DEFAULT_CHECKSUMS_ALGORITHMS, CONFIG_PROP_CHECKSUMS_ALGORITHMS ) )
116        );
117
118        // ensure uniqueness of (potentially user set) extension list
119        Set<String> omitChecksumsForExtensions = Arrays.stream( ConfigUtils.getString(
120                        session, DEFAULT_OMIT_CHECKSUMS_FOR_EXTENSIONS, CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS )
121                .split( "," )
122        ).filter( s -> s != null && !s.trim().isEmpty() ).collect( Collectors.toSet() );
123
124        // validation: enforce that all strings in this set are having leading dot
125        if ( omitChecksumsForExtensions.stream().anyMatch( s -> !s.startsWith( "." ) ) )
126        {
127            throw new IllegalArgumentException(
128                    String.format(
129                            "The configuration %s contains illegal values: %s (all entries must start with '.' (dot))",
130                            CONFIG_PROP_OMIT_CHECKSUMS_FOR_EXTENSIONS,
131                            omitChecksumsForExtensions
132                    )
133            );
134        }
135
136        return new Maven2RepositoryLayout(
137                new ArrayList<>( checksumAlgorithmFactorySelector.getChecksumAlgorithmFactories() ),
138                checksumsAlgorithms,
139                omitChecksumsForExtensions
140        );
141    }
142
143    private static class Maven2RepositoryLayout
144            implements RepositoryLayout
145    {
146        private final List<ChecksumAlgorithmFactory> allChecksumAlgorithms;
147
148        private final List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms;
149
150        private final Set<String> extensionsWithoutChecksums;
151
152        private Maven2RepositoryLayout( List<ChecksumAlgorithmFactory> allChecksumAlgorithms,
153                                        List<ChecksumAlgorithmFactory> configuredChecksumAlgorithms,
154                                        Set<String> extensionsWithoutChecksums )
155        {
156            this.allChecksumAlgorithms = Collections.unmodifiableList( allChecksumAlgorithms );
157            this.configuredChecksumAlgorithms = Collections.unmodifiableList( configuredChecksumAlgorithms );
158            this.extensionsWithoutChecksums = requireNonNull( extensionsWithoutChecksums );
159        }
160
161        private URI toUri( String path )
162        {
163            try
164            {
165                return new URI( null, null, path, null );
166            }
167            catch ( URISyntaxException e )
168            {
169                throw new IllegalStateException( e );
170            }
171        }
172
173        @Override
174        public List<ChecksumAlgorithmFactory> getChecksumAlgorithmFactories()
175        {
176            return configuredChecksumAlgorithms;
177        }
178
179        @Override
180        public boolean hasChecksums( Artifact artifact )
181        {
182            String artifactExtension = artifact.getExtension(); // ie. pom.asc
183            for ( String extensionWithoutChecksums : extensionsWithoutChecksums )
184            {
185                if ( artifactExtension.endsWith( extensionWithoutChecksums ) )
186                {
187                    return false;
188                }
189            }
190            return true;
191        }
192
193        @Override
194        public URI getLocation( Artifact artifact, boolean upload )
195        {
196            StringBuilder path = new StringBuilder( 128 );
197
198            path.append( artifact.getGroupId().replace( '.', '/' ) ).append( '/' );
199
200            path.append( artifact.getArtifactId() ).append( '/' );
201
202            path.append( artifact.getBaseVersion() ).append( '/' );
203
204            path.append( artifact.getArtifactId() ).append( '-' ).append( artifact.getVersion() );
205
206            if ( artifact.getClassifier().length() > 0 )
207            {
208                path.append( '-' ).append( artifact.getClassifier() );
209            }
210
211            if ( artifact.getExtension().length() > 0 )
212            {
213                path.append( '.' ).append( artifact.getExtension() );
214            }
215
216            return toUri( path.toString() );
217        }
218
219        @Override
220        public URI getLocation( Metadata metadata, boolean upload )
221        {
222            StringBuilder path = new StringBuilder( 128 );
223
224            if ( metadata.getGroupId().length() > 0 )
225            {
226                path.append( metadata.getGroupId().replace( '.', '/' ) ).append( '/' );
227
228                if ( metadata.getArtifactId().length() > 0 )
229                {
230                    path.append( metadata.getArtifactId() ).append( '/' );
231
232                    if ( metadata.getVersion().length() > 0 )
233                    {
234                        path.append( metadata.getVersion() ).append( '/' );
235                    }
236                }
237            }
238
239            path.append( metadata.getType() );
240
241            return toUri( path.toString() );
242        }
243
244        @Override
245        public List<ChecksumLocation> getChecksumLocations( Artifact artifact, boolean upload, URI location )
246        {
247            if ( !hasChecksums( artifact ) || isChecksum( artifact.getExtension() ) )
248            {
249                return Collections.emptyList();
250            }
251            return getChecksumLocations( location );
252        }
253
254        @Override
255        public List<ChecksumLocation> getChecksumLocations( Metadata metadata, boolean upload, URI location )
256        {
257            return getChecksumLocations( location );
258        }
259
260        private List<ChecksumLocation> getChecksumLocations( URI location )
261        {
262            List<ChecksumLocation> checksumLocations = new ArrayList<>( configuredChecksumAlgorithms.size() );
263            for ( ChecksumAlgorithmFactory checksumAlgorithmFactory : configuredChecksumAlgorithms )
264            {
265                checksumLocations.add( ChecksumLocation.forLocation( location, checksumAlgorithmFactory ) );
266            }
267            return checksumLocations;
268        }
269
270        private boolean isChecksum( String extension )
271        {
272            return allChecksumAlgorithms.stream().anyMatch( a -> extension.endsWith( "." + a.getFileExtension() ) );
273        }
274    }
275}