|
12 | 12 | import javax.annotation.Nonnull; |
13 | 13 | import javax.annotation.Nullable; |
14 | 14 | import java.util.*; |
| 15 | +import java.util.logging.Level; |
| 16 | +import java.util.logging.Logger; |
15 | 17 |
|
16 | 18 | /** |
17 | 19 | * Class that represents the details of a task failure. |
|
21 | 23 | * result in task failures, in which case there may not be any exception-specific information. |
22 | 24 | */ |
23 | 25 | public final class FailureDetails { |
| 26 | + private static final Logger logger = Logger.getLogger(FailureDetails.class.getName()); |
| 27 | + |
24 | 28 | private final String errorType; |
25 | 29 | private final String errorMessage; |
26 | 30 | private final String stackTrace; |
@@ -138,17 +142,32 @@ public Map<String, Object> getProperties() { |
138 | 142 | } |
139 | 143 |
|
140 | 144 | /** |
141 | | - * Returns {@code true} if the task failure was provided by the specified exception type, otherwise {@code false}. |
| 145 | + * Returns {@code true} if this failure's top-level error type matches the specified exception type, otherwise |
| 146 | + * {@code false}. |
| 147 | + * <p> |
| 148 | + * This method only inspects the error type reported by {@link #getErrorType()} on <em>this</em> instance; it |
| 149 | + * does <strong>not</strong> traverse the inner failure chain exposed by {@link #getInnerFailure()}. This matches |
| 150 | + * the behavior of {@code TaskFailureDetails.IsCausedBy} in the Durable Task .NET SDK. If you also want to test |
| 151 | + * wrapped causes, walk the chain explicitly, for example: |
| 152 | + * <pre>{@code |
| 153 | + * for (FailureDetails f = failureDetails; f != null; f = f.getInnerFailure()) { |
| 154 | + * if (f.isCausedBy(IllegalStateException.class)) { |
| 155 | + * // handle |
| 156 | + * break; |
| 157 | + * } |
| 158 | + * } |
| 159 | + * }</pre> |
142 | 160 | * <p> |
143 | | - * This method allows checking if a task failed due to a specific exception type by attempting to load the class |
144 | | - * specified in {@link #getErrorType()}. If the exception class cannot be loaded for any reason, this method will |
145 | | - * return {@code false}. Base types are supported by this method, as shown in the following example: |
| 161 | + * Internally the method attempts to load the class named by {@link #getErrorType()} via reflection. If the |
| 162 | + * exception class cannot be loaded for any reason (for example, it is not on the worker's classpath), this |
| 163 | + * method returns {@code false}. Base types are supported, as shown below: |
146 | 164 | * <pre>{@code |
147 | 165 | * boolean isRuntimeException = failureDetails.isCausedBy(RuntimeException.class); |
148 | 166 | * }</pre> |
149 | 167 | * |
150 | 168 | * @param exceptionClass the class representing the exception type to test |
151 | | - * @return {@code true} if the task failure was provided by the specified exception type, otherwise {@code false} |
| 169 | + * @return {@code true} if this failure's top-level error type is assignable to {@code exceptionClass}; |
| 170 | + * {@code false} otherwise |
152 | 171 | */ |
153 | 172 | public boolean isCausedBy(Class<? extends Exception> exceptionClass) { |
154 | 173 | String actualClassName = this.getErrorType(); |
@@ -211,8 +230,13 @@ private static FailureDetails fromExceptionRecursive( |
211 | 230 | if (provider != null && exception instanceof Exception) { |
212 | 231 | try { |
213 | 232 | properties = provider.getExceptionProperties((Exception) exception); |
214 | | - } catch (Exception ignored) { |
215 | | - // Don't let provider errors mask the original failure |
| 233 | + } catch (Exception providerException) { |
| 234 | + // Don't let provider errors mask the original failure, but log so the issue is diagnosable. |
| 235 | + logger.log( |
| 236 | + Level.WARNING, |
| 237 | + providerException, |
| 238 | + () -> "ExceptionPropertiesProvider threw while extracting properties for " |
| 239 | + + exception.getClass().getName() + "; ignoring provider output."); |
216 | 240 | } |
217 | 241 | } |
218 | 242 | return new FailureDetails( |
@@ -256,13 +280,13 @@ private static Object convertProtoValue(Value value) { |
256 | 280 | for (Value item : value.getListValue().getValuesList()) { |
257 | 281 | list.add(convertProtoValue(item)); |
258 | 282 | } |
259 | | - return list; |
| 283 | + return Collections.unmodifiableList(list); |
260 | 284 | case STRUCT_VALUE: |
261 | 285 | Map<String, Object> map = new HashMap<>(); |
262 | 286 | for (Map.Entry<String, Value> entry : value.getStructValue().getFieldsMap().entrySet()) { |
263 | 287 | map.put(entry.getKey(), convertProtoValue(entry.getValue())); |
264 | 288 | } |
265 | | - return map; |
| 289 | + return Collections.unmodifiableMap(map); |
266 | 290 | default: |
267 | 291 | return value.toString(); |
268 | 292 | } |
|
0 commit comments