admin.jmx.BatchMBeanExporter.java Source code

Java tutorial

Introduction

Here is the source code for admin.jmx.BatchMBeanExporter.java

Source

/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package admin.jmx;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import admin.service.JobService;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.context.SmartLifecycle;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedMetric;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
import org.springframework.jmx.support.MetricType;
import org.springframework.util.Assert;

@ManagedResource
public class BatchMBeanExporter extends MBeanExporter implements SmartLifecycle {

    private static final Log logger = LogFactory.getLog(BatchMBeanExporter.class);

    public static final String DEFAULT_DOMAIN = "org.springframework.batch";

    private volatile boolean autoStartup = true;

    private volatile int phase = 0;

    private volatile boolean running;

    private final Map<String, String> objectNameStaticProperties = new LinkedHashMap<String, String>();

    private final ReentrantLock lifecycleLock = new ReentrantLock();

    private Set<String> stepKeys = new HashSet<String>();

    private Set<String> jobKeys = new HashSet<String>();

    private final AnnotationJmxAttributeSource attributeSource = new AnnotationJmxAttributeSource();

    private JobService jobService;

    private String domain = DEFAULT_DOMAIN;

    private boolean registerSteps = true;

    private JobExecutionMetricsFactory jobExecutionMetricsFactory = new ExecutionMetricsFactory();

    private StepExecutionMetricsFactory stepExecutionMetricsFactory = new ExecutionMetricsFactory();

    public BatchMBeanExporter() {
        super();
        setAutodetect(false);
        setNamingStrategy(new MetadataNamingStrategy(attributeSource));
        setAssembler(new MetadataMBeanInfoAssembler(attributeSource));
    }

    /**
     * Flag to determine if any metrics at all should be exposed for step
     * executions (default true). Set to fals eto only expose job-level metrics.
     * 
     * @param registerSteps the flag to set
     */
    public void setRegisterSteps(boolean registerSteps) {
        this.registerSteps = registerSteps;
    }

    /**
     * The JMX domain to use for MBeans registered. Defaults to
     * <code>org.springframework.batch</code> (which is useful in SpringSource
     * HQ).
     * 
     * @param domain the domain name to set
     */
    public void setDefaultDomain(String domain) {
        this.domain = domain;
    }

    /**
     * Help method for extensions which need access to the default domain.
     * 
     * @return the default domain used to construct object names
     */
    protected String getDefaultDomain() {
        return this.domain;
    }

    public void setJobService(JobService jobService) {
        this.jobService = jobService;
    }

    /**
     * Static properties that will be added to all object names.
     * 
     * @param objectNameStaticProperties the objectNameStaticProperties to set
     */
    public void setObjectNameStaticProperties(Map<String, String> objectNameStaticProperties) {
        this.objectNameStaticProperties.putAll(objectNameStaticProperties);
    }

    /**
     * Factory for {@link JobExecutionMetrics}. Can be used to customize and
     * extend the metrics exposed.
     * 
     * @param stepExecutionMetricsFactory the {@link StepExecutionMetricsFactory} to set
     */
    public void setStepExecutionMetricsFactory(StepExecutionMetricsFactory stepExecutionMetricsFactory) {
        this.stepExecutionMetricsFactory = stepExecutionMetricsFactory;
    }

    /**
     * Factory for {@link StepExecutionMetrics}. Can be used to customize and
     * extend the metrics exposed.
     * 
     * @param jobExecutionMetricsFactory the {@link JobExecutionMetricsFactory} to set
     */
    public void setJobExecutionMetricsFactory(JobExecutionMetricsFactory jobExecutionMetricsFactory) {
        this.jobExecutionMetricsFactory = jobExecutionMetricsFactory;
    }

    @Override
    public void afterPropertiesSet() {
        Assert.state(jobService != null, "A JobService must be provided");
        super.afterPropertiesSet();
    }

    protected void registerBeans() {
        // Completely disable super class registration to avoid duplicates
    }

    private void registerSteps() {
        if (!registerSteps) {
            return;
        }
        for (String jobName : jobService.listJobs(0, Integer.MAX_VALUE)) {
            Collection<JobExecution> jobExecutions = Collections.emptySet();
            try {
                jobExecutions = jobService.listJobExecutionsForJob(jobName, 0, 1);
            } catch (NoSuchJobException e) {
                // do-nothing
                logger.error("Job listed but does not exist", e);
            }
            for (JobExecution jobExecution : jobExecutions) {
                for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
                    String stepName = stepExecution.getStepName();
                    String stepKey = String.format("%s/%s", jobName, stepName);
                    String beanKey = getBeanKeyForStepExecution(jobName, stepName);
                    if (!stepKeys.contains(stepKey)) {
                        stepKeys.add(stepKey);
                        logger.info("Registering step execution " + stepKey);
                        registerBeanNameOrInstance(
                                stepExecutionMetricsFactory.createMetricsForStep(jobName, stepName), beanKey);
                    }
                }
            }
        }
    }

    private void registerJobs() {
        for (String jobName : jobService.listJobs(0, Integer.MAX_VALUE)) {
            if (!jobKeys.contains(jobName)) {
                jobKeys.add(jobName);
                logger.info("Registering job execution " + jobName);
                registerBeanNameOrInstance(jobExecutionMetricsFactory.createMetricsForJob(jobName),
                        getBeanKeyForJobExecution(jobName));
            }
        }
    }

    /**
     * Encode the job name into an ObjectName in the form
     * <code>[domain]:type=JobExecution,name=[jobName]</code>.
     * 
     * @param jobName the name of the job
     * @return a String representation of an ObjectName
     */
    protected String getBeanKeyForJobExecution(String jobName) {
        jobName = escapeForObjectName(jobName);
        return String.format("%s:type=JobExecution,name=%s", domain, jobName) + getStaticNames();
    }

    /**
     * Encode the job and step name into an ObjectName in the form
     * <code>[domain]:type=JobExecution,name=[jobName],step=[stepName]</code>.
     * 
     * @param jobName the name of the job
     * @param stepName the name of the step
     * @return a String representation of an ObjectName
     */
    protected String getBeanKeyForStepExecution(String jobName, String stepName) {
        jobName = escapeForObjectName(jobName);
        stepName = escapeForObjectName(stepName);
        return String.format("%s:type=JobExecution,name=%s,step=%s", domain, jobName, stepName) + getStaticNames();
    }

    private String getStaticNames() {
        if (objectNameStaticProperties.isEmpty()) {
            return "";
        }
        StringBuilder builder = new StringBuilder();
        for (String key : objectNameStaticProperties.keySet()) {
            builder.append("," + key + "=" + objectNameStaticProperties.get(key));
        }
        return builder.toString();
    }

    private String escapeForObjectName(String value) {
        value = value.replaceAll(":", "@");
        value = value.replaceAll(",", ";");
        value = value.replaceAll("=", "~");
        return value;
    }

    @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Step Count")
    public int getStepCount() {
        registerSteps();
        return stepKeys.size();
    }

    @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Job Count")
    public int getJobCount() {
        registerJobs();
        return jobKeys.size();
    }

    @ManagedAttribute
    public String[] getJobNames() {
        return jobKeys.toArray(new String[0]);
    }

    @ManagedAttribute
    public String[] getStepNames() {
        return stepKeys.toArray(new String[0]);
    }

    @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Job Execution Failure Count")
    public int getJobExecutionFailureCount() {
        int count = 0;
        int start = 0;
        int pageSize = 100;
        Collection<JobExecution> jobExecutions;
        do {
            jobExecutions = jobService.listJobExecutions(start, pageSize);
            start += pageSize;
            for (JobExecution jobExecution : jobExecutions) {
                if (jobExecution.getStatus().isUnsuccessful()) {
                    count++;
                }
            }
        } while (!jobExecutions.isEmpty());
        return count;
    }

    @ManagedMetric(metricType = MetricType.COUNTER, displayName = "Job Execution Count")
    public int getJobExecutionCount() {
        return jobService.countJobExecutions();
    }

    public final boolean isAutoStartup() {
        return this.autoStartup;
    }

    public final int getPhase() {
        return this.phase;
    }

    public final boolean isRunning() {
        this.lifecycleLock.lock();
        try {
            return this.running;
        } finally {
            this.lifecycleLock.unlock();
        }
    }

    public final void start() {
        this.lifecycleLock.lock();
        try {
            if (!this.running) {
                this.doStart();
                this.running = true;
                if (logger.isInfoEnabled()) {
                    logger.info("started " + this);
                }
            }
        } finally {
            this.lifecycleLock.unlock();
        }
    }

    public final void stop() {
        this.lifecycleLock.lock();
        try {
            if (this.running) {
                this.doStop();
                this.running = false;
                if (logger.isInfoEnabled()) {
                    logger.info("stopped " + this);
                }
            }
        } finally {
            this.lifecycleLock.unlock();
        }
    }

    public final void stop(Runnable callback) {
        this.lifecycleLock.lock();
        try {
            this.stop();
            callback.run();
        } finally {
            this.lifecycleLock.unlock();
        }
    }

    protected void doStop() {
        unregisterBeans();
        jobKeys.clear();
        stepKeys.clear();
    }

    protected void doStart() {
        registerJobs();
        registerSteps();
    }

    private class ExecutionMetricsFactory implements JobExecutionMetricsFactory, StepExecutionMetricsFactory {

        public StepExecutionMetrics createMetricsForStep(String jobName, String stepName) {
            return new SimpleStepExecutionMetrics(jobService, jobName, stepName);
        }

        public JobExecutionMetrics createMetricsForJob(String jobName) {
            return new SimpleJobExecutionMetrics(jobService, jobName);
        }

    }

}