001package org.apache.archiva.repository.metadata; 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.checksum.ChecksumAlgorithm; 023import org.apache.archiva.checksum.ChecksummedFile; 024import org.apache.archiva.common.utils.FileUtils; 025import org.apache.archiva.common.utils.PathUtil; 026import org.apache.archiva.common.utils.VersionComparator; 027import org.apache.archiva.common.utils.VersionUtil; 028import org.apache.archiva.configuration.ArchivaConfiguration; 029import org.apache.archiva.configuration.ConfigurationNames; 030import org.apache.archiva.configuration.FileTypes; 031import org.apache.archiva.configuration.ProxyConnectorConfiguration; 032import org.apache.archiva.maven2.metadata.MavenMetadataReader; 033import org.apache.archiva.model.ArchivaRepositoryMetadata; 034import org.apache.archiva.model.ArtifactReference; 035import org.apache.archiva.model.Plugin; 036import org.apache.archiva.model.ProjectReference; 037import org.apache.archiva.model.SnapshotVersion; 038import org.apache.archiva.model.VersionedReference; 039import org.apache.archiva.redback.components.registry.Registry; 040import org.apache.archiva.redback.components.registry.RegistryListener; 041import org.apache.archiva.repository.ContentNotFoundException; 042import org.apache.archiva.repository.LayoutException; 043import org.apache.archiva.repository.ManagedRepositoryContent; 044import org.apache.archiva.repository.RemoteRepositoryContent; 045import org.apache.archiva.xml.XMLException; 046import org.apache.commons.collections4.CollectionUtils; 047import org.apache.commons.lang.StringUtils; 048import org.apache.commons.lang.math.NumberUtils; 049import org.apache.commons.lang.time.DateUtils; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052import org.springframework.stereotype.Service; 053 054import javax.annotation.PostConstruct; 055import javax.inject.Inject; 056import javax.inject.Named; 057import java.io.IOException; 058import java.nio.file.Files; 059import java.nio.file.Path; 060import java.nio.file.Paths; 061import java.text.ParseException; 062import java.text.SimpleDateFormat; 063import java.util.ArrayList; 064import java.util.Arrays; 065import java.util.Calendar; 066import java.util.Collection; 067import java.util.Collections; 068import java.util.Date; 069import java.util.HashMap; 070import java.util.HashSet; 071import java.util.Iterator; 072import java.util.LinkedHashSet; 073import java.util.List; 074import java.util.Map; 075import java.util.Set; 076import java.util.regex.Matcher; 077import java.util.stream.Stream; 078 079/** 080 * MetadataTools 081 * 082 * 083 */ 084@Service( "metadataTools#default" ) 085public class MetadataTools 086 implements RegistryListener 087{ 088 private Logger log = LoggerFactory.getLogger( getClass() ); 089 090 public static final String MAVEN_METADATA = "maven-metadata.xml"; 091 092 public static final String MAVEN_ARCHETYPE_CATALOG ="archetype-catalog.xml"; 093 094 private static final char PATH_SEPARATOR = '/'; 095 096 private static final char GROUP_SEPARATOR = '.'; 097 098 /** 099 * 100 */ 101 @Inject 102 @Named( value = "archivaConfiguration#default" ) 103 private ArchivaConfiguration configuration; 104 105 /** 106 * 107 */ 108 @Inject 109 @Named( value = "fileTypes" ) 110 private FileTypes filetypes; 111 112 private List<ChecksumAlgorithm> algorithms = Arrays.asList(ChecksumAlgorithm.SHA256, ChecksumAlgorithm.SHA1, ChecksumAlgorithm.MD5 ); 113 114 private List<String> artifactPatterns; 115 116 private Map<String, Set<String>> proxies; 117 118 private static final char NUMS[] = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; 119 120 private SimpleDateFormat lastUpdatedFormat; 121 122 public MetadataTools() 123 { 124 lastUpdatedFormat = new SimpleDateFormat( "yyyyMMddHHmmss" ); 125 lastUpdatedFormat.setTimeZone( DateUtils.UTC_TIME_ZONE ); 126 } 127 128 @Override 129 public void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue ) 130 { 131 if ( ConfigurationNames.isProxyConnector( propertyName ) ) 132 { 133 initConfigVariables(); 134 } 135 } 136 137 @Override 138 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue ) 139 { 140 /* nothing to do */ 141 } 142 143 /** 144 * Gather the set of snapshot versions found in a particular versioned reference. 145 * 146 * @return the Set of snapshot artifact versions found. 147 * @throws LayoutException 148 * @throws ContentNotFoundException 149 */ 150 public Set<String> gatherSnapshotVersions( ManagedRepositoryContent managedRepository, 151 VersionedReference reference ) 152 throws LayoutException, IOException, ContentNotFoundException 153 { 154 Set<String> foundVersions = managedRepository.getVersions( reference ); 155 156 // Next gather up the referenced 'latest' versions found in any proxied repositories 157 // maven-metadata-${proxyId}.xml files that may be present. 158 159 // Does this repository have a set of remote proxied repositories? 160 Set<String> proxiedRepoIds = this.proxies.get( managedRepository.getId() ); 161 162 if ( CollectionUtils.isNotEmpty( proxiedRepoIds ) ) 163 { 164 String baseVersion = VersionUtil.getBaseVersion( reference.getVersion() ); 165 baseVersion = baseVersion.substring( 0, baseVersion.indexOf( VersionUtil.SNAPSHOT ) - 1 ); 166 167 // Add in the proxied repo version ids too. 168 Iterator<String> it = proxiedRepoIds.iterator(); 169 while ( it.hasNext() ) 170 { 171 String proxyId = it.next(); 172 173 ArchivaRepositoryMetadata proxyMetadata = readProxyMetadata( managedRepository, reference, proxyId ); 174 if ( proxyMetadata == null ) 175 { 176 // There is no proxy metadata, skip it. 177 continue; 178 } 179 180 // Is there some snapshot info? 181 SnapshotVersion snapshot = proxyMetadata.getSnapshotVersion(); 182 if ( snapshot != null ) 183 { 184 String timestamp = snapshot.getTimestamp(); 185 int buildNumber = snapshot.getBuildNumber(); 186 187 // Only interested in the timestamp + buildnumber. 188 if ( StringUtils.isNotBlank( timestamp ) && ( buildNumber > 0 ) ) 189 { 190 foundVersions.add( baseVersion + "-" + timestamp + "-" + buildNumber ); 191 } 192 } 193 } 194 } 195 196 return foundVersions; 197 } 198 199 /** 200 * Take a path to a maven-metadata.xml, and attempt to translate it to a VersionedReference. 201 * 202 * @param path 203 * @return 204 */ 205 public VersionedReference toVersionedReference( String path ) 206 throws RepositoryMetadataException 207 { 208 if ( !path.endsWith( "/" + MAVEN_METADATA ) ) 209 { 210 throw new RepositoryMetadataException( "Cannot convert to versioned reference, not a metadata file. " ); 211 } 212 213 VersionedReference reference = new VersionedReference(); 214 215 String normalizedPath = StringUtils.replace( path, "\\", "/" ); 216 String pathParts[] = StringUtils.split( normalizedPath, '/' ); 217 218 int versionOffset = pathParts.length - 2; 219 int artifactIdOffset = versionOffset - 1; 220 int groupIdEnd = artifactIdOffset - 1; 221 222 reference.setVersion( pathParts[versionOffset] ); 223 224 if ( !hasNumberAnywhere( reference.getVersion() ) ) 225 { 226 // Scary check, but without it, all paths are version references; 227 throw new RepositoryMetadataException( 228 "Not a versioned reference, as version id on path has no number in it." ); 229 } 230 231 reference.setArtifactId( pathParts[artifactIdOffset] ); 232 233 StringBuilder gid = new StringBuilder(); 234 for ( int i = 0; i <= groupIdEnd; i++ ) 235 { 236 if ( i > 0 ) 237 { 238 gid.append( "." ); 239 } 240 gid.append( pathParts[i] ); 241 } 242 243 reference.setGroupId( gid.toString() ); 244 245 return reference; 246 } 247 248 private boolean hasNumberAnywhere( String version ) 249 { 250 return StringUtils.indexOfAny( version, NUMS ) != ( -1 ); 251 } 252 253 public ProjectReference toProjectReference( String path ) 254 throws RepositoryMetadataException 255 { 256 if ( !path.endsWith( "/" + MAVEN_METADATA ) ) 257 { 258 throw new RepositoryMetadataException( "Cannot convert to versioned reference, not a metadata file. " ); 259 } 260 261 ProjectReference reference = new ProjectReference(); 262 263 String normalizedPath = StringUtils.replace( path, "\\", "/" ); 264 String pathParts[] = StringUtils.split( normalizedPath, '/' ); 265 266 // Assume last part of the path is the version. 267 268 int artifactIdOffset = pathParts.length - 2; 269 int groupIdEnd = artifactIdOffset - 1; 270 271 reference.setArtifactId( pathParts[artifactIdOffset] ); 272 273 StringBuilder gid = new StringBuilder(); 274 for ( int i = 0; i <= groupIdEnd; i++ ) 275 { 276 if ( i > 0 ) 277 { 278 gid.append( "." ); 279 } 280 gid.append( pathParts[i] ); 281 } 282 283 reference.setGroupId( gid.toString() ); 284 285 return reference; 286 } 287 288 289 290 public String toPath( ProjectReference reference ) 291 { 292 StringBuilder path = new StringBuilder(); 293 294 path.append( formatAsDirectory( reference.getGroupId() ) ).append( PATH_SEPARATOR ); 295 path.append( reference.getArtifactId() ).append( PATH_SEPARATOR ); 296 path.append( MAVEN_METADATA ); 297 298 return path.toString(); 299 } 300 301 public String toPath( VersionedReference reference ) 302 { 303 StringBuilder path = new StringBuilder(); 304 305 path.append( formatAsDirectory( reference.getGroupId() ) ).append( PATH_SEPARATOR ); 306 path.append( reference.getArtifactId() ).append( PATH_SEPARATOR ); 307 if ( reference.getVersion() != null ) 308 { 309 // add the version only if it is present 310 path.append( VersionUtil.getBaseVersion( reference.getVersion() ) ).append( PATH_SEPARATOR ); 311 } 312 path.append( MAVEN_METADATA ); 313 314 return path.toString(); 315 } 316 317 private String formatAsDirectory( String directory ) 318 { 319 return directory.replace( GROUP_SEPARATOR, PATH_SEPARATOR ); 320 } 321 322 /** 323 * Adjusts a path for a metadata.xml file to its repository specific path. 324 * 325 * @param repository the repository to base new path off of. 326 * @param path the path to the metadata.xml file to adjust the name of. 327 * @return the newly adjusted path reference to the repository specific metadata path. 328 */ 329 public String getRepositorySpecificName( RemoteRepositoryContent repository, String path ) 330 { 331 return getRepositorySpecificName( repository.getId(), path ); 332 } 333 334 /** 335 * Adjusts a path for a metadata.xml file to its repository specific path. 336 * 337 * @param proxyId the repository id to base new path off of. 338 * @param path the path to the metadata.xml file to adjust the name of. 339 * @return the newly adjusted path reference to the repository specific metadata path. 340 */ 341 public String getRepositorySpecificName( String proxyId, String path ) 342 { 343 StringBuilder ret = new StringBuilder(); 344 345 int idx = path.lastIndexOf( '/' ); 346 if ( idx > 0 ) 347 { 348 ret.append( path.substring( 0, idx + 1 ) ); 349 } 350 351 // TODO: need to filter out 'bad' characters from the proxy id. 352 ret.append( "maven-metadata-" ).append( proxyId ).append( ".xml" ); 353 354 return ret.toString(); 355 } 356 357 @PostConstruct 358 public void initialize() 359 { 360 this.artifactPatterns = new ArrayList<>(); 361 this.proxies = new HashMap<>(); 362 initConfigVariables(); 363 364 configuration.addChangeListener( this ); 365 } 366 367 public ArchivaRepositoryMetadata readProxyMetadata( ManagedRepositoryContent managedRepository, 368 ProjectReference reference, String proxyId ) 369 { 370 String metadataPath = getRepositorySpecificName( proxyId, toPath( reference ) ); 371 Path metadataFile = Paths.get( managedRepository.getRepoRoot(), metadataPath ); 372 373 if ( !Files.exists(metadataFile) || !Files.isRegularFile( metadataFile )) 374 { 375 // Nothing to do. return null. 376 return null; 377 } 378 379 try 380 { 381 return MavenMetadataReader.read( metadataFile ); 382 } 383 catch ( XMLException e ) 384 { 385 // TODO: [monitor] consider a monitor for this event. 386 // TODO: consider a read-redo on monitor return code? 387 log.warn( "Unable to read metadata: {}", metadataFile.toAbsolutePath(), e ); 388 return null; 389 } 390 } 391 392 public ArchivaRepositoryMetadata readProxyMetadata( ManagedRepositoryContent managedRepository, 393 String logicalResource, String proxyId ) 394 { 395 String metadataPath = getRepositorySpecificName( proxyId, logicalResource ); 396 Path metadataFile = Paths.get( managedRepository.getRepoRoot(), metadataPath ); 397 398 if ( !Files.exists(metadataFile) || !Files.isRegularFile( metadataFile)) 399 { 400 // Nothing to do. return null. 401 return null; 402 } 403 404 try 405 { 406 return MavenMetadataReader.read( metadataFile ); 407 } 408 catch ( XMLException e ) 409 { 410 // TODO: [monitor] consider a monitor for this event. 411 // TODO: consider a read-redo on monitor return code? 412 log.warn( "Unable to read metadata: {}", metadataFile.toAbsolutePath(), e ); 413 return null; 414 } 415 } 416 417 public ArchivaRepositoryMetadata readProxyMetadata( ManagedRepositoryContent managedRepository, 418 VersionedReference reference, String proxyId ) 419 { 420 String metadataPath = getRepositorySpecificName( proxyId, toPath( reference ) ); 421 Path metadataFile = Paths.get( managedRepository.getRepoRoot(), metadataPath ); 422 423 if ( !Files.exists(metadataFile) || !Files.isRegularFile(metadataFile)) 424 { 425 // Nothing to do. return null. 426 return null; 427 } 428 429 try 430 { 431 return MavenMetadataReader.read( metadataFile ); 432 } 433 catch ( XMLException e ) 434 { 435 // TODO: [monitor] consider a monitor for this event. 436 // TODO: consider a read-redo on monitor return code? 437 log.warn( "Unable to read metadata: {}", metadataFile.toAbsolutePath(), e ); 438 return null; 439 } 440 } 441 442 public void updateMetadata( ManagedRepositoryContent managedRepository, String logicalResource ) 443 throws RepositoryMetadataException 444 { 445 final Path metadataFile = Paths.get( managedRepository.getRepoRoot(), logicalResource ); 446 ArchivaRepositoryMetadata metadata = null; 447 448 //Gather and merge all metadata available 449 List<ArchivaRepositoryMetadata> metadatas = 450 getMetadatasForManagedRepository( managedRepository, logicalResource ); 451 for ( ArchivaRepositoryMetadata proxiedMetadata : metadatas ) 452 { 453 if ( metadata == null ) 454 { 455 metadata = proxiedMetadata; 456 continue; 457 } 458 metadata = RepositoryMetadataMerge.merge( metadata, proxiedMetadata ); 459 } 460 461 if ( metadata == null ) 462 { 463 log.debug( "No metadata to update for {}", logicalResource ); 464 return; 465 } 466 467 Set<String> availableVersions = new HashSet<String>(); 468 List<String> metadataAvailableVersions = metadata.getAvailableVersions(); 469 if ( metadataAvailableVersions != null ) 470 { 471 availableVersions.addAll( metadataAvailableVersions ); 472 } 473 availableVersions = findPossibleVersions( availableVersions, metadataFile.getParent() ); 474 475 if ( availableVersions.size() > 0 ) 476 { 477 updateMetadataVersions( availableVersions, metadata ); 478 } 479 480 RepositoryMetadataWriter.write( metadata, metadataFile ); 481 482 ChecksummedFile checksum = new ChecksummedFile( metadataFile ); 483 checksum.fixChecksums( algorithms ); 484 } 485 486 /** 487 * Skims the parent directory of a metadata in vain hope of finding 488 * subdirectories that contain poms. 489 * 490 * @param metadataParentDirectory 491 * @return origional set plus newly found versions 492 */ 493 private Set<String> findPossibleVersions( Set<String> versions, Path metadataParentDirectory ) 494 { 495 496 Set<String> result = new HashSet<String>( versions ); 497 498 try (Stream<Path> stream = Files.list( metadataParentDirectory )) { 499 stream.filter( Files::isDirectory ).filter( 500 p -> 501 { 502 try(Stream<Path> substream = Files.list(p)) 503 { 504 return substream.anyMatch( f -> Files.isRegularFile( f ) && f.toString().endsWith( ".pom" )); 505 } 506 catch ( IOException e ) 507 { 508 return false; 509 } 510 } 511 ).forEach( 512 p -> result.add(p.getFileName().toString()) 513 ); 514 } catch (IOException e) { 515 // 516 } 517 return result; 518 } 519 520 private List<ArchivaRepositoryMetadata> getMetadatasForManagedRepository( 521 ManagedRepositoryContent managedRepository, String logicalResource ) 522 { 523 List<ArchivaRepositoryMetadata> metadatas = new ArrayList<>(); 524 Path file = Paths.get( managedRepository.getRepoRoot(), logicalResource ); 525 if ( Files.exists(file) ) 526 { 527 try 528 { 529 ArchivaRepositoryMetadata existingMetadata = MavenMetadataReader.read( file ); 530 if ( existingMetadata != null ) 531 { 532 metadatas.add( existingMetadata ); 533 } 534 } 535 catch ( XMLException e ) 536 { 537 log.debug( "Could not read metadata at {}. Metadata will be removed.", file.toAbsolutePath() ); 538 FileUtils.deleteQuietly( file ); 539 } 540 } 541 542 Set<String> proxyIds = proxies.get( managedRepository.getId() ); 543 if ( proxyIds != null ) 544 { 545 for ( String proxyId : proxyIds ) 546 { 547 ArchivaRepositoryMetadata proxyMetadata = 548 readProxyMetadata( managedRepository, logicalResource, proxyId ); 549 if ( proxyMetadata != null ) 550 { 551 metadatas.add( proxyMetadata ); 552 } 553 } 554 } 555 556 return metadatas; 557 } 558 559 560 /** 561 * Update the metadata to represent the all versions/plugins of 562 * the provided groupId:artifactId project or group reference, 563 * based off of information present in the repository, 564 * the maven-metadata.xml files, and the proxy/repository specific 565 * metadata file contents. 566 * <p> 567 * We must treat this as a group or a project metadata file as there is no way to know in advance 568 * 569 * @param managedRepository the managed repository where the metadata is kept. 570 * @param reference the reference to update. 571 * @throws LayoutException 572 * @throws RepositoryMetadataException 573 * @throws IOException 574 * @throws ContentNotFoundException 575 * @deprecated 576 */ 577 public void updateMetadata( ManagedRepositoryContent managedRepository, ProjectReference reference ) 578 throws LayoutException, RepositoryMetadataException, IOException, ContentNotFoundException 579 { 580 Path metadataFile = Paths.get( managedRepository.getRepoRoot(), toPath( reference ) ); 581 582 long lastUpdated = getExistingLastUpdated( metadataFile ); 583 584 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata(); 585 metadata.setGroupId( reference.getGroupId() ); 586 metadata.setArtifactId( reference.getArtifactId() ); 587 588 // Gather up all versions found in the managed repository. 589 Set<String> allVersions = managedRepository.getVersions( reference ); 590 591 // Gather up all plugins found in the managed repository. 592 // TODO: do we know this information instead? 593// Set<Plugin> allPlugins = managedRepository.getPlugins( reference ); 594 Set<Plugin> allPlugins; 595 if ( Files.exists(metadataFile)) 596 { 597 try 598 { 599 allPlugins = new LinkedHashSet<Plugin>( MavenMetadataReader.read( metadataFile ).getPlugins() ); 600 } 601 catch ( XMLException e ) 602 { 603 throw new RepositoryMetadataException( e.getMessage(), e ); 604 } 605 } 606 else 607 { 608 allPlugins = new LinkedHashSet<Plugin>(); 609 } 610 611 // Does this repository have a set of remote proxied repositories? 612 Set<String> proxiedRepoIds = this.proxies.get( managedRepository.getId() ); 613 614 if ( CollectionUtils.isNotEmpty( proxiedRepoIds ) ) 615 { 616 // Add in the proxied repo version ids too. 617 Iterator<String> it = proxiedRepoIds.iterator(); 618 while ( it.hasNext() ) 619 { 620 String proxyId = it.next(); 621 622 ArchivaRepositoryMetadata proxyMetadata = readProxyMetadata( managedRepository, reference, proxyId ); 623 if ( proxyMetadata != null ) 624 { 625 allVersions.addAll( proxyMetadata.getAvailableVersions() ); 626 allPlugins.addAll( proxyMetadata.getPlugins() ); 627 long proxyLastUpdated = getLastUpdated( proxyMetadata ); 628 629 lastUpdated = Math.max( lastUpdated, proxyLastUpdated ); 630 } 631 } 632 } 633 634 if ( !allVersions.isEmpty() ) 635 { 636 updateMetadataVersions( allVersions, metadata ); 637 } 638 else 639 { 640 // Add the plugins to the metadata model. 641 metadata.setPlugins( new ArrayList<>( allPlugins ) ); 642 643 // artifact ID was actually the last part of the group 644 metadata.setGroupId( metadata.getGroupId() + "." + metadata.getArtifactId() ); 645 metadata.setArtifactId( null ); 646 } 647 648 if ( lastUpdated > 0 ) 649 { 650 metadata.setLastUpdatedTimestamp( toLastUpdatedDate( lastUpdated ) ); 651 } 652 653 // Save the metadata model to disk. 654 RepositoryMetadataWriter.write( metadata, metadataFile ); 655 ChecksummedFile checksum = new ChecksummedFile( metadataFile ); 656 checksum.fixChecksums( algorithms ); 657 } 658 659 private void updateMetadataVersions( Collection<String> allVersions, ArchivaRepositoryMetadata metadata ) 660 { 661 // Sort the versions 662 List<String> sortedVersions = new ArrayList<>( allVersions ); 663 Collections.sort( sortedVersions, VersionComparator.getInstance() ); 664 665 // Split the versions into released and snapshots. 666 List<String> releasedVersions = new ArrayList<>(); 667 List<String> snapshotVersions = new ArrayList<>(); 668 669 for ( String version : sortedVersions ) 670 { 671 if ( VersionUtil.isSnapshot( version ) ) 672 { 673 snapshotVersions.add( version ); 674 } 675 else 676 { 677 releasedVersions.add( version ); 678 } 679 } 680 681 Collections.sort( releasedVersions, VersionComparator.getInstance() ); 682 Collections.sort( snapshotVersions, VersionComparator.getInstance() ); 683 684 String latestVersion = sortedVersions.get( sortedVersions.size() - 1 ); 685 String releaseVersion = null; 686 687 if ( CollectionUtils.isNotEmpty( releasedVersions ) ) 688 { 689 releaseVersion = releasedVersions.get( releasedVersions.size() - 1 ); 690 } 691 692 // Add the versions to the metadata model. 693 metadata.setAvailableVersions( sortedVersions ); 694 695 metadata.setLatestVersion( latestVersion ); 696 metadata.setReleasedVersion( releaseVersion ); 697 } 698 699 private Date toLastUpdatedDate( long lastUpdated ) 700 { 701 Calendar cal = Calendar.getInstance( DateUtils.UTC_TIME_ZONE ); 702 cal.setTimeInMillis( lastUpdated ); 703 704 return cal.getTime(); 705 } 706 707 private long toLastUpdatedLong( String timestampString ) 708 { 709 try 710 { 711 Date date = lastUpdatedFormat.parse( timestampString ); 712 Calendar cal = Calendar.getInstance( DateUtils.UTC_TIME_ZONE ); 713 cal.setTime( date ); 714 715 return cal.getTimeInMillis(); 716 } 717 catch ( ParseException e ) 718 { 719 return 0; 720 } 721 } 722 723 private long getLastUpdated( ArchivaRepositoryMetadata metadata ) 724 { 725 if ( metadata == null ) 726 { 727 // Doesn't exist. 728 return 0; 729 } 730 731 try 732 { 733 String lastUpdated = metadata.getLastUpdated(); 734 if ( StringUtils.isBlank( lastUpdated ) ) 735 { 736 // Not set. 737 return 0; 738 } 739 740 Date lastUpdatedDate = lastUpdatedFormat.parse( lastUpdated ); 741 return lastUpdatedDate.getTime(); 742 } 743 catch ( ParseException e ) 744 { 745 // Bad format on the last updated string. 746 return 0; 747 } 748 } 749 750 private long getExistingLastUpdated( Path metadataFile ) 751 { 752 if ( !Files.exists(metadataFile) ) 753 { 754 // Doesn't exist. 755 return 0; 756 } 757 758 try 759 { 760 ArchivaRepositoryMetadata metadata = MavenMetadataReader.read( metadataFile ); 761 762 return getLastUpdated( metadata ); 763 } 764 catch ( XMLException e ) 765 { 766 // Error. 767 return 0; 768 } 769 } 770 771 /** 772 * Update the metadata based on the following rules. 773 * <p> 774 * 1) If this is a SNAPSHOT reference, then utilize the proxy/repository specific 775 * metadata files to represent the current / latest SNAPSHOT available. 776 * 2) If this is a RELEASE reference, and the metadata file does not exist, then 777 * create the metadata file with contents required of the VersionedReference 778 * 779 * @param managedRepository the managed repository where the metadata is kept. 780 * @param reference the versioned reference to update 781 * @throws LayoutException 782 * @throws RepositoryMetadataException 783 * @throws IOException 784 * @throws ContentNotFoundException 785 * @deprecated 786 */ 787 public void updateMetadata( ManagedRepositoryContent managedRepository, VersionedReference reference ) 788 throws LayoutException, RepositoryMetadataException, IOException, ContentNotFoundException 789 { 790 Path metadataFile = Paths.get( managedRepository.getRepoRoot(), toPath( reference ) ); 791 792 long lastUpdated = getExistingLastUpdated( metadataFile ); 793 794 ArchivaRepositoryMetadata metadata = new ArchivaRepositoryMetadata(); 795 metadata.setGroupId( reference.getGroupId() ); 796 metadata.setArtifactId( reference.getArtifactId() ); 797 798 if ( VersionUtil.isSnapshot( reference.getVersion() ) ) 799 { 800 // Do SNAPSHOT handling. 801 metadata.setVersion( VersionUtil.getBaseVersion( reference.getVersion() ) ); 802 803 // Gather up all of the versions found in the reference dir, and any 804 // proxied maven-metadata.xml files. 805 Set<String> snapshotVersions = gatherSnapshotVersions( managedRepository, reference ); 806 807 if ( snapshotVersions.isEmpty() ) 808 { 809 throw new ContentNotFoundException( 810 "No snapshot versions found on reference [" + VersionedReference.toKey( reference ) + "]." ); 811 } 812 813 // sort the list to determine to aide in determining the Latest version. 814 List<String> sortedVersions = new ArrayList<>(); 815 sortedVersions.addAll( snapshotVersions ); 816 Collections.sort( sortedVersions, new VersionComparator() ); 817 818 String latestVersion = sortedVersions.get( sortedVersions.size() - 1 ); 819 820 if ( VersionUtil.isUniqueSnapshot( latestVersion ) ) 821 { 822 // The latestVersion will contain the full version string "1.0-alpha-5-20070821.213044-8" 823 // This needs to be broken down into ${base}-${timestamp}-${build_number} 824 825 Matcher m = VersionUtil.UNIQUE_SNAPSHOT_PATTERN.matcher( latestVersion ); 826 if ( m.matches() ) 827 { 828 metadata.setSnapshotVersion( new SnapshotVersion() ); 829 int buildNumber = NumberUtils.toInt( m.group( 3 ), -1 ); 830 metadata.getSnapshotVersion().setBuildNumber( buildNumber ); 831 832 Matcher mtimestamp = VersionUtil.TIMESTAMP_PATTERN.matcher( m.group( 2 ) ); 833 if ( mtimestamp.matches() ) 834 { 835 String tsDate = mtimestamp.group( 1 ); 836 String tsTime = mtimestamp.group( 2 ); 837 838 long snapshotLastUpdated = toLastUpdatedLong( tsDate + tsTime ); 839 840 lastUpdated = Math.max( lastUpdated, snapshotLastUpdated ); 841 842 metadata.getSnapshotVersion().setTimestamp( m.group( 2 ) ); 843 } 844 } 845 } 846 else if ( VersionUtil.isGenericSnapshot( latestVersion ) ) 847 { 848 // The latestVersion ends with the generic version string. 849 // Example: 1.0-alpha-5-SNAPSHOT 850 851 metadata.setSnapshotVersion( new SnapshotVersion() ); 852 853 /* Disabled due to decision in [MRM-535]. 854 * Do not set metadata.lastUpdated to file.lastModified. 855 * 856 * Should this be the last updated timestamp of the file, or in the case of an 857 * archive, the most recent timestamp in the archive? 858 * 859 ArtifactReference artifact = getFirstArtifact( managedRepository, reference ); 860 861 if ( artifact == null ) 862 { 863 throw new IOException( "Not snapshot artifact found to reference in " + reference ); 864 } 865 866 File artifactFile = managedRepository.toFile( artifact ); 867 868 if ( artifactFile.exists() ) 869 { 870 Date lastModified = new Date( artifactFile.lastModified() ); 871 metadata.setLastUpdatedTimestamp( lastModified ); 872 } 873 */ 874 } 875 else 876 { 877 throw new RepositoryMetadataException( 878 "Unable to process snapshot version <" + latestVersion + "> reference <" + reference + ">" ); 879 } 880 } 881 else 882 { 883 // Do RELEASE handling. 884 metadata.setVersion( reference.getVersion() ); 885 } 886 887 // Set last updated 888 if ( lastUpdated > 0 ) 889 { 890 metadata.setLastUpdatedTimestamp( toLastUpdatedDate( lastUpdated ) ); 891 } 892 893 // Save the metadata model to disk. 894 RepositoryMetadataWriter.write( metadata, metadataFile ); 895 ChecksummedFile checksum = new ChecksummedFile( metadataFile ); 896 checksum.fixChecksums( algorithms ); 897 } 898 899 private void initConfigVariables() 900 { 901 synchronized ( this.artifactPatterns ) 902 { 903 this.artifactPatterns.clear(); 904 905 this.artifactPatterns.addAll( filetypes.getFileTypePatterns( FileTypes.ARTIFACTS ) ); 906 } 907 908 synchronized ( proxies ) 909 { 910 this.proxies.clear(); 911 912 List<ProxyConnectorConfiguration> proxyConfigs = configuration.getConfiguration().getProxyConnectors(); 913 for ( ProxyConnectorConfiguration proxyConfig : proxyConfigs ) 914 { 915 String key = proxyConfig.getSourceRepoId(); 916 917 Set<String> remoteRepoIds = this.proxies.get( key ); 918 919 if ( remoteRepoIds == null ) 920 { 921 remoteRepoIds = new HashSet<String>(); 922 } 923 924 remoteRepoIds.add( proxyConfig.getTargetRepoId() ); 925 926 this.proxies.put( key, remoteRepoIds ); 927 } 928 } 929 } 930 931 /** 932 * Get the first Artifact found in the provided VersionedReference location. 933 * 934 * @param managedRepository the repository to search within. 935 * @param reference the reference to the versioned reference to search within 936 * @return the ArtifactReference to the first artifact located within the versioned reference. or null if 937 * no artifact was found within the versioned reference. 938 * @throws IOException if the versioned reference is invalid (example: doesn't exist, or isn't a directory) 939 * @throws LayoutException 940 */ 941 public ArtifactReference getFirstArtifact( ManagedRepositoryContent managedRepository, 942 VersionedReference reference ) 943 throws LayoutException, IOException 944 { 945 String path = toPath( reference ); 946 947 int idx = path.lastIndexOf( '/' ); 948 if ( idx > 0 ) 949 { 950 path = path.substring( 0, idx ); 951 } 952 953 Path repoDir = Paths.get( managedRepository.getRepoRoot(), path ); 954 955 if ( !Files.exists(repoDir)) 956 { 957 throw new IOException( "Unable to gather the list of snapshot versions on a non-existant directory: " 958 + repoDir.toAbsolutePath() ); 959 } 960 961 if ( !Files.isDirectory( repoDir )) 962 { 963 throw new IOException( 964 "Unable to gather the list of snapshot versions on a non-directory: " + repoDir.toAbsolutePath() ); 965 } 966 967 try(Stream<Path> stream = Files.list(repoDir)) { 968 String result = stream.filter( Files::isRegularFile ).map( path1 -> 969 PathUtil.getRelative( managedRepository.getRepoRoot(), path1 ) 970 ).filter( filetypes::matchesArtifactPattern ).findFirst().orElse( null ); 971 if (result!=null) { 972 return managedRepository.toArtifactReference( result ); 973 } 974 } 975 // No artifact was found. 976 return null; 977 } 978 979 public ArchivaConfiguration getConfiguration() 980 { 981 return configuration; 982 } 983 984 public void setConfiguration( ArchivaConfiguration configuration ) 985 { 986 this.configuration = configuration; 987 } 988 989 public FileTypes getFiletypes() 990 { 991 return filetypes; 992 } 993 994 public void setFiletypes( FileTypes filetypes ) 995 { 996 this.filetypes = filetypes; 997 } 998}