001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.resolution; 020 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.List; 024import java.util.Map; 025 026import org.eclipse.aether.RepositoryException; 027import org.eclipse.aether.repository.ArtifactRepository; 028import org.eclipse.aether.repository.LocalArtifactResult; 029import org.eclipse.aether.transfer.ArtifactFilteredOutException; 030import org.eclipse.aether.transfer.ArtifactNotFoundException; 031import org.eclipse.aether.transfer.RepositoryOfflineException; 032 033/** 034 * Thrown in case of a unresolvable artifacts. 035 */ 036public class ArtifactResolutionException extends RepositoryException { 037 private final transient List<ArtifactResult> results; 038 039 /** 040 * Creates a new exception with the specified results. 041 * 042 * @param results The resolution results at the point the exception occurred, may be {@code null}. 043 */ 044 public ArtifactResolutionException(List<ArtifactResult> results) { 045 super(getSmartMessage(results), getSmartCause(results)); 046 if (results != null) { 047 getSuppressed(results).forEach(this::addSuppressed); 048 } 049 this.results = results != null ? results : Collections.emptyList(); 050 } 051 052 /** 053 * Creates a new exception with the specified results and detail message. 054 * 055 * @param results The resolution results at the point the exception occurred, may be {@code null}. 056 * @param message The detail message, may be {@code null}. 057 */ 058 public ArtifactResolutionException(List<ArtifactResult> results, String message) { 059 super(message, getSmartCause(results)); 060 if (results != null) { 061 getSuppressed(results).forEach(this::addSuppressed); 062 } 063 this.results = results != null ? results : Collections.emptyList(); 064 } 065 066 /** 067 * Creates a new exception with the specified results, detail message and cause. 068 * 069 * @param results The resolution results at the point the exception occurred, may be {@code null}. 070 * @param message The detail message, may be {@code null}. 071 * @param cause The exception that caused this one, may be {@code null}. 072 */ 073 public ArtifactResolutionException(List<ArtifactResult> results, String message, Throwable cause) { 074 super(message, cause); 075 if (results != null) { 076 getSuppressed(results).forEach(this::addSuppressed); 077 } 078 this.results = results != null ? results : Collections.emptyList(); 079 } 080 081 /** 082 * Gets the resolution results at the point the exception occurred. Despite being incomplete, callers might want to 083 * use these results to fail gracefully and continue their operation with whatever interim data has been gathered. 084 * 085 * @return The resolution results, never {@code null} (empty if unknown). 086 */ 087 public List<ArtifactResult> getResults() { 088 return results; 089 } 090 091 /** 092 * Gets the first result from {@link #getResults()}. This is a convenience method for cases where callers know only 093 * a single result/request is involved. 094 * 095 * @return The (first) resolution result or {@code null} if none. 096 */ 097 public ArtifactResult getResult() { 098 return (results != null && !results.isEmpty()) ? results.get(0) : null; 099 } 100 101 private static String getSmartMessage(List<? extends ArtifactResult> results) { 102 if (results == null) { 103 return null; 104 } 105 StringBuilder buffer = new StringBuilder(256); 106 107 buffer.append("The following artifacts could not be resolved: "); 108 109 String sep = ""; 110 for (ArtifactResult result : results) { 111 if (!result.isResolved()) { 112 buffer.append(sep); 113 buffer.append(result.getRequest().getArtifact()); 114 LocalArtifactResult localResult = result.getLocalArtifactResult(); 115 if (localResult != null) { 116 buffer.append(" ("); 117 if (localResult.getPath() != null) { 118 buffer.append("present"); 119 if (!localResult.isAvailable()) { 120 buffer.append(", but unavailable"); 121 } 122 } else { 123 buffer.append("absent"); 124 } 125 buffer.append(")"); 126 } 127 sep = ", "; 128 } 129 } 130 131 Throwable cause = getSmartCause(results); 132 if (cause != null) { 133 buffer.append(": ").append(cause.getMessage()); 134 } 135 136 return buffer.toString(); 137 } 138 139 /** 140 * This method tries to be smart and figure out "cause", but it results in somewhat incomplete result. Maven Core 141 * and probably many other code relies on it, so is left in place, but client code should use {@link #getResults()} 142 * and {@link ArtifactResult#getMappedExceptions()} methods to build more appropriate error messages. 143 */ 144 private static Throwable getSmartCause(List<? extends ArtifactResult> results) { 145 if (results == null) { 146 return null; 147 } 148 for (ArtifactResult result : results) { 149 if (!result.isResolved()) { 150 Throwable notFound = null, offline = null; 151 for (Throwable t : result.getExceptions()) { 152 if (t instanceof ArtifactNotFoundException) { 153 if (notFound == null || notFound instanceof ArtifactFilteredOutException) { 154 notFound = t; 155 } 156 if (offline == null && t.getCause() instanceof RepositoryOfflineException) { 157 offline = t; 158 } 159 } else { 160 return t; 161 } 162 } 163 if (offline != null) { 164 return offline; 165 } 166 if (notFound != null) { 167 return notFound; 168 } 169 } 170 } 171 return null; 172 } 173 174 /** 175 * Builds a forest of exceptions to be used as suppressed, and it will contain the whole forest of exceptions per 176 * repository. 177 */ 178 private static List<Throwable> getSuppressed(List<? extends ArtifactResult> results) { 179 ArrayList<Throwable> result = new ArrayList<>(results.size()); 180 for (ArtifactResult artifactResult : results) { 181 if (!artifactResult.isResolved()) { 182 ArtifactResolutionException root = new ArtifactResolutionException( 183 null, 184 "Failed to resolve artifact " 185 + artifactResult.getRequest().getArtifact()); 186 for (Map.Entry<ArtifactRepository, List<Exception>> entry : 187 artifactResult.getMappedExceptions().entrySet()) { 188 for (Exception e : entry.getValue()) { 189 root.addSuppressed(e); 190 } 191 } 192 result.add(root); 193 } 194 } 195 return result; 196 } 197}