RESOLVED: BATCH-119: ExitStatus can't be used in JMX (or other remote interfaces).

http://opensource.atlassian.com/projects/spring/browse/BATCH-119
This commit is contained in:
dsyer
2007-08-29 13:24:24 +00:00
parent c74a726e6c
commit 4095702863
11 changed files with 492 additions and 113 deletions

View File

@@ -73,7 +73,7 @@ public abstract class AbstractJobLauncher implements JobLauncher,
* @param jobRuntimeInformationFactory
* the jobRuntimeInformationFactory to set
*/
public void setJobRuntimeInformationFactory(
public void setJobIdentifierFactory(
JobIdentifierFactory jobRuntimeInformationFactory) {
this.jobRuntimeInformationFactory = jobRuntimeInformationFactory;
}
@@ -103,14 +103,14 @@ public abstract class AbstractJobLauncher implements JobLauncher,
*
* @param batchContainer
*/
public void setBatchContainer(JobExecutorFacade batchContainer) {
public void setJobExecutorFacade(JobExecutorFacade batchContainer) {
this.batchContainer = batchContainer;
}
/**
* Check that mandatory properties are set.
*
* @see #setBatchContainer(JobExecutorFacade)
* @see #setJobExecutorFacade(JobExecutorFacade)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
@@ -203,7 +203,7 @@ public abstract class AbstractJobLauncher implements JobLauncher,
* @throws NoSuchJobConfigurationException
* if the job configuration cannot be located
*
* @see #setJobRuntimeInformationFactory(JobIdentifierFactory)
* @see #setJobIdentifierFactory(JobIdentifierFactory)
* @see org.springframework.context.Lifecycle#start()
*/
public ExitStatus run() throws NoSuchJobConfigurationException {

View File

@@ -58,7 +58,7 @@ public class SimpleJobLauncher implements JobLauncher {
/**
* Check that mandatory properties are set.
*
* @see #setBatchContainer(JobExecutorFacade)
* @see #setJobExecutorFacade(JobExecutorFacade)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
@@ -124,7 +124,7 @@ public class SimpleJobLauncher implements JobLauncher {
* default name is taken from the enclosed {@link JobConfiguration}.
* @throws NoSuchJobConfigurationException if the job configuration cannot be located
*
* @see #setJobRuntimeInformationFactory(JobIdentifierFactory)
* @see #setJobIdentifierFactory(JobIdentifierFactory)
* @see org.springframework.context.Lifecycle#start()
*/
public ExitStatus run(){

View File

@@ -53,10 +53,12 @@ import org.springframework.util.Assert;
* @author Dave Syer
* @since 2.1
*/
public class TaskExecutorJobLauncher extends AbstractJobLauncher implements ApplicationListener,
NotificationPublisherAware, ApplicationEventPublisherAware {
public class TaskExecutorJobLauncher extends AbstractJobLauncher implements
ApplicationListener, NotificationPublisherAware,
ApplicationEventPublisherAware {
private static final Log logger = LogFactory.getLog(TaskExecutorJobLauncher.class);
private static final Log logger = LogFactory
.getLog(TaskExecutorJobLauncher.class);
private TaskExecutor taskExecutor = new SyncTaskExecutor();
@@ -68,9 +70,11 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements Appl
/*
* (non-Javadoc)
*
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
*/
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
public void setApplicationEventPublisher(
ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@@ -78,7 +82,8 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements Appl
* Setter for the {@link TaskExecutor}. Defaults to a
* {@link SyncTaskExecutor}.
*
* @param taskExecutor the taskExecutor to set
* @param taskExecutor
* the taskExecutor to set
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
@@ -86,16 +91,19 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements Appl
/*
* (non-Javadoc)
*
* @see org.springframework.jmx.export.notification.NotificationPublisherAware#setNotificationPublisher(org.springframework.jmx.export.notification.NotificationPublisher)
*/
public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
public void setNotificationPublisher(
NotificationPublisher notificationPublisher) {
this.notificationPublisher = notificationPublisher;
}
/**
* Start the provided container using the task executor provided.
*
* @throws IllegalStateException if JobConfiguration is null.
* @throws IllegalStateException
* if JobConfiguration is null.
*/
protected ExitStatus doRun(final JobIdentifier runtimeInformation) {
@@ -105,14 +113,16 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements Appl
public void run() {
try {
batchContainer.start(runtimeInformation);
}
catch (NoSuchJobConfigurationException e) {
applicationEventPublisher.publishEvent(new RepeatOperationsApplicationEvent(runtimeInformation,
"No such job", RepeatOperationsApplicationEvent.ERROR));
logger.error("JobConfiguration could not be located inside Runnable for runtime information: ["
+ runtimeInformation + "]", e);
}
finally {
} catch (NoSuchJobConfigurationException e) {
applicationEventPublisher
.publishEvent(new RepeatOperationsApplicationEvent(
runtimeInformation, "No such job",
RepeatOperationsApplicationEvent.ERROR));
logger
.error(
"JobConfiguration could not be located inside Runnable for runtime information: ["
+ runtimeInformation + "]", e);
} finally {
unregister(runtimeInformation);
}
}
@@ -125,11 +135,13 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements Appl
/**
* Delegates to the underlying {@link JobExecutorFacade}. Does not wait for
* the jobs to stop (probably therefore returns immediately).
* @throws NoSuchJobExecutionException
*
* @throws NoSuchJobExecutionException
*
* @see org.springframework.context.Lifecycle#stop()
*/
protected void doStop(JobIdentifier runtimeInformation) throws NoSuchJobExecutionException {
protected void doStop(JobIdentifier runtimeInformation)
throws NoSuchJobExecutionException {
batchContainer.stop(runtimeInformation);
// TODO: wait for the jobs to stop?
}
@@ -146,9 +158,11 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements Appl
if (applicationEvent instanceof RepeatOperationsApplicationEvent) {
RepeatOperationsApplicationEvent event = (RepeatOperationsApplicationEvent) applicationEvent;
int type = event.getType();
if (type == RepeatOperationsApplicationEvent.OPEN || type == RepeatOperationsApplicationEvent.CLOSE
if (type == RepeatOperationsApplicationEvent.OPEN
|| type == RepeatOperationsApplicationEvent.CLOSE
|| type == RepeatOperationsApplicationEvent.ERROR) {
String message = event.getMessage() + "; source=" + event.getSource();
String message = event.getMessage() + "; source="
+ event.getSource();
logger.info(message);
publish(message);
}
@@ -164,8 +178,8 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements Appl
* for information purposes only.
*
* @return Properties representing the last {@link JobExecutionContext}
* objects passed up from the underlying execution. If there are no jobs
* running it will be empty.
* objects passed up from the underlying execution. If there are no
* jobs running it will be empty.
*/
public Properties getStatistics() {
if (batchContainer instanceof StatisticsProvider) {
@@ -180,8 +194,17 @@ public class TaskExecutorJobLauncher extends AbstractJobLauncher implements Appl
*/
private void publish(String message) {
if (notificationPublisher != null) {
notificationPublisher.sendNotification(new Notification("RepeatOperationsApplicationEvent", this,
notificationCount++, message));
Notification notification = new Notification(
"RepeatOperationsApplicationEvent", this,
notificationCount++, message);
/*
* We can't create a notification with a null source, but we can set
* it to null after creation(!). We want it to be null so that
* Spring will replace it automatically with the ObjectName (in
* ModelMBeanNotificationPublisher).
*/
notification.setSource(null);
notificationPublisher.sendNotification(notification);
}
}

View File

@@ -0,0 +1,55 @@
package org.springframework.batch.execution.bootstrap.support;
import org.springframework.batch.execution.bootstrap.JobLauncher;
import org.springframework.batch.repeat.ExitStatus;
/**
* Interface to expose for remote management of jobs. Similar to
* {@link JobLauncher}, but replaces {@link ExitStatus} with String in return
* types, so it can be inspected by remote clients like the jconsole from the
* JRE without any links to Spring Batch.
*
* @author Dave Syer
*
*/
public interface ExportedJobLauncher {
/**
* Launch a job and get back a representation of the {@link ExitStatus}
* returned by a {@link JobLauncher}. Normally the launch will be
* asynchronous, so the possible values of the return type are constrained
* (it will never be {@link ExitStatus#CONTINUABLE}).
*
* @return a representation of the {@link ExitStatus} returned by a
* {@link JobLauncher}.
*/
String run();
/**
* Launch a job configuration with the given name.
*
* @param name the name of the job to launch
* @return a representation of the {@link ExitStatus} returned by a
* {@link JobLauncher}.
*
* @see #run()
*/
String run(String name);
/**
* Stop all running jobs.
*
* @see JobLauncher#stop()
*/
void stop();
/**
* Enquire if any jobs are still running.
*
* @return true if any jobs are running.
*
* @see JobLauncher#isRunning()
*/
boolean isRunning();
}

View File

@@ -0,0 +1,79 @@
package org.springframework.batch.execution.bootstrap.support;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.beans.TypeConverter;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* A {@link MethodInterceptor} that can mask a mismatch between the target and
* proxy interfaces by converting the returned value to the correct type.
*
* @author Dave Syer
*
*/
public class TypeConverterMethodInterceptor implements MethodInterceptor {
// Get the default PropertyEditorRegistry free. TODO: make this
// configurable.
private TypeConverter typeConverter = new SimpleTypeConverter();
/**
* Public setter for the {@link TypeConverter} property. Defaults to a
* {@link SimpleTypeConverter}.
*
* @param typeConverter
* the typeConverter to set
*/
public void setTypeConverter(TypeConverter typeConverter) {
this.typeConverter = typeConverter;
}
/**
* Invoke the method with the same name and arguments on the target, but
* possibly with a different return type. If the return type doesn't match
* attempt to convert it.
*
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
// The method called on the proxy
Method invoked = invocation.getMethod();
// The corresponding method on the target if there is one...
Method method = ReflectionUtils.findMethod(invocation.getThis()
.getClass(), invoked.getName(), invoked.getParameterTypes());
// If there was no such method do nothing... TODO: throw Exception?
if (method == null) {
return null;
}
// Invoke the target method
Object result = ReflectionUtils.invokeMethod(method, invocation
.getThis(), invocation.getArguments());
if (result == null) {
return null;
}
// If the return type doesn't match, try and convert it
if (!ClassUtils.isAssignableValue(invoked.getReturnType(), result)) {
result = convert(result, invoked.getReturnType());
}
return result;
}
private Object convert(Object result, Class returnType) {
if (returnType.isAssignableFrom(String.class)) {
return result.toString();
}
return typeConverter.convertIfNecessary(result, returnType);
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
<p>
Support classes for use in bootstrap implementations or configurations.
</p>
</body>
</html>