View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.resolution;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.eclipse.aether.RepositoryException;
27  import org.eclipse.aether.repository.ArtifactRepository;
28  import org.eclipse.aether.repository.LocalArtifactResult;
29  import org.eclipse.aether.transfer.ArtifactFilteredOutException;
30  import org.eclipse.aether.transfer.ArtifactNotFoundException;
31  import org.eclipse.aether.transfer.RepositoryOfflineException;
32  
33  /**
34   * Thrown in case of a unresolvable artifacts.
35   */
36  public class ArtifactResolutionException extends RepositoryException {
37      private final transient List<ArtifactResult> results;
38  
39      /**
40       * Creates a new exception with the specified results.
41       *
42       * @param results The resolution results at the point the exception occurred, may be {@code null}.
43       */
44      public ArtifactResolutionException(List<ArtifactResult> results) {
45          super(getSmartMessage(results), getSmartCause(results));
46          if (results != null) {
47              getSuppressed(results).forEach(this::addSuppressed);
48          }
49          this.results = results != null ? results : Collections.emptyList();
50      }
51  
52      /**
53       * Creates a new exception with the specified results and detail message.
54       *
55       * @param results The resolution results at the point the exception occurred, may be {@code null}.
56       * @param message The detail message, may be {@code null}.
57       */
58      public ArtifactResolutionException(List<ArtifactResult> results, String message) {
59          super(message, getSmartCause(results));
60          if (results != null) {
61              getSuppressed(results).forEach(this::addSuppressed);
62          }
63          this.results = results != null ? results : Collections.emptyList();
64      }
65  
66      /**
67       * Creates a new exception with the specified results, detail message and cause.
68       *
69       * @param results The resolution results at the point the exception occurred, may be {@code null}.
70       * @param message The detail message, may be {@code null}.
71       * @param cause The exception that caused this one, may be {@code null}.
72       */
73      public ArtifactResolutionException(List<ArtifactResult> results, String message, Throwable cause) {
74          super(message, cause);
75          if (results != null) {
76              getSuppressed(results).forEach(this::addSuppressed);
77          }
78          this.results = results != null ? results : Collections.emptyList();
79      }
80  
81      /**
82       * Gets the resolution results at the point the exception occurred. Despite being incomplete, callers might want to
83       * use these results to fail gracefully and continue their operation with whatever interim data has been gathered.
84       *
85       * @return The resolution results, never {@code null} (empty if unknown).
86       */
87      public List<ArtifactResult> getResults() {
88          return results;
89      }
90  
91      /**
92       * Gets the first result from {@link #getResults()}. This is a convenience method for cases where callers know only
93       * a single result/request is involved.
94       *
95       * @return The (first) resolution result or {@code null} if none.
96       */
97      public ArtifactResult getResult() {
98          return (results != null && !results.isEmpty()) ? results.get(0) : null;
99      }
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 }