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.transfer;
20  
21  import org.eclipse.aether.RepositoryException;
22  
23  /**
24   * Thrown in case of a checksum failure during an artifact/metadata download. This exception is usually thrown in
25   * following cases:
26   * <ul>
27   *     <li>actual checksum <em>mismatch</em>, see {@link #mismatch(String, String, String)}</li>
28   *     <li>lack of required checksums, see {@link #noneAvailable(String, String)}</li>
29   *     <li>processing problem during checksum checks (ie IO problem), see {@link #processingFailure(String, Throwable)}</li>
30   * </ul>
31   * It is resolver expectation to provide most available information to caller.
32   */
33  public class ChecksumFailureException extends RepositoryException {
34  
35      private final String expected;
36  
37      private final String expectedKind;
38  
39      private final String actual;
40  
41      private final boolean retryWorthy;
42  
43      /**
44       * Use in case of checksum mismatch. Creates a new exception with the specified expected, expected kind and actual
45       * checksum. The resulting exception is {@link #isRetryWorthy() retry-worthy}. The checksum match check should
46       * have already happened, this method does not check for any kind of inequality.
47       *
48       * @param expected The expected checksum as declared by the hosting repository, may be {@code null}.
49       * @param expectedKind The expected checksum kind, may be {@code null}.
50       * @param actual The actual checksum as computed from the local bytes, may be {@code null}.
51       * @since 2.0.19
52       */
53      public static ChecksumFailureException mismatch(String expected, String expectedKind, String actual) {
54          return mismatchDetail(null, expected, expectedKind, actual);
55      }
56  
57      /**
58       * Use in case of checksum mismatch. Creates a new exception with the specified expected, expected kind and actual
59       * checksum. The resulting exception is {@link #isRetryWorthy() retry-worthy}. The checksum match check should
60       * have already happened, this method does not check for any kind of inequality.
61       *
62       * @param detail The extra detail/information regarding mismatch.
63       * @param expected The expected checksum as declared by the hosting repository, may be {@code null}.
64       * @param expectedKind The expected checksum kind, may be {@code null}.
65       * @param actual The actual checksum as computed from the local bytes, may be {@code null}.
66       * @since 2.0.19
67       */
68      public static ChecksumFailureException mismatchDetail(
69              String detail, String expected, String expectedKind, String actual) {
70          String message = "Checksum validation failed, expected '"
71                  + sanitize(expected) + "'" + (expectedKind == null ? "" : " (" + sanitize(expectedKind) + ")")
72                  + " but is actually '" + sanitize(actual) + "'";
73          if (detail != null) {
74              message = message + " (" + sanitize(detail) + ")";
75          }
76          return new ChecksumFailureException(message, null, expected, expectedKind, actual, true);
77      }
78  
79      /**
80       * Replaces ISO control characters below U+0020 (except LF and TAB) and DEL (U+007F) with their visible
81       * {@code \}{@code uXXXX} escapes. The expected checksum value is remote-supplied and ends up in log output (the
82       * default "warn" checksum policy logs this exception's message as the sole integrity warning): raw terminal
83       * control characters such as ESC or CR embedded in it could erase or forge that log line. The
84       * {@link #getExpected()}, {@link #getExpectedKind()} and {@link #getActual()} accessors keep returning the
85       * raw values.
86       */
87      private static String sanitize(String value) {
88          if (value == null) {
89              return null;
90          }
91          StringBuilder result = null;
92          for (int i = 0; i < value.length(); i++) {
93              char c = value.charAt(i);
94              if ((c < 0x20 && c != '\n' && c != '\t') || c == 0x7f) {
95                  if (result == null) {
96                      result = new StringBuilder(value.length() + 16);
97                      result.append(value, 0, i);
98                  }
99                  result.append(String.format("\\u%04X", (int) c));
100             } else if (result != null) {
101                 result.append(c);
102             }
103         }
104         return result == null ? value : result.toString();
105     }
106 
107     /**
108      * Use in case of checksum not available. Optionally, one can specify which kind was not available.
109      *
110      * @param message The message.
111      * @param expectedKind The expected checksum kind, may be {@code null}.
112      * @since 2.0.19
113      */
114     public static ChecksumFailureException noneAvailable(String message, String expectedKind) {
115         return new ChecksumFailureException(message, null, "", expectedKind == null ? "" : expectedKind, "", false);
116     }
117 
118     /**
119      * Use in case of error, for example IO problem during checksum processing, calculation and alike. Ideally, one
120      * should specify cause as well.
121      *
122      * @param message The message.
123      * @param cause The cause.
124      * @since 2.0.19
125      */
126     public static ChecksumFailureException processingFailure(String message, Throwable cause) {
127         return new ChecksumFailureException(message, cause, "", "", "", false);
128     }
129 
130     /**
131      * Creates a new exception with the specified expected, expected kind and actual checksum. The resulting exception
132      * is {@link #isRetryWorthy() retry-worthy}.
133      *
134      * @param expected The expected checksum as declared by the hosting repository, may be {@code null}.
135      * @param expectedKind The expected checksum kind, may be {@code null}.
136      * @param actual The actual checksum as computed from the local bytes, may be {@code null}.
137      * @since 1.8.0
138      * @deprecated Use {@link #mismatch(String, String, String)} or other suitable helper method instead.
139      */
140     @Deprecated
141     public ChecksumFailureException(String expected, String expectedKind, String actual) {
142         super("Checksum validation failed, expected '"
143                 + sanitize(expected) + "'" + (expectedKind == null ? "" : " (" + sanitize(expectedKind) + ")")
144                 + " but is actually '" + sanitize(actual) + "'");
145         this.expected = expected;
146         this.expectedKind = expectedKind;
147         this.actual = actual;
148         this.retryWorthy = true;
149     }
150 
151     /**
152      * Creates a new exception with the specified detail message. The resulting exception is not
153      * {@link #isRetryWorthy() retry-worthy}. Use this constructor ONLY in cases like "no data to work with",
154      * like missing checksums. In every other case use some other constructor.
155      *
156      * @param message The detail message, may be {@code null}.
157      * @deprecated Use {@link #noneAvailable(String, String)} or other suitable helper method instead.
158      */
159     @Deprecated
160     public ChecksumFailureException(String message) {
161         this(message, null, "", "", "", false);
162     }
163 
164     /**
165      * Creates a new exception with the specified cause. The resulting exception is not {@link #isRetryWorthy()
166      * retry-worthy}. Use this constructor in case some other error (ie IO problem) prevented checksum calculation.
167      *
168      * @param cause The exception that caused this one, may be {@code null}.
169      * @deprecated Use {@link #processingFailure(String, Throwable)} or other helper method instead.
170      */
171     @Deprecated
172     public ChecksumFailureException(Throwable cause) {
173         this("Checksum validation failed" + getMessage(": ", cause), cause, "", "", "", false);
174     }
175 
176     /**
177      * Creates a new exception with the specified detail message and cause. The resulting exception is not
178      * {@link #isRetryWorthy() retry-worthy}. Use this constructor in case some other error (ie IO problem)
179      * prevented checksum calculation.
180      *
181      * @param message The detail message, may be {@code null}.
182      * @param cause The exception that caused this one, may be {@code null}.
183      * @deprecated Use {@link #processingFailure(String, Throwable)} or other helper method instead.
184      */
185     @Deprecated
186     public ChecksumFailureException(String message, Throwable cause) {
187         this(message, cause, "", "", "", false);
188     }
189 
190     /**
191      * Creates a new exception with the specified retry flag, detail message and cause.
192      *
193      * @param retryWorthy {@code true} if the exception is retry-worthy, {@code false} otherwise.
194      * @param message The detail message, may be {@code null}.
195      * @param cause The exception that caused this one, may be {@code null}.
196      * @deprecated Do not use this constructor, it lacks information.
197      */
198     @Deprecated
199     public ChecksumFailureException(boolean retryWorthy, String message, Throwable cause) {
200         this(message, cause, "", "", "", retryWorthy);
201     }
202 
203     /**
204      * Hidden constructor, use static helper methods instead, suitable for your case.
205      */
206     private ChecksumFailureException(
207             String message, Throwable cause, String expected, String expectedKind, String actual, boolean retryWorthy) {
208         super(message, cause);
209         this.expected = expected;
210         this.expectedKind = expectedKind;
211         this.actual = actual;
212         this.retryWorthy = retryWorthy;
213     }
214 
215     /**
216      * Gets the expected checksum for the downloaded artifact/metadata.
217      *
218      * @return The expected checksum as declared by the hosting repository or {@code null} if unknown.
219      */
220     public String getExpected() {
221         return expected;
222     }
223 
224     /**
225      * Gets the expected checksum kind for the downloaded artifact/metadata.
226      *
227      * @return The expected checksum kind or {@code null} if unknown.
228      * @since 1.8.0
229      */
230     public String getExpectedKind() {
231         return expectedKind;
232     }
233 
234     /**
235      * Gets the actual checksum for the downloaded artifact/metadata.
236      *
237      * @return The actual checksum as computed from the local bytes or {@code null} if unknown.
238      */
239     public String getActual() {
240         return actual;
241     }
242 
243     /**
244      * Indicates whether the corresponding download is retry-worthy.
245      *
246      * @return {@code true} if retrying the download might solve the checksum failure, {@code false} if the checksum
247      *         failure is non-recoverable.
248      */
249     public boolean isRetryWorthy() {
250         return retryWorthy;
251     }
252 }