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