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}