1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.springframework.batch.admin.jmx;
17
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashSet;
21 import java.util.LinkedHashMap;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.locks.ReentrantLock;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.springframework.batch.admin.service.JobService;
29 import org.springframework.batch.core.JobExecution;
30 import org.springframework.batch.core.StepExecution;
31 import org.springframework.batch.core.launch.NoSuchJobException;
32 import org.springframework.context.SmartLifecycle;
33 import org.springframework.jmx.export.MBeanExporter;
34 import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
35 import org.springframework.jmx.export.annotation.ManagedAttribute;
36 import org.springframework.jmx.export.annotation.ManagedMetric;
37 import org.springframework.jmx.export.annotation.ManagedResource;
38 import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
39 import org.springframework.jmx.export.naming.MetadataNamingStrategy;
40 import org.springframework.jmx.support.MetricType;
41 import org.springframework.util.Assert;
42
43 @ManagedResource
44 public class BatchMBeanExporter extends MBeanExporter implements SmartLifecycle {
45
46 private static final Log logger = LogFactory.getLog(BatchMBeanExporter.class);
47
48 public static final String DEFAULT_DOMAIN = "org.springframework.batch";
49
50 private volatile boolean autoStartup = true;
51
52 private volatile int phase = 0;
53
54 private volatile boolean running;
55
56 private final Map<String, String> objectNameStaticProperties = new LinkedHashMap<String, String>();
57
58 private final ReentrantLock lifecycleLock = new ReentrantLock();
59
60 private Set<String> stepKeys = new HashSet<String>();
61
62 private Set<String> jobKeys = new HashSet<String>();
63
64 private final AnnotationJmxAttributeSource attributeSource = new AnnotationJmxAttributeSource();
65
66 private JobService jobService;
67
68 private String domain = DEFAULT_DOMAIN;
69
70 private boolean registerSteps = true;
71
72 private JobExecutionMetricsFactory jobExecutionMetricsFactory = new ExecutionMetricsFactory();
73
74 private StepExecutionMetricsFactory stepExecutionMetricsFactory = new ExecutionMetricsFactory();
75
76 public BatchMBeanExporter() {
77 super();
78 setAutodetect(false);
79 setNamingStrategy(new MetadataNamingStrategy(attributeSource));
80 setAssembler(new MetadataMBeanInfoAssembler(attributeSource));
81 }
82
83
84
85
86
87
88
89 public void setRegisterSteps(boolean registerSteps) {
90 this.registerSteps = registerSteps;
91 }
92
93
94
95
96
97
98
99
100 public void setDefaultDomain(String domain) {
101 this.domain = domain;
102 }
103
104
105
106
107
108
109 protected String getDefaultDomain() {
110 return this.domain;
111 }
112
113 public void setJobService(JobService jobService) {
114 this.jobService = jobService;
115 }
116
117
118
119
120
121
122 public void setObjectNameStaticProperties(Map<String, String> objectNameStaticProperties) {
123 this.objectNameStaticProperties.putAll(objectNameStaticProperties);
124 }
125
126
127
128
129
130
131
132 public void setStepExecutionMetricsFactory(StepExecutionMetricsFactory stepExecutionMetricsFactory) {
133 this.stepExecutionMetricsFactory = stepExecutionMetricsFactory;
134 }
135
136
137
138
139
140
141
142 public void setJobExecutionMetricsFactory(JobExecutionMetricsFactory jobExecutionMetricsFactory) {
143 this.jobExecutionMetricsFactory = jobExecutionMetricsFactory;
144 }
145
146 @Override
147 public void afterPropertiesSet() {
148 Assert.state(jobService != null, "A JobService must be provided");
149 super.afterPropertiesSet();
150 }
151
152 protected void registerBeans() {
153
154 }
155
156 private void registerSteps() {
157 if (!registerSteps) {
158 return;
159 }
160 for (String jobName : jobService.listJobs(0, Integer.MAX_VALUE)) {
161 Collection<JobExecution> jobExecutions = Collections.emptySet();
162 try {
163 jobExecutions = jobService.listJobExecutionsForJob(jobName, 0, 1);
164 }
165 catch (NoSuchJobException e) {
166
167 logger.error("Job listed but does not exist", e);
168 }
169 for (JobExecution jobExecution : jobExecutions) {
170 for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
171 String stepName = stepExecution.getStepName();
172 String stepKey = String.format("%s/%s", jobName, stepName);
173 String beanKey = getBeanKeyForStepExecution(jobName, stepName);
174 if (!stepKeys.contains(stepKey)) {
175 stepKeys.add(stepKey);
176 logger.info("Registering step execution " + stepKey);
177 registerBeanNameOrInstance(stepExecutionMetricsFactory.createMetricsForStep(jobName, stepName),
178 beanKey);
179 }
180 }
181 }
182 }
183 }
184
185 private void registerJobs() {
186 for (String jobName : jobService.listJobs(0, Integer.MAX_VALUE)) {
187 if (!jobKeys.contains(jobName)) {
188 jobKeys.add(jobName);
189 logger.info("Registering job execution " + jobName);
190 registerBeanNameOrInstance(jobExecutionMetricsFactory.createMetricsForJob(jobName),
191 getBeanKeyForJobExecution(jobName));
192 }
193 }
194 }
195
196
197
198
199
200
201
202
203 protected String getBeanKeyForJobExecution(String jobName) {
204 jobName = escapeForObjectName(jobName);
205 return String.format("%s:type=JobExecution,name=%s", domain, jobName) + getStaticNames();
206 }
207
208
209
210
211
212
213
214
215
216 protected String getBeanKeyForStepExecution(String jobName, String stepName) {
217 jobName = escapeForObjectName(jobName);
218 stepName = escapeForObjectName(stepName);
219 return String.format("%s:type=JobExecution,name=%s,step=%s", domain, jobName, stepName) + getStaticNames();
220 }
221
222 private String getStaticNames() {
223 if (objectNameStaticProperties.isEmpty()) {
224 return "";
225 }
226 StringBuilder builder = new StringBuilder();
227 for (String key : objectNameStaticProperties.keySet()) {
228 builder.append("," + key + "=" + objectNameStaticProperties.get(key));
229 }
230 return builder.toString();
231 }
232
233 private String escapeForObjectName(String value) {
234 value = value.replaceAll(":", "@");
235 value = value.replaceAll(",", ";");
236 value = value.replaceAll("=", "~");
237 return value;
238 }
239
240 @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Step Count")
241 public int getStepCount() {
242 registerSteps();
243 return stepKeys.size();
244 }
245
246 @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Job Count")
247 public int getJobCount() {
248 registerJobs();
249 return jobKeys.size();
250 }
251
252 @ManagedAttribute
253 public String[] getJobNames() {
254 return jobKeys.toArray(new String[0]);
255 }
256
257 @ManagedAttribute
258 public String[] getStepNames() {
259 return stepKeys.toArray(new String[0]);
260 }
261
262 @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Job Execution Failure Count")
263 public int getJobExecutionFailureCount() {
264 int count = 0;
265 int start = 0;
266 int pageSize = 100;
267 Collection<JobExecution> jobExecutions;
268 do {
269 jobExecutions = jobService.listJobExecutions(start, pageSize);
270 start += pageSize;
271 for (JobExecution jobExecution : jobExecutions) {
272 if (jobExecution.getStatus().isUnsuccessful()) {
273 count++;
274 }
275 }
276 } while (!jobExecutions.isEmpty());
277 return count;
278 }
279
280 @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Job Execution Count")
281 public int getJobExecutionCount() {
282 return jobService.countJobExecutions();
283 }
284
285 public final boolean isAutoStartup() {
286 return this.autoStartup;
287 }
288
289 public final int getPhase() {
290 return this.phase;
291 }
292
293 public final boolean isRunning() {
294 this.lifecycleLock.lock();
295 try {
296 return this.running;
297 }
298 finally {
299 this.lifecycleLock.unlock();
300 }
301 }
302
303 public final void start() {
304 this.lifecycleLock.lock();
305 try {
306 if (!this.running) {
307 this.doStart();
308 this.running = true;
309 if (logger.isInfoEnabled()) {
310 logger.info("started " + this);
311 }
312 }
313 }
314 finally {
315 this.lifecycleLock.unlock();
316 }
317 }
318
319 public final void stop() {
320 this.lifecycleLock.lock();
321 try {
322 if (this.running) {
323 this.doStop();
324 this.running = false;
325 if (logger.isInfoEnabled()) {
326 logger.info("stopped " + this);
327 }
328 }
329 }
330 finally {
331 this.lifecycleLock.unlock();
332 }
333 }
334
335 public final void stop(Runnable callback) {
336 this.lifecycleLock.lock();
337 try {
338 this.stop();
339 callback.run();
340 }
341 finally {
342 this.lifecycleLock.unlock();
343 }
344 }
345
346 protected void doStop() {
347 unregisterBeans();
348 jobKeys.clear();
349 stepKeys.clear();
350 }
351
352 protected void doStart() {
353 registerJobs();
354 registerSteps();
355 }
356
357 private class ExecutionMetricsFactory implements JobExecutionMetricsFactory, StepExecutionMetricsFactory {
358
359 public StepExecutionMetrics createMetricsForStep(String jobName, String stepName) {
360 return new SimpleStepExecutionMetrics(jobService, jobName, stepName);
361 }
362
363 public JobExecutionMetrics createMetricsForJob(String jobName) {
364 return new SimpleJobExecutionMetrics(jobService, jobName);
365 }
366
367 }
368
369 }