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