1 | /* |
2 | * Copyright 2006-2007 the original author or authors. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | package org.springframework.batch.retry.policy; |
18 | |
19 | import java.util.HashMap; |
20 | import java.util.Iterator; |
21 | import java.util.Map; |
22 | |
23 | import org.springframework.batch.retry.RetryCallback; |
24 | import org.springframework.batch.retry.RetryContext; |
25 | import org.springframework.batch.retry.RetryPolicy; |
26 | import org.springframework.batch.retry.TerminatedRetryException; |
27 | import org.springframework.batch.retry.context.RetryContextSupport; |
28 | import org.springframework.batch.support.ExceptionClassifier; |
29 | import org.springframework.batch.support.ExceptionClassifierSupport; |
30 | import org.springframework.util.Assert; |
31 | |
32 | /** |
33 | * A {@link RetryPolicy} that dynamically adapts to one of a set of injected |
34 | * policies according to the value of the latest exception. |
35 | * |
36 | * @author Dave Syer |
37 | * |
38 | */ |
39 | public class ExceptionClassifierRetryPolicy extends AbstractStatelessRetryPolicy { |
40 | |
41 | private ExceptionClassifier exceptionClassifier = new ExceptionClassifierSupport(); |
42 | |
43 | private Map policyMap = new HashMap(); |
44 | |
45 | public ExceptionClassifierRetryPolicy() { |
46 | policyMap.put(ExceptionClassifierSupport.DEFAULT, new NeverRetryPolicy()); |
47 | } |
48 | |
49 | /** |
50 | * Setter for policy map. This property should not be changed dynamically - |
51 | * set it once, e.g. in configuration, and then don't change it during a |
52 | * running application. |
53 | * |
54 | * @param policyMap a map of String to {@link RetryPolicy} that will be |
55 | * applied to the result of the {@link ExceptionClassifier} to locate a |
56 | * policy. |
57 | */ |
58 | public void setPolicyMap(Map policyMap) { |
59 | this.policyMap = policyMap; |
60 | } |
61 | |
62 | /** |
63 | * Setter for an exception classifier. The classifier is responsible for |
64 | * translating exceptions to keys in the policy map. |
65 | * |
66 | * @param exceptionClassifier |
67 | */ |
68 | public void setExceptionClassifier(ExceptionClassifier exceptionClassifier) { |
69 | this.exceptionClassifier = exceptionClassifier; |
70 | } |
71 | |
72 | /** |
73 | * Delegate to the policy currently activated in the context. |
74 | * |
75 | * @see org.springframework.batch.retry.RetryPolicy#canRetry(org.springframework.batch.retry.RetryContext) |
76 | */ |
77 | public boolean canRetry(RetryContext context) { |
78 | RetryPolicy policy = (RetryPolicy) context; |
79 | return policy.canRetry(context); |
80 | } |
81 | |
82 | /** |
83 | * Delegate to the policy currently activated in the context. |
84 | * |
85 | * @see org.springframework.batch.retry.RetryPolicy#close(org.springframework.batch.retry.RetryContext) |
86 | */ |
87 | public void close(RetryContext context) { |
88 | RetryPolicy policy = (RetryPolicy) context; |
89 | policy.close(context); |
90 | } |
91 | |
92 | /** |
93 | * Create an active context that proxies a retry policy by chosing a target |
94 | * from the policy map. |
95 | * |
96 | * @see org.springframework.batch.retry.RetryPolicy#open(org.springframework.batch.retry.RetryCallback, RetryContext) |
97 | */ |
98 | public RetryContext open(RetryCallback callback, RetryContext parent) { |
99 | return new ExceptionClassifierRetryContext(parent, exceptionClassifier).open(callback, parent); |
100 | } |
101 | |
102 | /** |
103 | * Delegate to the policy currently activated in the context. |
104 | * |
105 | * @see org.springframework.batch.retry.RetryPolicy#registerThrowable(org.springframework.batch.retry.RetryContext, |
106 | * java.lang.Throwable) |
107 | */ |
108 | public void registerThrowable(RetryContext context, Throwable throwable) throws TerminatedRetryException { |
109 | RetryPolicy policy = (RetryPolicy) context; |
110 | policy.registerThrowable(context, throwable); |
111 | ((RetryContextSupport) context).registerThrowable(throwable); |
112 | } |
113 | |
114 | private class ExceptionClassifierRetryContext extends RetryContextSupport implements RetryPolicy { |
115 | |
116 | private ExceptionClassifier exceptionClassifier; |
117 | |
118 | // Dynamic: depends on the latest exception: |
119 | RetryPolicy policy; |
120 | |
121 | // Dynamic: depends on the policy: |
122 | RetryContext context; |
123 | |
124 | // The same for the life of the context: |
125 | RetryCallback callback; |
126 | |
127 | Map contexts = new HashMap(); |
128 | |
129 | public ExceptionClassifierRetryContext(RetryContext parent, ExceptionClassifier exceptionClassifier) { |
130 | super(parent); |
131 | this.exceptionClassifier = exceptionClassifier; |
132 | Object key = exceptionClassifier.getDefault(); |
133 | policy = getPolicy(key); |
134 | Assert.notNull(policy, "Could not locate default policy: key=[" + key + "]."); |
135 | } |
136 | |
137 | public boolean canRetry(RetryContext context) { |
138 | return policy.canRetry(this.context); |
139 | } |
140 | |
141 | public boolean shouldRethrow(RetryContext context) { |
142 | return policy.shouldRethrow(context); |
143 | } |
144 | |
145 | public void close(RetryContext context) { |
146 | // Only close those policies that have been used (opened): |
147 | for (Iterator iter = contexts.keySet().iterator(); iter.hasNext();) { |
148 | RetryPolicy policy = (RetryPolicy) iter.next(); |
149 | policy.close(getContext(policy)); |
150 | } |
151 | } |
152 | |
153 | public RetryContext open(RetryCallback callback, RetryContext parent) { |
154 | this.callback = callback; |
155 | return this; |
156 | } |
157 | |
158 | public void registerThrowable(RetryContext context, Throwable throwable) throws TerminatedRetryException { |
159 | policy = getPolicy(exceptionClassifier.classify(throwable)); |
160 | this.context = getContext(policy); |
161 | policy.registerThrowable(this.context, throwable); |
162 | } |
163 | |
164 | private RetryContext getContext(RetryPolicy policy) { |
165 | RetryContext context = (RetryContext) contexts.get(policy); |
166 | if (context == null) { |
167 | context = policy.open(callback, null); |
168 | contexts.put(policy, context); |
169 | } |
170 | return context; |
171 | } |
172 | |
173 | private RetryPolicy getPolicy(Object key) { |
174 | RetryPolicy result = (RetryPolicy) policyMap.get(key); |
175 | Assert.notNull(result, "Could not locate policy for key=[" + key + "]."); |
176 | return result; |
177 | } |
178 | |
179 | public Object handleRetryExhausted(RetryContext context) throws Exception { |
180 | // Not called... |
181 | throw new UnsupportedOperationException("Not supported - this code should be unreachable."); |
182 | } |
183 | |
184 | } |
185 | |
186 | } |