1 package org.apache.archiva.repository.maven.content;
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
21 import org.apache.archiva.common.utils.VersionUtil;
22 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
23 import org.apache.archiva.repository.RepositoryContent;
24 import org.apache.archiva.repository.content.ItemSelector;
25 import org.apache.archiva.repository.content.LayoutException;
26 import org.apache.archiva.repository.content.base.ArchivaItemSelector;
27 import org.apache.archiva.repository.maven.metadata.storage.ArtifactMappingProvider;
28 import org.apache.commons.lang3.StringUtils;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
32 import java.util.List;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
37 * AbstractDefaultRepositoryContent - common methods for working with default (maven 2) layout.
39 public abstract class AbstractDefaultRepositoryContent implements RepositoryContent
43 protected Logger log = LoggerFactory.getLogger( getClass() );
45 public static final String MAVEN_METADATA = "maven-metadata.xml";
47 protected static final char PATH_SEPARATOR = '/';
49 protected static final char GROUP_SEPARATOR = '.';
51 protected static final char ARTIFACT_SEPARATOR = '-';
53 private static final Pattern TIMESTAMP_PATTERN = Pattern.compile( "([0-9]{8}.[0-9]{6})-([0-9]+).*" );
54 private static final Pattern MAVEN_PLUGIN_PATTERN = Pattern.compile( "^(maven-.*-plugin)|(.*-maven-plugin)$" );
56 private RepositoryPathTranslator pathTranslator;
57 private List<? extends ArtifactMappingProvider> artifactMappingProviders;
60 AbstractDefaultRepositoryContent() {
63 public RepositoryPathTranslator getPathTranslator( )
65 return pathTranslator;
68 public void setPathTranslator( RepositoryPathTranslator pathTranslator )
70 this.pathTranslator = pathTranslator;
73 public void setArtifactMappingProviders(List<? extends ArtifactMappingProvider> artifactMappingProviders) {
74 this.artifactMappingProviders = artifactMappingProviders;
77 public ArchivaItemSelector.Builder getArtifactFromFilename( String namespace, String projectId, String projectVersion,
78 String artifactFileName )
80 if ( !artifactFileName.startsWith( projectId + "-" ) )
82 throw new IllegalArgumentException( "Not a valid artifact path in a Maven 2 repository, filename '" + artifactFileName
83 + "' doesn't start with artifact ID '" + projectId + "'" );
86 int index = projectId.length() + 1;
88 String idSubStrFromVersion = artifactFileName.substring( index );
89 if ( idSubStrFromVersion.startsWith( projectVersion ) && !VersionUtil.isUniqueSnapshot( projectVersion ) )
91 // non-snapshot versions, or non-timestamped snapshot versions
92 version = projectVersion;
94 else if ( VersionUtil.isGenericSnapshot( projectVersion ) )
96 // timestamped snapshots
99 int mainVersionLength = projectVersion.length() - 8; // 8 is length of "SNAPSHOT"
100 if ( mainVersionLength == 0 )
102 throw new IllegalArgumentException(
103 "Timestamped snapshots must contain the main version, filename was '" + artifactFileName + "'" );
106 Matcher m = TIMESTAMP_PATTERN.matcher( idSubStrFromVersion.substring( mainVersionLength ) );
108 String timestamp = m.group( 1 );
109 String buildNumber = m.group( 2 );
110 version = idSubStrFromVersion.substring( 0, mainVersionLength ) + timestamp + "-" + buildNumber;
112 catch ( IllegalStateException e )
114 throw new IllegalArgumentException( "Not a valid artifact path in a Maven 2 repository, filename '" + artifactFileName
115 + "' doesn't contain a timestamped version matching snapshot '"
116 + projectVersion + "'", e);
122 throw new IllegalArgumentException(
123 "Not a valid artifact path in a Maven 2 repository, filename '" + artifactFileName + "' doesn't contain version '"
124 + projectVersion + "'" );
129 index += version.length();
130 if ( index == artifactFileName.length() )
132 // no classifier or extension
138 char c = artifactFileName.charAt( index );
141 // classifier up until '.'
142 int extIndex = artifactFileName.indexOf( '.', index );
145 classifier = artifactFileName.substring( index + 1, extIndex );
146 ext = artifactFileName.substring( extIndex + 1 );
150 classifier = artifactFileName.substring( index + 1 );
156 // rest is the extension
158 ext = artifactFileName.substring( index + 1 );
162 throw new IllegalArgumentException( "Not a valid artifact path in a Maven 2 repository, filename '" + artifactFileName
163 + "' expected classifier or extension but got '"
164 + artifactFileName.substring( index ) + "'" );
168 ArchivaItemSelector.Builder selectorBuilder = ArchivaItemSelector.builder( )
169 .withNamespace( namespace )
170 .withProjectId( projectId )
171 .withArtifactId( projectId )
172 .withVersion( projectVersion )
173 .withArtifactVersion( version )
174 .withClassifier( classifier );
177 // we use our own provider here instead of directly accessing Maven's artifact handlers as it has no way
178 // to select the correct order to apply multiple extensions mappings to a preferred type
179 // TODO: this won't allow the user to decide order to apply them if there are conflicts or desired changes -
180 // perhaps the plugins could register missing entries in configuration, then we just use configuration
184 for ( ArtifactMappingProvider mapping : artifactMappingProviders )
186 type = mapping.mapClassifierAndExtensionToType( classifier, ext );
193 // TODO: this is cheating! We should check the POM metadata instead
194 if ( type == null && "jar".equals( ext ) && isArtifactIdValidMavenPlugin( projectId ) )
196 type = "maven-plugin";
199 // use extension as default
205 // TODO: should we allow this instead?
208 throw new IllegalArgumentException(
209 "Not a valid artifact path in a Maven 2 repository, filename '" + artifactFileName + "' does not have a type" );
212 selectorBuilder.withType( type );
215 return selectorBuilder;
218 public boolean isArtifactIdValidMavenPlugin( String artifactId )
220 return MAVEN_PLUGIN_PATTERN.matcher( artifactId ).matches();
223 private ArchivaItemSelector getArtifactForPath( String relativePath )
225 String[] parts = relativePath.replace( '\\', '/' ).split( "/" );
227 int len = parts.length;
230 throw new IllegalArgumentException(
231 "Not a valid artifact path in a Maven 2 repository, not enough directories: " + relativePath );
234 String fileName = parts[--len];
235 String baseVersion = parts[--len];
236 String artifactId = parts[--len];
237 StringBuilder namespaceBuilder = new StringBuilder();
238 for ( int i = 0; i < len - 1; i++ )
240 namespaceBuilder.append( parts[i] );
241 namespaceBuilder.append( '.' );
243 namespaceBuilder.append( parts[len - 1] );
245 return getArtifactFromFilename( namespaceBuilder.toString(), artifactId, baseVersion, fileName ).build();
249 public ItemSelector toItemSelector( String path ) throws LayoutException
251 if ( StringUtils.isBlank( path ) )
253 throw new LayoutException( "Unable to convert blank path." );
258 return getArtifactForPath( path );
260 catch ( IllegalArgumentException e )
262 throw new LayoutException( e.getMessage(), e );
268 public String toPath ( ItemSelector selector ) {
269 if (selector==null) {
270 throw new IllegalArgumentException( "ItemSelector must not be null." );
273 // Initialize the project id if not set
274 if (selector.hasProjectId()) {
275 projectId = selector.getProjectId( );
276 } else if (selector.hasArtifactId()) {
277 // projectId same as artifact id, if set
278 projectId = selector.getArtifactId( );
280 // we arrive here, if projectId && artifactId not set
281 return pathTranslator.toPath( selector.getNamespace(), "");
283 if ( !selector.hasArtifactId( )) {
284 return pathTranslator.toPath( selector.getNamespace( ), projectId );
286 // this part only, if projectId && artifactId is set
287 String artifactVersion = "";
289 if (selector.hasVersion() && selector.hasArtifactVersion() ) {
290 artifactVersion = selector.getArtifactVersion();
291 version = VersionUtil.getBaseVersion( selector.getVersion( ) );
292 } else if (!selector.hasVersion() && selector.hasArtifactVersion()) {
293 // we try to retrieve the base version, if artifact version is only set
294 version = VersionUtil.getBaseVersion( selector.getArtifactVersion( ) );
295 artifactVersion = selector.getArtifactVersion( );
296 } else if (selector.hasVersion() && !selector.hasArtifactVersion()) {
297 artifactVersion = selector.getVersion();
298 version = VersionUtil.getBaseVersion( selector.getVersion( ) );
301 return pathTranslator.toPath( selector.getNamespace(), projectId, version,
302 constructId( selector.getArtifactId(), artifactVersion, selector.getClassifier(), selector.getType() ) );
307 public String toPath( String namespace )
309 return formatAsDirectory( namespace );
313 protected String formatAsDirectory( String directory )
315 return directory.replace( GROUP_SEPARATOR, PATH_SEPARATOR );
318 private String toPath( String groupId, String artifactId, String baseVersion, String version, String classifier,
321 if ( baseVersion != null )
323 return pathTranslator.toPath( groupId, artifactId, baseVersion,
324 constructId( artifactId, version, classifier, type ) );
328 return pathTranslator.toPath( groupId, artifactId );
332 // TODO: move into the Maven Artifact facet when refactoring away the caller - the caller will need to have access
333 // to the facet or filename (for the original ID)
334 private String constructId( String artifactId, String version, String classifier, String type )
337 for ( ArtifactMappingProvider provider : artifactMappingProviders )
339 ext = provider.mapTypeToExtension( type );
350 StringBuilder id = new StringBuilder();
351 if ( ( version != null ) && ( type != null ) )
353 id.append( artifactId ).append( ARTIFACT_SEPARATOR ).append( version );
355 if ( StringUtils.isNotBlank( classifier ) )
357 id.append( ARTIFACT_SEPARATOR ).append( classifier );
360 id.append( "." ).append( ext );
362 return id.toString();