001/* 002 * Extra Bnd Repository Plugins 003 * Copyright (C) 2019-2022 Michael N. Lipp 004 * 005 * This program is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU Affero General Public License as published by 007 * the Free Software Foundation; either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 013 * for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License along 016 * with this program; if not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package de.mnl.osgi.bnd.repository.maven.idxmvn; 020 021import aQute.bnd.http.HttpClient; 022import aQute.bnd.osgi.repository.ResourcesRepository; 023import aQute.bnd.osgi.repository.XMLResourceGenerator; 024import aQute.bnd.osgi.repository.XMLResourceParser; 025import aQute.bnd.osgi.resource.ResourceUtils; 026import aQute.bnd.version.Version; 027import aQute.maven.api.Archive; 028import aQute.maven.api.Program; 029import aQute.maven.api.Revision; 030import aQute.maven.provider.MavenBackingRepository; 031import aQute.service.reporter.Reporter; 032import de.mnl.osgi.bnd.maven.CompositeMavenRepository.BinaryLocation; 033import de.mnl.osgi.bnd.maven.MavenResource; 034import de.mnl.osgi.bnd.maven.MavenResourceException; 035import de.mnl.osgi.bnd.maven.MavenVersion; 036import de.mnl.osgi.bnd.maven.MavenVersionRange; 037import de.mnl.osgi.bnd.maven.MavenVersionSpecification; 038import static de.mnl.osgi.bnd.maven.RepositoryUtils.rethrow; 039import static de.mnl.osgi.bnd.maven.RepositoryUtils.unthrow; 040import java.io.File; 041import java.io.IOException; 042import java.io.InputStream; 043import java.io.Writer; 044import java.net.URI; 045import java.nio.charset.Charset; 046import java.nio.file.Files; 047import java.nio.file.Path; 048import java.util.ArrayList; 049import java.util.Collection; 050import java.util.Collections; 051import java.util.Comparator; 052import java.util.HashSet; 053import java.util.List; 054import java.util.Map; 055import java.util.Optional; 056import java.util.Properties; 057import java.util.Set; 058import java.util.concurrent.CompletableFuture; 059import java.util.concurrent.ConcurrentHashMap; 060import java.util.concurrent.ConcurrentMap; 061import java.util.concurrent.ExecutionException; 062import java.util.function.Supplier; 063import java.util.regex.Matcher; 064import java.util.regex.Pattern; 065import java.util.stream.Collectors; 066import org.apache.maven.model.Dependency; 067import org.osgi.resource.Capability; 068import org.osgi.resource.Resource; 069import org.slf4j.Logger; 070import org.slf4j.LoggerFactory; 071 072/** 073 * A repository with artifacts from a single group. 074 */ 075@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.TooManyFields", 076 "PMD.ExcessiveImports", "PMD.GodClass", "PMD.TooManyMethods", 077 "PMD.CyclomaticComplexity" }) 078public class MavenGroupRepository extends ResourcesRepository { 079 080 private static final Logger LOG = LoggerFactory.getLogger( 081 MavenGroupRepository.class); 082 083 /** Indexing state. */ 084 private enum IndexingState { 085 NONE, CHECKING, INDEXED, EXCLUDED, EXCL_BY_DEP 086 } 087 088 private final String groupId; 089 private boolean requested; 090 private final IndexedMavenRepository indexedRepository; 091 private final HttpClient client; 092 private final Reporter reporter; 093 private Path groupDir; 094 private Path groupPropsPath; 095 private Path groupIndexPath; 096 private final Properties groupProps; 097 private VersionSpecification[] versionSpecs = new VersionSpecification[0]; 098 private final ConcurrentMap<Archive, IndexingState> indexingState 099 = new ConcurrentHashMap<>(); 100 private ResourcesRepository backupRepo; 101 private Writer indexingLog; 102 private final Map<Revision, List<String>> loggedMessages 103 = new ConcurrentHashMap<>(); 104 105 @SuppressWarnings("PMD.FieldNamingConventions") 106 private static final Pattern hrefPattern = Pattern.compile( 107 "<[aA]\\s+(?:[^>]*?\\s+)?href=(?<quote>[\"'])" 108 + ":?(?<href>[a-zA-Z].*?)\\k<quote>"); 109 110 /** 111 * Instantiates a new representation of group data backed 112 * by the specified directory. 113 * 114 * @param groupId the maven groupId indexed by this repository 115 * @param directory the directory used to persist data 116 * @param requested if it is a requested group id 117 * @param indexedRepository the indexed maven repository 118 * @param client the client used for remote access 119 * @param reporter the reporter 120 * @throws IOException Signals that an I/O exception has occurred. 121 */ 122 @SuppressWarnings({ "PMD.ConfusingTernary", 123 "PMD.AvoidCatchingGenericException", "PMD.AvoidDuplicateLiterals", 124 "PMD.GuardLogStatement" }) 125 public MavenGroupRepository(String groupId, Path directory, 126 boolean requested, IndexedMavenRepository indexedRepository, 127 HttpClient client, Reporter reporter) throws IOException { 128 this.groupId = groupId; 129 this.requested = requested; 130 this.indexedRepository = indexedRepository; 131 this.client = client; 132 this.reporter = reporter; 133 134 // Prepare directory and files 135 updatePaths(directory); 136 groupProps = new Properties(); 137 if (groupPropsPath.toFile().canRead()) { 138 try (InputStream input = Files.newInputStream(groupPropsPath)) { 139 groupProps.load(input); 140 versionSpecs = VersionSpecification.parse(groupProps); 141 } 142 } 143 144 // Prepare OSGi repository 145 if (groupIndexPath.toFile().canRead()) { 146 try (XMLResourceParser parser 147 = new XMLResourceParser(groupIndexPath.toFile())) { 148 addAll(parser.parse()); 149 } catch (Exception e) { // NOPMD 150 reporter.warning("Cannot parse %s, ignored: %s", groupIndexPath, 151 e.getMessage()); 152 } 153 } 154 155 // Re-use exiting resources for faster checks. 156 for (Capability cap : findProvider( 157 newRequirementBuilder("bnd.info").build())) { 158 indexingState.put( 159 Archive.valueOf((String) cap.getAttributes().get("from")), 160 IndexingState.INDEXED); 161 } 162 LOG.debug("Created group repository for {}.", groupId); 163 } 164 165 private void updatePaths(Path directory) { 166 if (!directory.toFile().exists()) { 167 directory.toFile().mkdir(); 168 } 169 groupDir = directory; 170 groupPropsPath = groupDir.resolve("group.properties"); 171 groupIndexPath = groupDir.resolve("index.xml"); 172 } 173 174 /** 175 * Checks if is requested. 176 * 177 * @return the requested 178 */ 179 public final boolean isRequested() { 180 return requested; 181 } 182 183 /** 184 * Writes all changes to persistent storage and removes 185 * any backup information prepared by {@link #reset(Path, boolean)}. 186 * 187 * @throws IOException Signals that an I/O exception has occurred. 188 */ 189 public void flush() throws IOException { 190 boolean indexChanged = true; 191 if (backupRepo != null) { 192 Set<Resource> oldSet = new HashSet<>(backupRepo.getResources()); 193 Set<Resource> newSet = new HashSet<>(getResources()); 194 if (newSet.equals(oldSet)) { 195 indexChanged = false; 196 } 197 } 198 if (indexChanged) { 199 XMLResourceGenerator generator = new XMLResourceGenerator(); 200 generator.resources(getResources()); 201 generator.name(indexedRepository.mavenRepository().name()); 202 try { 203 generator.save(groupIndexPath.toFile()); 204 } catch (IOException e) { 205 reporter.exception(e, "Cannot save %s.", groupIndexPath); 206 } 207 } 208 backupRepo = null; 209 if (indexingLog != null) { 210 rethrow(IOException.class, () -> loggedMessages.entrySet().stream() 211 .sorted(Map.Entry.comparingByKey()) 212 .flatMap(e -> e.getValue().stream()) 213 .forEach(msg -> unthrow(() -> indexingLog 214 .write(msg + System.lineSeparator())))); 215 indexingLog.close(); 216 indexingLog = null; 217 } 218 loggedMessages.clear(); 219 indexingState.clear(); 220 } 221 222 /** 223 * Removes the group directory if empty. 224 * 225 * @return true, if removed 226 * @throws IOException Signals that an I/O exception has occurred. 227 */ 228 public boolean removeIfRedundant() throws IOException { 229 if (isRequested() || !groupProps.isEmpty() 230 || !getResources().isEmpty()) { 231 return false; 232 } 233 // Nothing in this group 234 Files.walk(groupDir) 235 .sorted(Comparator.reverseOrder()) 236 .map(Path::toFile) 237 .forEach(File::delete); 238 return true; 239 } 240 241 /** 242 * Returns the group id. 243 * 244 * @return the groupId 245 */ 246 @SuppressWarnings("PMD.ShortMethodName") 247 public final String id() { 248 return groupId; 249 } 250 251 /** 252 * Clears this repository and updates the path and the requested 253 * flag. Keeps the current content as backup for reuse in a 254 * subsequent call to {@link #reload()}. 255 * 256 * @param directory this group's directory 257 * @param requested whether this is a requested group 258 */ 259 @SuppressWarnings({ "PMD.ConfusingTernary", "PMD.GuardLogStatement" }) 260 public void reset(Path directory, boolean requested) { 261 synchronized (this) { 262 // Update basic properties 263 this.requested = requested; 264 if (!groupDir.equals(directory)) { 265 updatePaths(directory); 266 backupRepo = null; 267 } else { 268 // Save current content and clear. 269 backupRepo = new ResourcesRepository(getResources()); 270 } 271 set(Collections.emptyList()); 272 // Clear and reload properties 273 groupProps.clear(); 274 if (groupPropsPath.toFile().canRead()) { 275 try (InputStream input = Files.newInputStream(groupPropsPath)) { 276 groupProps.load(input); 277 versionSpecs = VersionSpecification.parse(groupProps); 278 } catch (IOException e) { 279 reporter.warning("Problem reading %s (ignored): %s", 280 groupPropsPath, e.getMessage()); 281 } 282 } 283 // Clear remaining caches. 284 indexingState.clear(); 285 } 286 } 287 288 /** 289 * Reset the repository group. Must be called for all groups before 290 * reloading. 291 * 292 * @throws IOException Signals that an I/O exception has occurred. 293 */ 294 /* package */ void reset() throws IOException { 295 if (indexedRepository.logIndexing()) { 296 indexingLog = Files.newBufferedWriter( 297 groupDir.resolve("indexing.log"), Charset.defaultCharset()); 298 } else { 299 // Don't keep out-dated log files, it's irritating. 300 groupDir.resolve("indexing.log").toFile().delete(); 301 } 302 loggedMessages.clear(); 303 304 if (!isRequested()) { 305 // Will be filled with dependencies only 306 return; 307 } 308 // Actively filled. 309 synchronized (this) { 310 if (backupRepo == null) { 311 backupRepo = new ResourcesRepository(getResources()); 312 } 313 } 314 } 315 316 /** 317 * Reload the repository. May be called concurrently for different 318 * group repositories after resetting all. Requested repositories 319 * retrieve the list of known artifactIds from the remote repository 320 * and add the versions. For versions already in the repository, 321 * the backup information is re-used. 322 * 323 * @throws IOException Signals that an I/O exception has occurred. 324 */ 325 @SuppressWarnings({ "PMD.AvoidReassigningLoopVariables", 326 "PMD.AvoidCatchingGenericException", "PMD.AvoidDuplicateLiterals", 327 "PMD.AvoidInstantiatingObjectsInLoops", 328 "PMD.AvoidThrowingRawExceptionTypes", "PMD.PreserveStackTrace" }) 329 /* package */ void reload() throws IOException { 330 if (!isRequested()) { 331 // Will be filled with dependencies only 332 return; 333 } 334 try { 335 CompletableFuture<?>[] programLoaders = findArtifactIds().stream() 336 .map(artifactId -> loadProgram( 337 Program.valueOf(groupId, artifactId))) 338 .toArray(CompletableFuture[]::new); 339 CompletableFuture.allOf(programLoaders).get(); 340 } catch (ExecutionException e) { 341 if (e.getCause() instanceof IOException) { 342 throw (IOException) e.getCause(); 343 } 344 throw new IOException(e.getCause()); 345 } catch (InterruptedException e) { 346 reporter.exception(e, "Loading %s has been interrupted: %s", 347 groupId, e.getMessage()); 348 throw new RuntimeException(e); 349 } 350 } 351 352 @SuppressWarnings({ "PMD.AvoidCatchingGenericException", 353 "PMD.AvoidInstantiatingObjectsInLoops", "PMD.GuardLogStatement" }) 354 private Collection<String> findArtifactIds() { 355 Set<String> result = new HashSet<>(); 356 for (MavenBackingRepository repo : indexedRepository.mavenRepository() 357 .backing()) { 358 URI groupUri = null; 359 try { 360 groupUri 361 = repo.toURI("").resolve(groupId.replace('.', '/') + "/"); 362 String page = client.build().headers("User-Agent", "Bnd") 363 .get(String.class) 364 .go(groupUri); 365 if (page == null) { 366 continue; 367 } 368 Matcher matcher = hrefPattern.matcher(page); 369 while (matcher.find()) { 370 URI programUri = groupUri.resolve(matcher.group("href")); 371 String artifactId = programUri.getPath() 372 .substring(groupUri.getPath().length()); 373 if (artifactId.endsWith("/")) { 374 artifactId 375 = artifactId.substring(0, artifactId.length() - 1); 376 } 377 result.add(artifactId); 378 } 379 } catch (Exception e) { 380 reporter.warning("Problem retrieving %s, skipped: %s", groupUri, 381 e.getMessage()); 382 } 383 } 384 return result; 385 } 386 387 @SuppressWarnings({ "PMD.AvoidCatchingThrowable", "PMD.CognitiveComplexity", 388 "PMD.NPathComplexity", "PMD.NcssCount" }) 389 private CompletableFuture<Void> loadProgram(Program program) { 390 // Get revisions of program and process. 391 CompletableFuture<Void> result = new CompletableFuture<>(); 392 IndexedMavenRepository.programLoaders.submit(() -> { 393 String threadName = Thread.currentThread().getName(); 394 try { 395 Thread.currentThread().setName("RevisionQuerier " + program); 396 var resources = listRevisions(program); 397 if (resources.isEmpty()) { 398 return; 399 } 400 removeOutOfOrderVersions(resources); 401 402 // Now start indexing for remaining 403 for (var resource : resources) { 404 var archive = resource.archive(); 405 // Indexing may have been started for this as dependency 406 // but as we don't know yet if that will be successful, 407 // we start it nevertheless (concurrently). 408 if (Optional.ofNullable(indexingState.putIfAbsent( 409 resource.archive(), IndexingState.CHECKING)) 410 .orElse( 411 IndexingState.CHECKING) != IndexingState.CHECKING) { 412 logIndexing(resource, () -> String.format( 413 "%s from revision list already handled as dependency.", 414 resource)); 415 continue; 416 } 417 logIndexing(resource, () -> String.format( 418 "%s in revision list, indexing...", resource)); 419 var deps = indexableDependencies(resource, true); 420 if (deps == null) { 421 if (indexingState.replace(archive, 422 IndexingState.CHECKING, 423 IndexingState.EXCL_BY_DEP)) { 424 logIndexing(archive, () -> String.format( 425 "%s skipped due to unavailable " 426 + "dependencies.", 427 archive)); 428 } 429 continue; 430 } 431 addResourceAndDependencies(resource, deps); 432 } 433 } finally { 434 Thread.currentThread().setName(threadName); 435 result.complete(null); 436 } 437 }); 438 return result; 439 } 440 441 private List<MavenResource> listRevisions(Program program) { 442 return indexedRepository.mavenRepository().findRevisions(program) 443 .flatMap(revision -> { 444 var boundArchives 445 = VersionSpecification.toSelected(versionSpecs, revision); 446 if (boundArchives.isEmpty()) { 447 logIndexing(revision.unbound(), 448 () -> String.format("%s not selected for indexing.", 449 revision.unbound())); 450 } 451 return boundArchives.stream(); 452 }).map(boundArchive -> { 453 LOG.debug("Loading archive {}.", boundArchive); 454 return indexedRepository.mavenRepository() 455 .resource(boundArchive, BinaryLocation.REMOTE); 456 }).sorted(new Comparator<>() { 457 @Override 458 public int compare(MavenResource res1, MavenResource res2) { 459 // Sort descending 460 return res2.archive().compareTo(res1.archive()); 461 } 462 }).collect(Collectors.toList()); 463 } 464 465 private void removeOutOfOrderVersions(List<MavenResource> resources) { 466 // Remove resources with versions that are inconsistent 467 // with OSGi version order. 468 var resourcesIter = resources.iterator(); 469 Version lastVersion = null; 470 while (resourcesIter.hasNext()) { 471 var next = resourcesIter.next(); 472 var nextVersion = osgiVersion(next); 473 if (nextVersion.isEmpty()) { 474 continue; 475 } 476 if (lastVersion != null 477 && nextVersion.get().compareTo(lastVersion) >= 0) { 478 resourcesIter.remove(); 479 logIndexing(next, () -> String.format( 480 "%s skipped, violates OSGi version order.", next)); 481 continue; 482 } 483 lastVersion = nextVersion.get(); 484 } 485 } 486 487 /** 488 * Checks if the resource matches the selection criteria. 489 * 490 * @param resource the resource to check 491 * @return true, if the revision matches 492 */ 493 @SuppressWarnings("PMD.CollapsibleIfStatements") 494 private boolean indexingCandidate(MavenResource resource) { 495 Archive archive = resource.archive(); 496 if (!archive.revision.group.equals(groupId)) { 497 throw new IllegalArgumentException("Wrong groupId " 498 + archive.revision.group + " (must be " + groupId + ")."); 499 } 500 // Check if forced. 501 if (VersionSpecification.isForced(versionSpecs, archive)) { 502 return true; 503 } 504 // Check if excluded by rule. 505 if (VersionSpecification 506 .excluded(versionSpecs, archive.revision.artifact) 507 .includes(MavenVersion.from(archive.revision.version))) { 508 if (indexingState.replace(archive, IndexingState.CHECKING, 509 IndexingState.EXCLUDED)) { 510 logIndexing(archive.revision, 511 () -> String.format("%s is excluded by rule.", archive)); 512 } 513 return false; 514 } 515 return true; 516 } 517 518 @SuppressWarnings("PMD.AvoidCatchingGenericException") 519 private void addResourceAndDependencies(MavenResource resource, 520 Set<MavenResource> allDeps) { 521 addResource(resource); 522 // Add the dependencies found while checking to the index. 523 for (var depRes : allDeps) { 524 try { 525 indexedRepository.getOrCreateGroupRepository( 526 depRes.archive().getRevision().group).addResource(depRes); 527 } catch (IOException e) { 528 // No reason to fail completely. 529 reporter.exception(e, "Failed to add dependency %s of %s: %s", 530 depRes, resource, e.getMessage()); 531 logIndexing(resource, () -> String.format( 532 "%s failed to add depedendency %s.", resource, depRes)); 533 } 534 } 535 } 536 537 private Optional<Version> osgiVersion(MavenResource resource) { 538 try { 539 if (ResourceUtils.getIdentityCapability( 540 resource.asResource()) == null) { 541 return Optional.empty(); 542 } 543 return Optional 544 .ofNullable(ResourceUtils.getVersion(resource.asResource())); 545 } catch (IllegalArgumentException | MavenResourceException e) { 546 reporter.exception(e, "Failed to get as resource %s: %s", 547 resource.archive(), e.getMessage()); 548 logIndexing(resource, 549 () -> String.format("%s failed to load.", resource)); 550 return Optional.empty(); 551 } 552 } 553 554 @SuppressWarnings({ "PMD.AvoidCatchingGenericException", 555 "PMD.ReturnEmptyCollectionRatherThanNull" }) 556 private Set<MavenResource> indexableDependencies(MavenResource resource, 557 boolean log) { 558 // Get dependencies and check them 559 List<Dependency> dependencies = evaluateDependencies(resource); 560 if (!dependencies.isEmpty() && log) { 561 logIndexing(resource, 562 () -> String.format("%s has dependencies: %s", 563 resource, dependencies.stream() 564 .map(d -> d.getGroupId() + ":" + d.getArtifactId() + ":" 565 + d.getVersion()) 566 .collect(Collectors.joining(", ")))); 567 } 568 Set<MavenResource> indexable = new HashSet<>(); 569 boolean isForced = VersionSpecification.isForced(versionSpecs, 570 resource.archive()); 571 for (Dependency dep : dependencies) { 572 MavenGroupRepository depRepo; 573 try { 574 depRepo = indexedRepository 575 .getOrCreateGroupRepository(dep.getGroupId()); 576 } catch (Exception e) { 577 reporter.exception(e, "Failed to get repo %s: %s", 578 dep.getGroupId(), e.getMessage()); 579 // Failing to get a dependency is no reason to fail. 580 continue; 581 } 582 var depsDeps = depRepo.collectTransient(resource, dep, isForced); 583 if (depsDeps == null) { 584 if (log) { 585 logIndexing(resource, () -> String.format( 586 "%s lacks dependency: %s", resource, 587 dep.getGroupId() + ":" + dep.getArtifactId() + ":" 588 + dep.getVersion())); 589 } 590 return null; 591 } 592 indexable.addAll(depsDeps); 593 } 594 return indexable; 595 } 596 597 @SuppressWarnings({ "PMD.CollapsibleIfStatements", 598 "PMD.ReturnEmptyCollectionRatherThanNull", "PMD.NcssCount", 599 "PMD.CognitiveComplexity" }) 600 private Set<MavenResource> collectTransient(MavenResource dependant, 601 Dependency dependency, boolean dontFail) { 602 Set<MavenResource> collected = new HashSet<>(); 603 Optional<MavenResource> optRes = dependencyToResource(dependency); 604 if (!optRes.isPresent()) { 605 // Failing to get the resource is no reason to fail. 606 return collected; 607 } 608 MavenResource resource = optRes.get(); 609 IndexingState state = Optional.ofNullable(indexingState 610 .putIfAbsent(resource.archive(), IndexingState.CHECKING)) 611 .orElse(IndexingState.NONE); 612 switch (state) { 613 case INDEXED: 614 // Dependency (and its dependencies() have already been indexed. 615 return collected; 616 case EXCLUDED: 617 if (dontFail) { 618 return collected; 619 } 620 logIndexing(resource, () -> String.format( 621 "%s is excluded, thus blocks %s.", resource, 622 dependant)); 623 return null; 624 case EXCL_BY_DEP: 625 // Indexing of dependency has already failed. 626 if (dontFail) { 627 return collected; 628 } 629 logIndexing(resource, () -> String.format( 630 "%s lacks dependencies, thus blocks %s.", resource, 631 dependant)); 632 return null; 633 case NONE: 634 // Only the first attempt reports. 635 logIndexing(resource, () -> String.format( 636 "%s is checked as dependency of %s...", resource, dependant)); 637 break; 638 default: 639 break; 640 } 641 642 // Attempt to index. 643 Set<MavenResource> transDeps = null; 644 boolean candidate = indexingCandidate(resource); 645 if (candidate || dontFail) { 646 transDeps = indexableDependencies(resource, 647 state == IndexingState.NONE); 648 } 649 if (transDeps == null) { 650 // Note that the revision which was checked is not indexable 651 // due to a dependency that is not indexable (unless forced) 652 if (!dontFail) { 653 if (indexingState.replace(resource.archive(), 654 IndexingState.CHECKING, IndexingState.EXCL_BY_DEP)) { 655 logIndexing(resource, () -> String.format( 656 "%s lacks dependencies, thus blocks %s.", resource, 657 dependant)); 658 } 659 return null; 660 } 661 } else { 662 collected.addAll(transDeps); 663 } 664 collected.add(resource); 665 return collected; 666 } 667 668 @SuppressWarnings("PMD.AvoidCatchingGenericException") 669 private List<Dependency> evaluateDependencies(MavenResource resource) { 670 List<Dependency> deps; 671 try { 672 deps = resource.dependencies(); 673 } catch (Exception e) { 674 reporter.exception(e, "Failed to get dependency of %s: %s", 675 resource, e.getMessage()); 676 logIndexing(resource, () -> String.format( 677 "Failed to get dependencies of %s: %s", resource, 678 e.getMessage())); 679 // Failing to get the dependencies is no reason to fail. 680 return Collections.emptyList(); 681 } 682 return deps; 683 } 684 685 @SuppressWarnings("PMD.AvoidCatchingGenericException") 686 private Optional<MavenResource> dependencyToResource(Dependency dep) { 687 Program depPgm = Program.valueOf(dep.getGroupId(), dep.getArtifactId()); 688 try { 689 return indexedRepository.mavenRepository().resource( 690 depPgm, narrowVersion(depPgm, MavenVersionSpecification 691 .from(dep.getVersion())), 692 dep.getType(), dep.getClassifier(), 693 BinaryLocation.REMOTE); 694 } catch (Exception e) { 695 reporter.exception(e, "Failed to get resource %s: %s", 696 depPgm, e.getMessage()); 697 // Failing to get a dependency is no reason to fail. 698 return Optional.empty(); 699 } 700 } 701 702 private MavenVersionSpecification narrowVersion( 703 Program program, MavenVersionSpecification version) 704 throws IOException { 705 if (version instanceof MavenVersion) { 706 // Specific version, leave as is. 707 return version; 708 } 709 // If it's a range, restrict it to allowed 710 MavenVersionRange excluded = VersionSpecification 711 .excluded(versionSpecs, program.artifact); 712 return excluded.complement().restrict((MavenVersionRange) version); 713 } 714 715 /** 716 * Adds the specified revision. 717 * 718 * @param revision the revision to add 719 */ 720 @SuppressWarnings("PMD.AvoidCatchingGenericException") 721 private void addResource(MavenResource resource) { 722 if (!indexingState.replace(resource.archive(), 723 IndexingState.CHECKING, IndexingState.INDEXED)) { 724 return; 725 } 726 try { 727 // The ResourcesRepoitory that we inherit from isn't thread safe 728 synchronized (this) { 729 add(resource.asResource()); 730 } 731 logIndexing(resource, 732 () -> String.format("%s added to index.", resource)); 733 } catch (Exception e) { 734 reporter.exception(e, "Failed to get %s as resource.", resource); 735 logIndexing(resource, () -> String.format( 736 "%s could not be indexed: %s.", resource, e.getMessage())); 737 } 738 } 739 740 /* package */ Optional<Resource> searchInBackup(Archive archive) { 741 if (backupRepo == null) { 742 return Optional.empty(); 743 } 744 return backupRepo.findProvider( 745 backupRepo.newRequirementBuilder("bnd.info") 746 .addDirective("filter", 747 String.format("(from=%s)", archive.toString())) 748 .build()) 749 .stream().findFirst().map(Capability::getResource); 750 } 751 752 private void logIndexing(Revision revision, Supplier<String> msgSupplier) { 753 loggedMessages 754 .computeIfAbsent(revision, 755 rev -> Collections.synchronizedList(new ArrayList<>())) 756 .add(msgSupplier.get()); 757 } 758 759 private void logIndexing(Archive archive, Supplier<String> msgSupplier) { 760 logIndexing(archive.revision, msgSupplier); 761 } 762 763 private void logIndexing(MavenResource resource, 764 Supplier<String> msgSupplier) { 765 logIndexing(resource.archive(), msgSupplier); 766 } 767 768 /* 769 * (non-Javadoc) 770 * 771 * @see java.lang.Object#toString() 772 */ 773 @Override 774 public String toString() { 775 return "MavenGroupRepository [groupId=" + groupId + "]"; 776 } 777}