This project has retired. For details please refer to its Attic page.
Source code
001package org.apache.archiva.metadata.repository.storage.maven2;
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 org.apache.archiva.common.utils.VersionUtil;
023import org.apache.archiva.metadata.model.ArtifactMetadata;
024import org.apache.archiva.metadata.model.maven2.MavenArtifactFacet;
025import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
026import org.apache.archiva.repository.storage.StorageAsset;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029import org.springframework.stereotype.Service;
030
031import javax.annotation.PostConstruct;
032import javax.inject.Inject;
033import java.nio.file.Path;
034import java.util.List;
035import java.util.regex.Matcher;
036import java.util.regex.Pattern;
037
038/**
039 *
040 */
041@Service( "repositoryPathTranslator#maven2" )
042public class Maven2RepositoryPathTranslator
043    implements RepositoryPathTranslator
044{
045
046    private Logger log = LoggerFactory.getLogger( getClass() );
047
048    private static final char GROUP_SEPARATOR = '.';
049
050    private static final Pattern TIMESTAMP_PATTERN = Pattern.compile( "([0-9]{8}.[0-9]{6})-([0-9]+).*" );
051    
052
053    private static final Pattern MAVEN_PLUGIN_PATTERN = Pattern.compile( "^(maven-.*-plugin)|(.*-maven-plugin)$" );    
054
055    /**
056     *
057     * see #initialize
058     */
059    @Inject
060    private List<ArtifactMappingProvider> artifactMappingProviders;
061
062    public Maven2RepositoryPathTranslator()
063    {
064        // noop
065    }
066
067    @PostConstruct
068    public void initialize()
069    {
070        //artifactMappingProviders = new ArrayList<ArtifactMappingProvider>(
071        //    applicationContext.getBeansOfType( ArtifactMappingProvider.class ).values() );
072
073    }
074
075
076    public Maven2RepositoryPathTranslator( List<ArtifactMappingProvider> artifactMappingProviders )
077    {
078        this.artifactMappingProviders = artifactMappingProviders;
079    }
080
081    @Override
082    public StorageAsset toFile(StorageAsset basedir, String namespace, String projectId, String projectVersion, String filename )
083    {
084        return basedir.resolve( toPath( namespace, projectId, projectVersion, filename ) );
085    }
086
087    @Override
088    public StorageAsset toFile( StorageAsset basedir, String namespace, String projectId, String projectVersion )
089    {
090        return basedir.resolve( toPath( namespace, projectId, projectVersion ) );
091    }
092
093    @Override
094    public String toPath( String namespace, String projectId, String projectVersion, String filename )
095    {
096        StringBuilder path = new StringBuilder();
097
098        appendNamespaceToProjectVersion( path, namespace, projectId, projectVersion );
099        path.append( PATH_SEPARATOR );
100        path.append( filename );
101
102        return path.toString();
103    }
104
105    private void appendNamespaceToProjectVersion( StringBuilder path, String namespace, String projectId,
106                                                  String projectVersion )
107    {
108        appendNamespaceAndProject( path, namespace, projectId );
109        path.append( projectVersion );
110    }
111
112    public String toPath( String namespace, String projectId, String projectVersion )
113    {
114        StringBuilder path = new StringBuilder();
115
116        appendNamespaceToProjectVersion( path, namespace, projectId, projectVersion );
117
118        return path.toString();
119    }
120
121    public String toPath( String namespace )
122    {
123        StringBuilder path = new StringBuilder();
124
125        appendNamespace( path, namespace );
126
127        return path.toString();
128    }
129
130    @Override
131    public String toPath( String namespace, String projectId )
132    {
133        StringBuilder path = new StringBuilder();
134
135        appendNamespaceAndProject( path, namespace, projectId );
136
137        return path.toString();
138    }
139
140    private void appendNamespaceAndProject( StringBuilder path, String namespace, String projectId )
141    {
142        appendNamespace( path, namespace );
143        path.append( projectId ).append( PATH_SEPARATOR );
144    }
145
146    private void appendNamespace( StringBuilder path, String namespace )
147    {
148        path.append( formatAsDirectory( namespace ) ).append( PATH_SEPARATOR );
149    }
150
151    @Override
152    public StorageAsset toFile( StorageAsset basedir, String namespace, String projectId )
153    {
154        return basedir.resolve( toPath( namespace, projectId ) );
155    }
156
157    @Override
158    public StorageAsset toFile( StorageAsset basedir, String namespace )
159    {
160        return basedir.resolve( toPath( namespace ) );
161    }
162
163    private String formatAsDirectory( String directory )
164    {
165        return directory.replace( GROUP_SEPARATOR, PATH_SEPARATOR );
166    }
167
168    @Override
169    public ArtifactMetadata getArtifactForPath( String repoId, String relativePath )
170    {
171        String[] parts = relativePath.replace( '\\', '/' ).split( "/" );
172
173        int len = parts.length;
174        if ( len < 4 )
175        {
176            throw new IllegalArgumentException(
177                "Not a valid artifact path in a Maven 2 repository, not enough directories: " + relativePath );
178        }
179
180        String id = parts[--len];
181        String baseVersion = parts[--len];
182        String artifactId = parts[--len];
183        StringBuilder groupIdBuilder = new StringBuilder();
184        for ( int i = 0; i < len - 1; i++ )
185        {
186            groupIdBuilder.append( parts[i] );
187            groupIdBuilder.append( '.' );
188        }
189        groupIdBuilder.append( parts[len - 1] );
190
191        return getArtifactFromId( repoId, groupIdBuilder.toString(), artifactId, baseVersion, id );
192    }
193
194    @Override
195    public ArtifactMetadata getArtifactFromId( String repoId, String namespace, String projectId, String projectVersion,
196                                               String id )
197    {
198        if ( !id.startsWith( projectId + "-" ) )
199        {
200            throw new IllegalArgumentException( "Not a valid artifact path in a Maven 2 repository, filename '" + id
201                                                    + "' doesn't start with artifact ID '" + projectId + "'" );
202        }
203
204        MavenArtifactFacet facet = new MavenArtifactFacet();
205
206        int index = projectId.length() + 1;
207        String version;
208        String idSubStrFromVersion = id.substring( index );
209        if ( idSubStrFromVersion.startsWith( projectVersion ) && !VersionUtil.isUniqueSnapshot( projectVersion ) )
210        {
211            // non-snapshot versions, or non-timestamped snapshot versions
212            version = projectVersion;
213        }
214        else if ( VersionUtil.isGenericSnapshot( projectVersion ) )
215        {
216            // timestamped snapshots
217            try
218            {
219                int mainVersionLength = projectVersion.length() - 8; // 8 is length of "SNAPSHOT"
220                if ( mainVersionLength == 0 )
221                {
222                    throw new IllegalArgumentException(
223                        "Timestamped snapshots must contain the main version, filename was '" + id + "'" );
224                }
225
226                Matcher m = TIMESTAMP_PATTERN.matcher( idSubStrFromVersion.substring( mainVersionLength ) );
227                m.matches();
228                String timestamp = m.group( 1 );
229                String buildNumber = m.group( 2 );
230                facet.setTimestamp( timestamp );
231                facet.setBuildNumber( Integer.parseInt( buildNumber ) );
232                version = idSubStrFromVersion.substring( 0, mainVersionLength ) + timestamp + "-" + buildNumber;
233            }
234            catch ( IllegalStateException e )
235            {
236                throw new IllegalArgumentException( "Not a valid artifact path in a Maven 2 repository, filename '" + id
237                                                        + "' doesn't contain a timestamped version matching snapshot '"
238                                                        + projectVersion + "'", e);
239            }
240        }
241        else
242        {
243            // invalid
244            throw new IllegalArgumentException(
245                "Not a valid artifact path in a Maven 2 repository, filename '" + id + "' doesn't contain version '"
246                    + projectVersion + "'" );
247        }
248
249        String classifier;
250        String ext;
251        index += version.length();
252        if ( index == id.length() )
253        {
254            // no classifier or extension
255            classifier = null;
256            ext = null;
257        }
258        else
259        {
260            char c = id.charAt( index );
261            if ( c == '-' )
262            {
263                // classifier up until '.'
264                int extIndex = id.indexOf( '.', index );
265                if ( extIndex >= 0 )
266                {
267                    classifier = id.substring( index + 1, extIndex );
268                    ext = id.substring( extIndex + 1 );
269                }
270                else
271                {
272                    classifier = id.substring( index + 1 );
273                    ext = null;
274                }
275            }
276            else if ( c == '.' )
277            {
278                // rest is the extension
279                classifier = null;
280                ext = id.substring( index + 1 );
281            }
282            else
283            {
284                throw new IllegalArgumentException( "Not a valid artifact path in a Maven 2 repository, filename '" + id
285                                                        + "' expected classifier or extension but got '"
286                                                        + id.substring( index ) + "'" );
287            }
288        }
289
290        ArtifactMetadata metadata = new ArtifactMetadata();
291        metadata.setId( id );
292        metadata.setNamespace( namespace );
293        metadata.setProject( projectId );
294        metadata.setRepositoryId( repoId );
295        metadata.setProjectVersion( projectVersion );
296        metadata.setVersion( version );
297
298        facet.setClassifier( classifier );
299
300        // we use our own provider here instead of directly accessing Maven's artifact handlers as it has no way
301        // to select the correct order to apply multiple extensions mappings to a preferred type
302        // TODO: this won't allow the user to decide order to apply them if there are conflicts or desired changes -
303        //       perhaps the plugins could register missing entries in configuration, then we just use configuration
304        //       here?
305
306        String type = null;
307        for ( ArtifactMappingProvider mapping : artifactMappingProviders )
308        {
309            type = mapping.mapClassifierAndExtensionToType( classifier, ext );
310            if ( type != null )
311            {
312                break;
313            }
314        }
315
316        // TODO: this is cheating! We should check the POM metadata instead
317        if ( type == null && "jar".equals( ext ) && isArtifactIdValidMavenPlugin( projectId ) )
318        {
319            type = "maven-plugin";
320        }
321
322        // use extension as default
323        if ( type == null )
324        {
325            type = ext;
326        }
327
328        // TODO: should we allow this instead?
329        if ( type == null )
330        {
331            throw new IllegalArgumentException(
332                "Not a valid artifact path in a Maven 2 repository, filename '" + id + "' does not have a type" );
333        }
334
335        facet.setType( type );
336        metadata.addFacet( facet );
337
338        return metadata;
339    }
340
341
342    public boolean isArtifactIdValidMavenPlugin( String artifactId )
343    {
344        return MAVEN_PLUGIN_PATTERN.matcher( artifactId ).matches();
345    }
346}