CLOSED - issue BATCH-979: Insert Apache license header in Java sources (where missing)

This commit is contained in:
dsyer
2009-01-13 11:08:59 +00:00
parent 33d38611f4
commit df2ef02932
156 changed files with 6384 additions and 3957 deletions

View File

@@ -1,6 +1,19 @@
/**
*
/*
* Copyright 2006-2007 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 org.springframework.batch.core;
import java.io.Serializable;

View File

@@ -1,6 +1,19 @@
/**
*
/*
* Copyright 2006-2007 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 org.springframework.batch.core;
import java.io.Serializable;

View File

@@ -1,6 +1,19 @@
/**
*
/*
* Copyright 2006-2007 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 org.springframework.batch.core;
import java.util.Date;

View File

@@ -1,11 +1,27 @@
package org.springframework.batch.core;
/**
* Indicates the step's start limit has been exceeded.
*/
public class StartLimitExceededException extends RuntimeException {
public StartLimitExceededException(String message) {
super(message);
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core;
/**
* Indicates the step's start limit has been exceeded.
*/
public class StartLimitExceededException extends RuntimeException {
public StartLimitExceededException(String message) {
super(message);
}
}

View File

@@ -1,6 +1,19 @@
/**
*
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,6 +1,19 @@
/**
*
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,6 +1,19 @@
/**
*
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,6 +1,19 @@
/**
*
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.annotation;
import java.lang.annotation.ElementType;

View File

@@ -1,9 +1,25 @@
package org.springframework.batch.core.configuration.support;
import org.springframework.context.ConfigurableApplicationContext;
public interface ApplicationContextFactory {
ConfigurableApplicationContext createApplicationContext();
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.configuration.support;
import org.springframework.context.ConfigurableApplicationContext;
public interface ApplicationContextFactory {
ConfigurableApplicationContext createApplicationContext();
}

View File

@@ -1,68 +1,84 @@
package org.springframework.batch.core.configuration.support;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
public class ClassPathXmlApplicationContextFactory implements ApplicationContextFactory, ApplicationContextAware {
private ConfigurableApplicationContext parent;
private Resource path;
/**
* Setter for the path to the xml to load to create an
* {@link ApplicationContext}. Use imports to centralise the configuration in
* one file.
*
* @param path the resource path to the xml to load for the child context.
*/
public void setPath(Resource path) {
this.path = path;
}
/**
* Setter for the parent application context.
*
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
parent = (ConfigurableApplicationContext) applicationContext;
}
/**
* Creates an {@link ApplicationContext} from the provided path.
*
* @see ApplicationContextFactory#createApplicationContext()
*/
public ConfigurableApplicationContext createApplicationContext() {
if (path == null) {
return parent;
}
return new ResourceXmlApplicationContext(parent);
}
/**
* @author Dave Syer
*
*/
private final class ResourceXmlApplicationContext extends AbstractXmlApplicationContext {
/**
* @param parent
*/
private ResourceXmlApplicationContext(ApplicationContext parent) {
super(parent);
refresh();
}
protected Resource[] getConfigResources() {
return new Resource[] {path};
}
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.configuration.support;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
public class ClassPathXmlApplicationContextFactory implements ApplicationContextFactory, ApplicationContextAware {
private ConfigurableApplicationContext parent;
private Resource path;
/**
* Setter for the path to the xml to load to create an
* {@link ApplicationContext}. Use imports to centralise the configuration in
* one file.
*
* @param path the resource path to the xml to load for the child context.
*/
public void setPath(Resource path) {
this.path = path;
}
/**
* Setter for the parent application context.
*
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
parent = (ConfigurableApplicationContext) applicationContext;
}
/**
* Creates an {@link ApplicationContext} from the provided path.
*
* @see ApplicationContextFactory#createApplicationContext()
*/
public ConfigurableApplicationContext createApplicationContext() {
if (path == null) {
return parent;
}
return new ResourceXmlApplicationContext(parent);
}
/**
* @author Dave Syer
*
*/
private final class ResourceXmlApplicationContext extends AbstractXmlApplicationContext {
/**
* @param parent
*/
private ResourceXmlApplicationContext(ApplicationContext parent) {
super(parent);
refresh();
}
protected Resource[] getConfigResources() {
return new Resource[] {path};
}
}
}

View File

@@ -1,6 +1,19 @@
/**
*
/*
* Copyright 2006-2007 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 org.springframework.batch.core.configuration.support;
import java.util.Collection;

View File

@@ -1,57 +1,73 @@
package org.springframework.batch.core.configuration.support;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.configuration.JobFactory;
import org.springframework.batch.core.configuration.JobRegistry;
/**
* Generic service that can bind and unbind a {@link JobFactory} in a
* {@link JobRegistry}.
*
* @author Dave Syer
*
*/
public class JobFactoryRegistrationListener {
private Log logger = LogFactory.getLog(getClass());
private JobRegistry jobRegistry;
/**
* Public setter for a {@link JobRegistry} to use for all the bind and
* unbind events.
*
* @param jobRegistry {@link JobRegistry}
*/
public void setJobRegistry(JobRegistry jobRegistry) {
this.jobRegistry = jobRegistry;
}
/**
* Take the {@link JobFactory} provided and register it with the
* {@link JobRegistry}.
* @param jobFactory a {@link JobFactory}
* @param params not needed by this listener.
* @throws Exception if there is a problem
*/
public void bind(JobFactory jobFactory, Map<String, ?> params) throws Exception {
logger.info("Binding JobFactory: " + jobFactory.getJobName());
jobRegistry.register(jobFactory);
}
/**
* Take the {@link JobFactory} provided and unregister it with the
* {@link JobRegistry}.
* @param jobFactory a {@link JobFactory}
* @param params not needed by this listener.
* @throws Exception if there is a problem
*/
public void unbind(JobFactory jobFactory, Map<String, ?> params) throws Exception {
logger.info("Unbinding JobFactory: " + jobFactory.getJobName());
jobRegistry.unregister(jobFactory.getJobName());
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.configuration.support;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.configuration.JobFactory;
import org.springframework.batch.core.configuration.JobRegistry;
/**
* Generic service that can bind and unbind a {@link JobFactory} in a
* {@link JobRegistry}.
*
* @author Dave Syer
*
*/
public class JobFactoryRegistrationListener {
private Log logger = LogFactory.getLog(getClass());
private JobRegistry jobRegistry;
/**
* Public setter for a {@link JobRegistry} to use for all the bind and
* unbind events.
*
* @param jobRegistry {@link JobRegistry}
*/
public void setJobRegistry(JobRegistry jobRegistry) {
this.jobRegistry = jobRegistry;
}
/**
* Take the {@link JobFactory} provided and register it with the
* {@link JobRegistry}.
* @param jobFactory a {@link JobFactory}
* @param params not needed by this listener.
* @throws Exception if there is a problem
*/
public void bind(JobFactory jobFactory, Map<String, ?> params) throws Exception {
logger.info("Binding JobFactory: " + jobFactory.getJobName());
jobRegistry.register(jobFactory);
}
/**
* Take the {@link JobFactory} provided and unregister it with the
* {@link JobRegistry}.
* @param jobFactory a {@link JobFactory}
* @param params not needed by this listener.
* @throws Exception if there is a problem
*/
public void unbind(JobFactory jobFactory, Map<String, ?> params) throws Exception {
logger.info("Unbinding JobFactory: " + jobFactory.getJobName());
jobRegistry.unregister(jobFactory.getJobName());
}
}

View File

@@ -1,74 +1,90 @@
package org.springframework.batch.core.configuration.support;
import org.osgi.framework.BundleContext;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.osgi.context.BundleContextAware;
import org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext;
public class OsgiBundleXmlApplicationContextFactory implements BundleContextAware,
ApplicationContextFactory, ApplicationContextAware {
private BundleContext bundleContext;
private ApplicationContext parent;
private String path;
private String displayName;
/**
* @param path
* the resource path to the xml to load for the child context.
*/
public void setPath(String path) {
this.path = path;
}
/**
* @param displayName the display name for the application context created.
*/
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
/**
* Setter for the parent application context.
*
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
parent = applicationContext;
}
/**
* Stash the {@link BundleContext} for creating a job application context
* later.
*
* @see org.springframework.osgi.context.BundleContextAware#setBundleContext(org.osgi.framework.BundleContext)
*/
public void setBundleContext(BundleContext context) {
this.bundleContext = context;
}
/**
* Create an application context from the provided path, using the current
* OSGi {@link BundleContext} and the enclosing Spring
* {@link ApplicationContext} as a parent context.
*
* @see ApplicationContextFactory#createApplicationContext()
*/
public ConfigurableApplicationContext createApplicationContext() {
OsgiBundleXmlApplicationContext context = new OsgiBundleXmlApplicationContext(
new String[] { path }, parent);
String displayName = bundleContext.getBundle().getSymbolicName() + ":" + this.displayName;
context.setDisplayName(displayName);
context.setBundleContext(bundleContext);
context.refresh();
return context;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.configuration.support;
import org.osgi.framework.BundleContext;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.osgi.context.BundleContextAware;
import org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext;
public class OsgiBundleXmlApplicationContextFactory implements BundleContextAware,
ApplicationContextFactory, ApplicationContextAware {
private BundleContext bundleContext;
private ApplicationContext parent;
private String path;
private String displayName;
/**
* @param path
* the resource path to the xml to load for the child context.
*/
public void setPath(String path) {
this.path = path;
}
/**
* @param displayName the display name for the application context created.
*/
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
/**
* Setter for the parent application context.
*
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
parent = applicationContext;
}
/**
* Stash the {@link BundleContext} for creating a job application context
* later.
*
* @see org.springframework.osgi.context.BundleContextAware#setBundleContext(org.osgi.framework.BundleContext)
*/
public void setBundleContext(BundleContext context) {
this.bundleContext = context;
}
/**
* Create an application context from the provided path, using the current
* OSGi {@link BundleContext} and the enclosing Spring
* {@link ApplicationContext} as a parent context.
*
* @see ApplicationContextFactory#createApplicationContext()
*/
public ConfigurableApplicationContext createApplicationContext() {
OsgiBundleXmlApplicationContext context = new OsgiBundleXmlApplicationContext(
new String[] { path }, parent);
String displayName = bundleContext.getBundle().getSymbolicName() + ":" + this.displayName;
context.setDisplayName(displayName);
context.setBundleContext(bundleContext);
context.refresh();
return context;
}
}

View File

@@ -1,47 +1,63 @@
package org.springframework.batch.core.explore.support;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.beans.factory.FactoryBean;
/**
* A {@link FactoryBean} that automates the creation of a
* {@link SimpleJobExplorer}. Declares abstract methods for providing DAO
* object implementations.
*
* @see JobExplorerFactoryBean
* @see MapJobExplorerFactoryBean
*
* @author Dave Syer
*/
public abstract class AbstractJobExplorerFactoryBean implements FactoryBean {
/**
* @return fully configured {@link JobInstanceDao} implementation.
*/
protected abstract JobInstanceDao createJobInstanceDao() throws Exception;
/**
* @return fully configured {@link JobExecutionDao} implementation.
*/
protected abstract JobExecutionDao createJobExecutionDao() throws Exception;
protected abstract StepExecutionDao createStepExecutionDao() throws Exception;
/**
* The type of object to be returned from {@link #getObject()}.
*
* @return JobExplorer.class
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<JobExplorer> getObjectType() {
return JobExplorer.class;
}
public boolean isSingleton() {
return true;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.explore.support;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.beans.factory.FactoryBean;
/**
* A {@link FactoryBean} that automates the creation of a
* {@link SimpleJobExplorer}. Declares abstract methods for providing DAO
* object implementations.
*
* @see JobExplorerFactoryBean
* @see MapJobExplorerFactoryBean
*
* @author Dave Syer
*/
public abstract class AbstractJobExplorerFactoryBean implements FactoryBean {
/**
* @return fully configured {@link JobInstanceDao} implementation.
*/
protected abstract JobInstanceDao createJobInstanceDao() throws Exception;
/**
* @return fully configured {@link JobExecutionDao} implementation.
*/
protected abstract JobExecutionDao createJobExecutionDao() throws Exception;
protected abstract StepExecutionDao createStepExecutionDao() throws Exception;
/**
* The type of object to be returned from {@link #getObject()}.
*
* @return JobExplorer.class
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<JobExplorer> getObjectType() {
return JobExplorer.class;
}
public boolean isSingleton() {
return true;
}
}

View File

@@ -1,38 +1,54 @@
package org.springframework.batch.core.explore.support;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.beans.factory.FactoryBean;
/**
* A {@link FactoryBean} that automates the creation of a
* {@link SimpleJobExplorer} using in-memory DAO implementations.
*
* @author Dave Syer
*/
public class MapJobExplorerFactoryBean extends AbstractJobExplorerFactoryBean {
@Override
protected JobExecutionDao createJobExecutionDao() throws Exception {
return new MapJobExecutionDao();
}
@Override
protected JobInstanceDao createJobInstanceDao() throws Exception {
return new MapJobInstanceDao();
}
@Override
protected StepExecutionDao createStepExecutionDao() throws Exception {
return new MapStepExecutionDao();
}
public Object getObject() throws Exception {
return new SimpleJobExplorer(createJobInstanceDao(), createJobExecutionDao(), createStepExecutionDao());
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.explore.support;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.beans.factory.FactoryBean;
/**
* A {@link FactoryBean} that automates the creation of a
* {@link SimpleJobExplorer} using in-memory DAO implementations.
*
* @author Dave Syer
*/
public class MapJobExplorerFactoryBean extends AbstractJobExplorerFactoryBean {
@Override
protected JobExecutionDao createJobExecutionDao() throws Exception {
return new MapJobExecutionDao();
}
@Override
protected JobInstanceDao createJobInstanceDao() throws Exception {
return new MapJobInstanceDao();
}
@Override
protected StepExecutionDao createStepExecutionDao() throws Exception {
return new MapStepExecutionDao();
}
public Object getObject() throws Exception {
return new SimpleJobExplorer(createJobInstanceDao(), createJobExecutionDao(), createStepExecutionDao());
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.job.flow.support.state;
import org.springframework.batch.core.job.flow.FlowExecutor;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.job.flow.support.state;
import org.springframework.batch.core.BatchStatus;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.job.flow.support.state;
import org.springframework.batch.core.Step;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.launch.support;
/**

View File

@@ -1,38 +1,54 @@
package org.springframework.batch.core.partition;
import java.util.Collection;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
/**
* Interface defining the responsibilities of controlling the execution of a
* partitioned {@link StepExecution}. Implementations will need to create a
* partition with the {@link StepExecutionSplitter}, and then use an execution
* fabric (grid, etc.), to execute the partitioned step. The results of the
* executions can be returned raw from remote workers to be aggregated by the
* caller.
*
* @author Dave Syer
*
*/
public interface PartitionHandler {
/**
* Main entry point for {@link PartitionHandler} interface. The splitter
* creates all the executions that need to be farmed out, along with their
* input parameters (in the form of their {@link ExecutionContext}). The
* master step execution is used to identify the partition and group
* together the results logically.
*
* @param stepSplitter a strategy for generating a collection of
* {@link StepExecution} instances
* @param stepExecution the master step execution for the whole partition
* @return a collection of completed {@link StepExecution} instances
* @throws Exception if anything goes wrong. This allows implementations to
* be liberal and rely on the caller to translate an exception into a step
* failure as necessary.
*/
Collection<StepExecution> handle(StepExecutionSplitter stepSplitter, StepExecution stepExecution) throws Exception;
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.partition;
import java.util.Collection;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
/**
* Interface defining the responsibilities of controlling the execution of a
* partitioned {@link StepExecution}. Implementations will need to create a
* partition with the {@link StepExecutionSplitter}, and then use an execution
* fabric (grid, etc.), to execute the partitioned step. The results of the
* executions can be returned raw from remote workers to be aggregated by the
* caller.
*
* @author Dave Syer
*
*/
public interface PartitionHandler {
/**
* Main entry point for {@link PartitionHandler} interface. The splitter
* creates all the executions that need to be farmed out, along with their
* input parameters (in the form of their {@link ExecutionContext}). The
* master step execution is used to identify the partition and group
* together the results logically.
*
* @param stepSplitter a strategy for generating a collection of
* {@link StepExecution} instances
* @param stepExecution the master step execution for the whole partition
* @return a collection of completed {@link StepExecution} instances
* @throws Exception if anything goes wrong. This allows implementations to
* be liberal and rely on the caller to translate an exception into a step
* failure as necessary.
*/
Collection<StepExecution> handle(StepExecutionSplitter stepSplitter, StepExecution stepExecution) throws Exception;
}

View File

@@ -1,47 +1,63 @@
package org.springframework.batch.core.partition;
import java.util.Set;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.StepExecution;
/**
* Strategy interface for generating input contexts for a partitioned step
* execution independent from the fabric they are going to run on.
*
* @author Dave Syer
*
*/
public interface StepExecutionSplitter {
/**
* The name of the step configuration that will be executed remotely. Remote
* workers are going to execute a the same step for each execution context
* in the partition.
* @return the name of the step that will execute the business logic
*/
String getStepName();
/**
* Partition the provided {@link StepExecution} into a set of parallel
* executable instances with the same parent {@link JobExecution}. The grid
* size will be treated as a hint for the size of the collection to be
* returned. It may or may not correspond to the physical size of an
* execution grid.<br/>
* <br/>
*
* On a restart clients of the {@link StepExecutionSplitter} should expect
* it to reconstitute the state of the last failed execution and only return
* those executions that need to be restarted. Thus the grid size hint will
* be ignored on a restart.
*
* @param stepExecution the {@link StepExecution} to be partitioned.
* @param gridSize a hint for the splitter if the size of the grid is known
* @return a set of {@link StepExecution} instances for remote processing
*
* @throws JobExecutionException if the split cannot be made
*/
Set<StepExecution> split(StepExecution stepExecution, int gridSize) throws JobExecutionException;
/*
* Copyright 2006-2007 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 org.springframework.batch.core.partition;
import java.util.Set;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.StepExecution;
/**
* Strategy interface for generating input contexts for a partitioned step
* execution independent from the fabric they are going to run on.
*
* @author Dave Syer
*
*/
public interface StepExecutionSplitter {
/**
* The name of the step configuration that will be executed remotely. Remote
* workers are going to execute a the same step for each execution context
* in the partition.
* @return the name of the step that will execute the business logic
*/
String getStepName();
/**
* Partition the provided {@link StepExecution} into a set of parallel
* executable instances with the same parent {@link JobExecution}. The grid
* size will be treated as a hint for the size of the collection to be
* returned. It may or may not correspond to the physical size of an
* execution grid.<br/>
* <br/>
*
* On a restart clients of the {@link StepExecutionSplitter} should expect
* it to reconstitute the state of the last failed execution and only return
* those executions that need to be restarted. Thus the grid size hint will
* be ignored on a restart.
*
* @param stepExecution the {@link StepExecution} to be partitioned.
* @param gridSize a hint for the splitter if the size of the grid is known
* @return a set of {@link StepExecution} instances for remote processing
*
* @throws JobExecutionException if the split cannot be made
*/
Set<StepExecution> split(StepExecution stepExecution, int gridSize) throws JobExecutionException;
}

View File

@@ -1,86 +1,102 @@
package org.springframework.batch.core.partition.support;
import java.util.Collection;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.partition.PartitionHandler;
import org.springframework.batch.core.partition.StepExecutionSplitter;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.util.Assert;
/**
* Implementation of {@link Step} which partitions the execution and spreads the
* load using a {@link PartitionHandler}.
*
* @author Dave Syer
*
*/
public class PartitionStep extends AbstractStep {
private StepExecutionSplitter stepExecutionSplitter;
private PartitionHandler partitionHandler;
private StepExecutionAggregator aggregator = new StepExecutionAggregator();
/**
* Public setter for mandatory property {@link PartitionHandler}.
* @param partitionHandler the {@link PartitionHandler} to set
*/
public void setPartitionHandler(PartitionHandler partitionHandler) {
this.partitionHandler = partitionHandler;
}
/**
* Public setter for mandatory property {@link StepExecutionSplitter}.
* @param stepExecutionSplitter the {@link StepExecutionSplitter} to set
*/
public void setStepExecutionSplitter(StepExecutionSplitter stepExecutionSplitter) {
this.stepExecutionSplitter = stepExecutionSplitter;
}
/**
* Assert that mandatory properties are set (stepExecutionSplitter,
* partitionHandler) and delegate top superclass.
*
* @see AbstractStep#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(stepExecutionSplitter, "StepExecutionSplitter must be provided");
Assert.notNull(partitionHandler, "PartitionHandler must be provided");
super.afterPropertiesSet();
}
/**
* Delegate execution to the {@link PartitionHandler} provided. The
* {@link StepExecution} passed in here becomes the parent or master
* execution for the partition, summarising the status on exit of the
* logical grouping of work carried out by the {@link PartitionHandler}. The
* individual step executions and their input parameters (through
* {@link ExecutionContext}) for the partition elements are provided by the
* {@link StepExecutionSplitter}.
*
* @param stepExecution the master step execution for the partition
*
* @see Step#execute(StepExecution)
*/
@Override
protected void doExecute(StepExecution stepExecution) throws Exception {
// Wait for task completion and then aggregate the results
Collection<StepExecution> executions = partitionHandler.handle(stepExecutionSplitter, stepExecution);
stepExecution.upgradeStatus(BatchStatus.COMPLETED);
aggregator.aggregate(stepExecution, executions);
// If anything failed or had a problem we need to crap out
if (stepExecution.getStatus().isUnsuccessful()) {
throw new JobExecutionException("Partition handler returned an unsuccessful step");
}
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.partition.support;
import java.util.Collection;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.partition.PartitionHandler;
import org.springframework.batch.core.partition.StepExecutionSplitter;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.util.Assert;
/**
* Implementation of {@link Step} which partitions the execution and spreads the
* load using a {@link PartitionHandler}.
*
* @author Dave Syer
*
*/
public class PartitionStep extends AbstractStep {
private StepExecutionSplitter stepExecutionSplitter;
private PartitionHandler partitionHandler;
private StepExecutionAggregator aggregator = new StepExecutionAggregator();
/**
* Public setter for mandatory property {@link PartitionHandler}.
* @param partitionHandler the {@link PartitionHandler} to set
*/
public void setPartitionHandler(PartitionHandler partitionHandler) {
this.partitionHandler = partitionHandler;
}
/**
* Public setter for mandatory property {@link StepExecutionSplitter}.
* @param stepExecutionSplitter the {@link StepExecutionSplitter} to set
*/
public void setStepExecutionSplitter(StepExecutionSplitter stepExecutionSplitter) {
this.stepExecutionSplitter = stepExecutionSplitter;
}
/**
* Assert that mandatory properties are set (stepExecutionSplitter,
* partitionHandler) and delegate top superclass.
*
* @see AbstractStep#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(stepExecutionSplitter, "StepExecutionSplitter must be provided");
Assert.notNull(partitionHandler, "PartitionHandler must be provided");
super.afterPropertiesSet();
}
/**
* Delegate execution to the {@link PartitionHandler} provided. The
* {@link StepExecution} passed in here becomes the parent or master
* execution for the partition, summarising the status on exit of the
* logical grouping of work carried out by the {@link PartitionHandler}. The
* individual step executions and their input parameters (through
* {@link ExecutionContext}) for the partition elements are provided by the
* {@link StepExecutionSplitter}.
*
* @param stepExecution the master step execution for the partition
*
* @see Step#execute(StepExecution)
*/
@Override
protected void doExecute(StepExecution stepExecution) throws Exception {
// Wait for task completion and then aggregate the results
Collection<StepExecution> executions = partitionHandler.handle(stepExecutionSplitter, stepExecution);
stepExecution.upgradeStatus(BatchStatus.COMPLETED);
aggregator.aggregate(stepExecution, executions);
// If anything failed or had a problem we need to crap out
if (stepExecution.getStatus().isUnsuccessful()) {
throw new JobExecutionException("Partition handler returned an unsuccessful step");
}
}
}

View File

@@ -1,29 +1,45 @@
package org.springframework.batch.core.partition.support;
import java.util.Map;
import org.springframework.batch.item.ExecutionContext;
/**
* Central strategy interface for creating input parameters for a partitioned
* step in the form of {@link ExecutionContext} instances. The usual aim is to
* create a set of distinct input values, e.g. a set of non-overlapping primary
* key ranges, or a set of unique filenames.
*
* @author Dave Syer
*
*/
public interface Partitioner {
/**
* Create a set of distinct {@link ExecutionContext} instances together with
* a unique identifier for each one. The identifiers should be short,
* mnemonic values, and only have to be unique within the return value (e.g.
* use an incrementer).
*
* @param gridSize the size of the map to return
* @return a map from identifier to input parameters
*/
Map<String, ExecutionContext> partition(int gridSize);
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.partition.support;
import java.util.Map;
import org.springframework.batch.item.ExecutionContext;
/**
* Central strategy interface for creating input parameters for a partitioned
* step in the form of {@link ExecutionContext} instances. The usual aim is to
* create a set of distinct input values, e.g. a set of non-overlapping primary
* key ranges, or a set of unique filenames.
*
* @author Dave Syer
*
*/
public interface Partitioner {
/**
* Create a set of distinct {@link ExecutionContext} instances together with
* a unique identifier for each one. The identifiers should be short,
* mnemonic values, and only have to be unique within the return value (e.g.
* use an incrementer).
*
* @param gridSize the size of the map to return
* @return a map from identifier to input parameters
*/
Map<String, ExecutionContext> partition(int gridSize);
}

View File

@@ -1,29 +1,45 @@
package org.springframework.batch.core.partition.support;
import java.util.HashMap;
import java.util.Map;
import org.springframework.batch.item.ExecutionContext;
/**
* Simplest possible implementation of {@link Partitioner}. Just creates a set
* of empty {@link ExecutionContext} instances, and labels them as
* <code>{partition0, partition1, ..., partitionN}</code>, where <code>N</code> is the grid
* size.
*
* @author Dave Syer
*
*/
public class SimplePartitioner implements Partitioner {
private static final String PARTITION_KEY = "partition";
public Map<String, ExecutionContext> partition(int gridSize) {
Map<String, ExecutionContext> map = new HashMap<String, ExecutionContext>(gridSize);
for (int i = 0; i < gridSize; i++) {
map.put(PARTITION_KEY + i, new ExecutionContext());
}
return map;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.partition.support;
import java.util.HashMap;
import java.util.Map;
import org.springframework.batch.item.ExecutionContext;
/**
* Simplest possible implementation of {@link Partitioner}. Just creates a set
* of empty {@link ExecutionContext} instances, and labels them as
* <code>{partition0, partition1, ..., partitionN}</code>, where <code>N</code> is the grid
* size.
*
* @author Dave Syer
*
*/
public class SimplePartitioner implements Partitioner {
private static final String PARTITION_KEY = "partition";
public Map<String, ExecutionContext> partition(int gridSize) {
Map<String, ExecutionContext> map = new HashMap<String, ExecutionContext>(gridSize);
for (int i = 0; i < gridSize; i++) {
map.put(PARTITION_KEY + i, new ExecutionContext());
}
return map;
}
}

View File

@@ -1,159 +1,175 @@
package org.springframework.batch.core.partition.support;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.partition.StepExecutionSplitter;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.item.ExecutionContext;
/**
* Generic implementation of {@link StepExecutionSplitter} that delegates to a
* {@link Partitioner} to generate {@link ExecutionContext} instances. Takes
* care of restartability and identifying the step executions from previous runs
* of the same job. The generated {@link StepExecution} instances have names
* that identify them uniquely in the partition. The name is constructed from a
* base (name of the target step) plus a suffix taken from the
* {@link Partitioner} identifiers, separated by a colon, e.g.
* <code>{step1:partition0, step1:partition1, ...}</code>.
*
* @author Dave Syer
*
*/
public class SimpleStepExecutionSplitter implements StepExecutionSplitter {
private static final String STEP_NAME_SEPARATOR = ":";
private final String stepName;
private final Partitioner partitioner;
private final Step step;
private final JobRepository jobRepository;
public SimpleStepExecutionSplitter(JobRepository jobRepository, Step step) {
this(jobRepository, step, new SimplePartitioner());
}
/**
* Construct a {@link SimpleStepExecutionSplitter} from its mandatory
* properties.
*
* @param jobRepository the {@link JobRepository}
* @param step the target step (a local version of it)
* @param partitioner a {@link Partitioner} to use for generating input
* parameters
*/
public SimpleStepExecutionSplitter(JobRepository jobRepository, Step step, Partitioner partitioner) {
this.jobRepository = jobRepository;
this.step = step;
this.partitioner = partitioner;
this.stepName = step.getName();
}
/**
* @see StepExecutionSplitter#getStepName()
*/
public String getStepName() {
return this.stepName;
}
/**
* @see StepExecutionSplitter#split(StepExecution, int)
*/
public Set<StepExecution> split(StepExecution stepExecution, int gridSize) throws JobExecutionException {
JobExecution jobExecution = stepExecution.getJobExecution();
// If this is a restart we must retain the same grid size, ignoring the
// one passed in...
int splitSize = getSplitSize(stepExecution, gridSize);
Map<String, ExecutionContext> contexts = partitioner.partition(splitSize);
Set<StepExecution> set = new HashSet<StepExecution>(contexts.size());
for (Entry<String, ExecutionContext> context : contexts.entrySet()) {
// Make the step execution name unique and repeatable
String stepName = this.stepName + STEP_NAME_SEPARATOR + context.getKey();
StepExecution currentStepExecution = jobExecution.createStepExecution(stepName);
boolean startable = getStartable(currentStepExecution, context.getValue());
if (startable) {
jobRepository.add(currentStepExecution);
set.add(currentStepExecution);
}
}
return set;
}
private int getSplitSize(StepExecution stepExecution, int gridSize) {
ExecutionContext context = stepExecution.getExecutionContext();
String key = SimpleStepExecutionSplitter.class.getSimpleName() + ".GRID_SIZE";
int result = (int) context.getLong(key, gridSize);
context.putLong(key, result);
if (context.isDirty()) {
jobRepository.updateExecutionContext(stepExecution);
}
return result;
}
private boolean getStartable(StepExecution stepExecution, ExecutionContext context) throws JobExecutionException {
JobInstance jobInstance = stepExecution.getJobExecution().getJobInstance();
String stepName = stepExecution.getStepName();
StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, stepName);
boolean isRestart = (lastStepExecution != null && lastStepExecution.getStatus() != BatchStatus.COMPLETED) ? true
: false;
if (isRestart) {
stepExecution.setExecutionContext(lastStepExecution.getExecutionContext());
}
else {
stepExecution.setExecutionContext(context);
}
return shouldStart(step, lastStepExecution) || isRestart;
}
private boolean shouldStart(Step step, StepExecution lastStepExecution) throws JobExecutionException {
if (lastStepExecution == null) {
return true;
}
BatchStatus stepStatus = lastStepExecution.getStatus();
if (stepStatus == BatchStatus.UNKNOWN) {
throw new JobExecutionException("Cannot restart step from UNKNOWN status. "
+ "The last execution ended with a failure that could not be rolled back, "
+ "so it may be dangerous to proceed. " + "Manual intervention is probably necessary.");
}
if (stepStatus == BatchStatus.COMPLETED && step.isAllowStartIfComplete() == false) {
// step is complete, false should be returned, indicating that the
// step should not be started
return false;
}
return true;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.partition.support;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionException;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.partition.StepExecutionSplitter;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.item.ExecutionContext;
/**
* Generic implementation of {@link StepExecutionSplitter} that delegates to a
* {@link Partitioner} to generate {@link ExecutionContext} instances. Takes
* care of restartability and identifying the step executions from previous runs
* of the same job. The generated {@link StepExecution} instances have names
* that identify them uniquely in the partition. The name is constructed from a
* base (name of the target step) plus a suffix taken from the
* {@link Partitioner} identifiers, separated by a colon, e.g.
* <code>{step1:partition0, step1:partition1, ...}</code>.
*
* @author Dave Syer
*
*/
public class SimpleStepExecutionSplitter implements StepExecutionSplitter {
private static final String STEP_NAME_SEPARATOR = ":";
private final String stepName;
private final Partitioner partitioner;
private final Step step;
private final JobRepository jobRepository;
public SimpleStepExecutionSplitter(JobRepository jobRepository, Step step) {
this(jobRepository, step, new SimplePartitioner());
}
/**
* Construct a {@link SimpleStepExecutionSplitter} from its mandatory
* properties.
*
* @param jobRepository the {@link JobRepository}
* @param step the target step (a local version of it)
* @param partitioner a {@link Partitioner} to use for generating input
* parameters
*/
public SimpleStepExecutionSplitter(JobRepository jobRepository, Step step, Partitioner partitioner) {
this.jobRepository = jobRepository;
this.step = step;
this.partitioner = partitioner;
this.stepName = step.getName();
}
/**
* @see StepExecutionSplitter#getStepName()
*/
public String getStepName() {
return this.stepName;
}
/**
* @see StepExecutionSplitter#split(StepExecution, int)
*/
public Set<StepExecution> split(StepExecution stepExecution, int gridSize) throws JobExecutionException {
JobExecution jobExecution = stepExecution.getJobExecution();
// If this is a restart we must retain the same grid size, ignoring the
// one passed in...
int splitSize = getSplitSize(stepExecution, gridSize);
Map<String, ExecutionContext> contexts = partitioner.partition(splitSize);
Set<StepExecution> set = new HashSet<StepExecution>(contexts.size());
for (Entry<String, ExecutionContext> context : contexts.entrySet()) {
// Make the step execution name unique and repeatable
String stepName = this.stepName + STEP_NAME_SEPARATOR + context.getKey();
StepExecution currentStepExecution = jobExecution.createStepExecution(stepName);
boolean startable = getStartable(currentStepExecution, context.getValue());
if (startable) {
jobRepository.add(currentStepExecution);
set.add(currentStepExecution);
}
}
return set;
}
private int getSplitSize(StepExecution stepExecution, int gridSize) {
ExecutionContext context = stepExecution.getExecutionContext();
String key = SimpleStepExecutionSplitter.class.getSimpleName() + ".GRID_SIZE";
int result = (int) context.getLong(key, gridSize);
context.putLong(key, result);
if (context.isDirty()) {
jobRepository.updateExecutionContext(stepExecution);
}
return result;
}
private boolean getStartable(StepExecution stepExecution, ExecutionContext context) throws JobExecutionException {
JobInstance jobInstance = stepExecution.getJobExecution().getJobInstance();
String stepName = stepExecution.getStepName();
StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobInstance, stepName);
boolean isRestart = (lastStepExecution != null && lastStepExecution.getStatus() != BatchStatus.COMPLETED) ? true
: false;
if (isRestart) {
stepExecution.setExecutionContext(lastStepExecution.getExecutionContext());
}
else {
stepExecution.setExecutionContext(context);
}
return shouldStart(step, lastStepExecution) || isRestart;
}
private boolean shouldStart(Step step, StepExecution lastStepExecution) throws JobExecutionException {
if (lastStepExecution == null) {
return true;
}
BatchStatus stepStatus = lastStepExecution.getStatus();
if (stepStatus == BatchStatus.UNKNOWN) {
throw new JobExecutionException("Cannot restart step from UNKNOWN status. "
+ "The last execution ended with a failure that could not be rolled back, "
+ "so it may be dangerous to proceed. " + "Manual intervention is probably necessary.");
}
if (stepStatus == BatchStatus.COMPLETED && step.isAllowStartIfComplete() == false) {
// step is complete, false should be returned, indicating that the
// step should not be started
return false;
}
return true;
}
}

View File

@@ -1,49 +1,65 @@
package org.springframework.batch.core.partition.support;
import java.util.Collection;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.util.Assert;
/**
* Convenience class for aggregating a set of {@link StepExecution} instances
* into a single result.
*
* @author Dave Syer
*
*/
public class StepExecutionAggregator {
/**
* Take the inputs and aggregate certain fields, putting the aggregates into
* the result. The aggregated fields are
* <ul>
* <li>status - choosing the highest value using {@link BatchStatus#max(BatchStatus, BatchStatus)}</li>
* <li>exitStatus - using {@link ExitStatus#and(ExitStatus)}</li>
* <li>commitCount, rollbackCount, etc. - by arithmetic sum</li>
* </ul>
*
* @param result the result to overwrite
* @param executions the inputs
*/
public void aggregate(StepExecution result, Collection<StepExecution> executions) {
Assert.notNull(result, "To aggregate into a result it must be non-null.");
if (executions == null || executions.isEmpty()) {
throw new IllegalArgumentException("Cannot aggregate empty or null executions: " + executions);
}
for (StepExecution stepExecution : executions) {
BatchStatus status = stepExecution.getStatus();
result.setStatus(BatchStatus.max(result.getStatus(), status));
result.setExitStatus(result.getExitStatus().and(stepExecution.getExitStatus()));
result.setCommitCount(result.getCommitCount() + stepExecution.getCommitCount());
result.setRollbackCount(result.getRollbackCount() + stepExecution.getRollbackCount());
result.setReadCount(result.getReadCount() + stepExecution.getReadCount());
result.setReadSkipCount(result.getReadSkipCount() + stepExecution.getReadSkipCount());
result.setWriteCount(result.getWriteCount() + stepExecution.getWriteCount());
result.setWriteSkipCount(result.getWriteSkipCount() + stepExecution.getWriteSkipCount());
}
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.partition.support;
import java.util.Collection;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.util.Assert;
/**
* Convenience class for aggregating a set of {@link StepExecution} instances
* into a single result.
*
* @author Dave Syer
*
*/
public class StepExecutionAggregator {
/**
* Take the inputs and aggregate certain fields, putting the aggregates into
* the result. The aggregated fields are
* <ul>
* <li>status - choosing the highest value using {@link BatchStatus#max(BatchStatus, BatchStatus)}</li>
* <li>exitStatus - using {@link ExitStatus#and(ExitStatus)}</li>
* <li>commitCount, rollbackCount, etc. - by arithmetic sum</li>
* </ul>
*
* @param result the result to overwrite
* @param executions the inputs
*/
public void aggregate(StepExecution result, Collection<StepExecution> executions) {
Assert.notNull(result, "To aggregate into a result it must be non-null.");
if (executions == null || executions.isEmpty()) {
throw new IllegalArgumentException("Cannot aggregate empty or null executions: " + executions);
}
for (StepExecution stepExecution : executions) {
BatchStatus status = stepExecution.getStatus();
result.setStatus(BatchStatus.max(result.getStatus(), status));
result.setExitStatus(result.getExitStatus().and(stepExecution.getExitStatus()));
result.setCommitCount(result.getCommitCount() + stepExecution.getCommitCount());
result.setRollbackCount(result.getRollbackCount() + stepExecution.getRollbackCount());
result.setReadCount(result.getReadCount() + stepExecution.getReadCount());
result.setReadSkipCount(result.getReadSkipCount() + stepExecution.getReadSkipCount());
result.setWriteCount(result.getWriteCount() + stepExecution.getWriteCount());
result.setWriteSkipCount(result.getWriteSkipCount() + stepExecution.getWriteSkipCount());
}
}
}

View File

@@ -1,92 +1,108 @@
package org.springframework.batch.core.partition.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.partition.PartitionHandler;
import org.springframework.batch.core.partition.StepExecutionSplitter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.util.Assert;
public class TaskExecutorPartitionHandler implements PartitionHandler, InitializingBean {
private int gridSize = 1;
private TaskExecutor taskExecutor = new SyncTaskExecutor();
private Step step;
public void afterPropertiesSet() throws Exception {
Assert.notNull(step, "A Step must be provided.");
}
public void setGridSize(int gridSize) {
this.gridSize = gridSize;
}
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void setStep(Step step) {
this.step = step;
}
/**
* @see PartitionHandler#handle(StepExecutionSplitter, StepExecution)
*/
public Collection<StepExecution> handle(StepExecutionSplitter stepExecutionSplitter,
StepExecution masterStepExecution) throws Exception {
Set<Future<StepExecution>> tasks = new HashSet<Future<StepExecution>>(gridSize);
Collection<StepExecution> result = new ArrayList<StepExecution>();
for (final StepExecution stepExecution : stepExecutionSplitter.split(masterStepExecution, gridSize)) {
final FutureTask<StepExecution> task = new FutureTask<StepExecution>(new Callable<StepExecution>() {
public StepExecution call() throws Exception {
step.execute(stepExecution);
return stepExecution;
}
});
try {
taskExecutor.execute(task);
tasks.add(task);
}
catch (TaskRejectedException e) {
// couldn't execute one of the tasks
ExitStatus exitStatus = ExitStatus.FAILED
.addExitDescription("TaskExecutor rejected the task for this step.");
/*
* Set the status in case the caller is tracking it through the
* JobExecution.
*/
stepExecution.setStatus(BatchStatus.FAILED);
stepExecution.setExitStatus(exitStatus);
result.add(stepExecution);
}
}
for (Future<StepExecution> task : tasks) {
result.add(task.get());
}
return result;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.partition.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.partition.PartitionHandler;
import org.springframework.batch.core.partition.StepExecutionSplitter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.SyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.core.task.TaskRejectedException;
import org.springframework.util.Assert;
public class TaskExecutorPartitionHandler implements PartitionHandler, InitializingBean {
private int gridSize = 1;
private TaskExecutor taskExecutor = new SyncTaskExecutor();
private Step step;
public void afterPropertiesSet() throws Exception {
Assert.notNull(step, "A Step must be provided.");
}
public void setGridSize(int gridSize) {
this.gridSize = gridSize;
}
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
public void setStep(Step step) {
this.step = step;
}
/**
* @see PartitionHandler#handle(StepExecutionSplitter, StepExecution)
*/
public Collection<StepExecution> handle(StepExecutionSplitter stepExecutionSplitter,
StepExecution masterStepExecution) throws Exception {
Set<Future<StepExecution>> tasks = new HashSet<Future<StepExecution>>(gridSize);
Collection<StepExecution> result = new ArrayList<StepExecution>();
for (final StepExecution stepExecution : stepExecutionSplitter.split(masterStepExecution, gridSize)) {
final FutureTask<StepExecution> task = new FutureTask<StepExecution>(new Callable<StepExecution>() {
public StepExecution call() throws Exception {
step.execute(stepExecution);
return stepExecution;
}
});
try {
taskExecutor.execute(task);
tasks.add(task);
}
catch (TaskRejectedException e) {
// couldn't execute one of the tasks
ExitStatus exitStatus = ExitStatus.FAILED
.addExitDescription("TaskExecutor rejected the task for this step.");
/*
* Set the status in case the caller is tracking it through the
* JobExecution.
*/
stepExecution.setStatus(BatchStatus.FAILED);
stepExecution.setExitStatus(exitStatus);
result.add(stepExecution);
}
}
for (Future<StepExecution> task : tasks) {
result.add(task.get());
}
return result;
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import org.springframework.beans.factory.InitializingBean;

View File

@@ -1,37 +1,53 @@
package org.springframework.batch.core.repository.dao;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
/**
* DAO interface for persisting and retrieving {@link ExecutionContext}s.
*
* @author Robert Kasanicky
*/
public interface ExecutionContextDao {
/**
* @param jobExecution
* @return execution context associated with the given jobExecution
*/
ExecutionContext getExecutionContext(JobExecution jobExecution);
/**
* @param stepExecution
* @return execution context associated with the given stepExecution
*/
ExecutionContext getExecutionContext(StepExecution stepExecution);
/**
* Persist the execution context associated with the given jobExecution
* @param jobExecution
*/
void persistExecutionContext(final JobExecution jobExecution);
/**
* Persist the execution context associated with the given stepExecution
* @param stepExecution
*/
void persistExecutionContext(final StepExecution stepExecution);
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
/**
* DAO interface for persisting and retrieving {@link ExecutionContext}s.
*
* @author Robert Kasanicky
*/
public interface ExecutionContextDao {
/**
* @param jobExecution
* @return execution context associated with the given jobExecution
*/
ExecutionContext getExecutionContext(JobExecution jobExecution);
/**
* @param stepExecution
* @return execution context associated with the given stepExecution
*/
ExecutionContext getExecutionContext(StepExecution stepExecution);
/**
* Persist the execution context associated with the given jobExecution
* @param jobExecution
*/
void persistExecutionContext(final JobExecution jobExecution);
/**
* Persist the execution context associated with the given stepExecution
* @param stepExecution
*/
void persistExecutionContext(final StepExecution stepExecution);
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import java.sql.ResultSet;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import java.sql.ResultSet;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import java.sql.ResultSet;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import java.util.List;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import java.util.List;

View File

@@ -1,44 +1,60 @@
package org.springframework.batch.core.repository.dao;
import java.util.Map;
import org.apache.commons.lang.SerializationUtils;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
public class MapExecutionContextDao implements ExecutionContextDao {
private static Map<Long, ExecutionContext> contextsByStepExecutionId = TransactionAwareProxyFactory.createTransactionalMap();
private static Map<Long, ExecutionContext> contextsByJobExecutionId = TransactionAwareProxyFactory.createTransactionalMap();
public static void clear() {
contextsByJobExecutionId.clear();
contextsByStepExecutionId.clear();
}
private static ExecutionContext copy(ExecutionContext original) {
return (ExecutionContext) SerializationUtils.deserialize(SerializationUtils.serialize(original));
}
public ExecutionContext getExecutionContext(StepExecution stepExecution) {
return copy(contextsByStepExecutionId.get(stepExecution.getId()));
}
public void persistExecutionContext(StepExecution stepExecution) {
contextsByStepExecutionId.put(stepExecution.getId(), copy(stepExecution.getExecutionContext()));
}
public ExecutionContext getExecutionContext(JobExecution jobExecution) {
return copy(contextsByJobExecutionId.get(jobExecution.getId()));
}
public void persistExecutionContext(JobExecution jobExecution) {
contextsByJobExecutionId.put(jobExecution.getId(), copy(jobExecution.getExecutionContext()));
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import java.util.Map;
import org.apache.commons.lang.SerializationUtils;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.support.transaction.TransactionAwareProxyFactory;
public class MapExecutionContextDao implements ExecutionContextDao {
private static Map<Long, ExecutionContext> contextsByStepExecutionId = TransactionAwareProxyFactory.createTransactionalMap();
private static Map<Long, ExecutionContext> contextsByJobExecutionId = TransactionAwareProxyFactory.createTransactionalMap();
public static void clear() {
contextsByJobExecutionId.clear();
contextsByStepExecutionId.clear();
}
private static ExecutionContext copy(ExecutionContext original) {
return (ExecutionContext) SerializationUtils.deserialize(SerializationUtils.serialize(original));
}
public ExecutionContext getExecutionContext(StepExecution stepExecution) {
return copy(contextsByStepExecutionId.get(stepExecution.getId()));
}
public void persistExecutionContext(StepExecution stepExecution) {
contextsByStepExecutionId.put(stepExecution.getId(), copy(stepExecution.getExecutionContext()));
}
public ExecutionContext getExecutionContext(JobExecution jobExecution) {
return copy(contextsByJobExecutionId.get(jobExecution.getId()));
}
public void persistExecutionContext(JobExecution jobExecution) {
contextsByJobExecutionId.put(jobExecution.getId(), copy(jobExecution.getExecutionContext()));
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import java.util.ArrayList;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import java.util.ArrayList;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.dao;
import java.util.List;

View File

@@ -1,134 +1,150 @@
package org.springframework.batch.core.repository.support;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.batch.support.PropertiesConverter;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.Assert;
/**
* A {@link FactoryBean} that automates the creation of a
* {@link SimpleJobRepository}. Declares abstract methods for providing DAO
* object implementations.
*
* @see JobRepositoryFactoryBean
* @see MapJobRepositoryFactoryBean
*
* @author Ben Hale
* @author Lucas Ward
* @author Robert Kasanicky
*/
public abstract class AbstractJobRepositoryFactoryBean implements FactoryBean, InitializingBean {
private PlatformTransactionManager transactionManager;
private ProxyFactory proxyFactory;
private String isolationLevelForCreate = DEFAULT_ISOLATION_LEVEL;
/**
* Default value for isolation level in create* method.
*/
private static final String DEFAULT_ISOLATION_LEVEL = "ISOLATION_SERIALIZABLE";
/**
* @return fully configured {@link JobInstanceDao} implementation.
*/
protected abstract JobInstanceDao createJobInstanceDao() throws Exception;
/**
* @return fully configured {@link JobExecutionDao} implementation.
*/
protected abstract JobExecutionDao createJobExecutionDao() throws Exception;
/**
* @return fully configured {@link StepExecutionDao} implementation.
*/
protected abstract StepExecutionDao createStepExecutionDao() throws Exception;
/**
* @return fully configured {@link ExecutionContextDao} implementation.
*/
protected abstract ExecutionContextDao createExecutionContextDao() throws Exception;
/**
* The type of object to be returned from {@link #getObject()}.
*
* @return JobRepository.class
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<JobRepository> getObjectType() {
return JobRepository.class;
}
public boolean isSingleton() {
return true;
}
/**
* public setter for the isolation level to be used for the transaction when
* job execution entities are initially created. The default is
* ISOLATION_SERIALIZABLE, which prevents accidental concurrent execution of
* the same job (ISOLATION_REPEATABLE_READ would work as well).
*
* @param isolationLevelForCreate the isolation level name to set
*
* @see SimpleJobRepository#createJobExecution(String,
* org.springframework.batch.core.JobParameters)
*/
public void setIsolationLevelForCreate(String isolationLevelForCreate) {
this.isolationLevelForCreate = isolationLevelForCreate;
}
/**
* Public setter for the {@link PlatformTransactionManager}.
* @param transactionManager the transactionManager to set
*/
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
private void initializeProxy() throws Exception {
proxyFactory = new ProxyFactory();
TransactionInterceptor advice = new TransactionInterceptor(transactionManager, PropertiesConverter
.stringToProperties("create*=PROPAGATION_REQUIRES_NEW," + isolationLevelForCreate
+ "\ngetLastJobExecution*=PROPAGATION_REQUIRES_NEW," + isolationLevelForCreate
+ "\n*=PROPAGATION_REQUIRED"));
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(advice);
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.addMethodName("*");
advisor.setPointcut(pointcut);
proxyFactory.addAdvisor(advisor);
proxyFactory.setProxyTargetClass(false);
proxyFactory.addInterface(JobRepository.class);
proxyFactory.setTarget(getTarget());
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(transactionManager, "TransactionManager must not be null.");
initializeProxy();
}
private Object getTarget() throws Exception {
return new SimpleJobRepository(createJobInstanceDao(), createJobExecutionDao(), createStepExecutionDao(),
createExecutionContextDao());
}
public Object getObject() throws Exception {
if (proxyFactory == null) {
afterPropertiesSet();
}
return proxyFactory.getProxy();
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.support;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.NameMatchMethodPointcut;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.batch.support.PropertiesConverter;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.Assert;
/**
* A {@link FactoryBean} that automates the creation of a
* {@link SimpleJobRepository}. Declares abstract methods for providing DAO
* object implementations.
*
* @see JobRepositoryFactoryBean
* @see MapJobRepositoryFactoryBean
*
* @author Ben Hale
* @author Lucas Ward
* @author Robert Kasanicky
*/
public abstract class AbstractJobRepositoryFactoryBean implements FactoryBean, InitializingBean {
private PlatformTransactionManager transactionManager;
private ProxyFactory proxyFactory;
private String isolationLevelForCreate = DEFAULT_ISOLATION_LEVEL;
/**
* Default value for isolation level in create* method.
*/
private static final String DEFAULT_ISOLATION_LEVEL = "ISOLATION_SERIALIZABLE";
/**
* @return fully configured {@link JobInstanceDao} implementation.
*/
protected abstract JobInstanceDao createJobInstanceDao() throws Exception;
/**
* @return fully configured {@link JobExecutionDao} implementation.
*/
protected abstract JobExecutionDao createJobExecutionDao() throws Exception;
/**
* @return fully configured {@link StepExecutionDao} implementation.
*/
protected abstract StepExecutionDao createStepExecutionDao() throws Exception;
/**
* @return fully configured {@link ExecutionContextDao} implementation.
*/
protected abstract ExecutionContextDao createExecutionContextDao() throws Exception;
/**
* The type of object to be returned from {@link #getObject()}.
*
* @return JobRepository.class
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
*/
public Class<JobRepository> getObjectType() {
return JobRepository.class;
}
public boolean isSingleton() {
return true;
}
/**
* public setter for the isolation level to be used for the transaction when
* job execution entities are initially created. The default is
* ISOLATION_SERIALIZABLE, which prevents accidental concurrent execution of
* the same job (ISOLATION_REPEATABLE_READ would work as well).
*
* @param isolationLevelForCreate the isolation level name to set
*
* @see SimpleJobRepository#createJobExecution(String,
* org.springframework.batch.core.JobParameters)
*/
public void setIsolationLevelForCreate(String isolationLevelForCreate) {
this.isolationLevelForCreate = isolationLevelForCreate;
}
/**
* Public setter for the {@link PlatformTransactionManager}.
* @param transactionManager the transactionManager to set
*/
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
private void initializeProxy() throws Exception {
proxyFactory = new ProxyFactory();
TransactionInterceptor advice = new TransactionInterceptor(transactionManager, PropertiesConverter
.stringToProperties("create*=PROPAGATION_REQUIRES_NEW," + isolationLevelForCreate
+ "\ngetLastJobExecution*=PROPAGATION_REQUIRES_NEW," + isolationLevelForCreate
+ "\n*=PROPAGATION_REQUIRED"));
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(advice);
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
pointcut.addMethodName("*");
advisor.setPointcut(pointcut);
proxyFactory.addAdvisor(advisor);
proxyFactory.setProxyTargetClass(false);
proxyFactory.addInterface(JobRepository.class);
proxyFactory.setTarget(getTarget());
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(transactionManager, "TransactionManager must not be null.");
initializeProxy();
}
private Object getTarget() throws Exception {
return new SimpleJobRepository(createJobInstanceDao(), createJobExecutionDao(), createStepExecutionDao(),
createExecutionContextDao());
}
public Object getObject() throws Exception {
if (proxyFactory == null) {
afterPropertiesSet();
}
return proxyFactory.getProxy();
}
}

View File

@@ -1,57 +1,73 @@
package org.springframework.batch.core.repository.support;
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.MapExecutionContextDao;
import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.factory.FactoryBean;
/**
* A {@link FactoryBean} that automates the creation of a
* {@link SimpleJobRepository} using non-persistent in-memory DAO
* implementations. This repository is only really intended for use in testing
* and rapid prototyping. In such settings you might find that
* {@link ResourcelessTransactionManager} is useful (as long as your business
* logic does not use a relational database).
*
* @author Robert Kasanicky
*/
public class MapJobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean {
/**
* Convenience method to clear all the map daos globally, removing all
* entities.
*/
public static void clear() {
MapJobInstanceDao.clear();
MapJobExecutionDao.clear();
MapStepExecutionDao.clear();
MapExecutionContextDao.clear();
}
@Override
protected JobExecutionDao createJobExecutionDao() throws Exception {
return new MapJobExecutionDao();
}
@Override
protected JobInstanceDao createJobInstanceDao() throws Exception {
return new MapJobInstanceDao();
}
@Override
protected StepExecutionDao createStepExecutionDao() throws Exception {
return new MapStepExecutionDao();
}
@Override
protected ExecutionContextDao createExecutionContextDao() throws Exception {
return new MapExecutionContextDao();
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.repository.support;
import org.springframework.batch.core.repository.dao.ExecutionContextDao;
import org.springframework.batch.core.repository.dao.JobExecutionDao;
import org.springframework.batch.core.repository.dao.JobInstanceDao;
import org.springframework.batch.core.repository.dao.MapExecutionContextDao;
import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.dao.StepExecutionDao;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.beans.factory.FactoryBean;
/**
* A {@link FactoryBean} that automates the creation of a
* {@link SimpleJobRepository} using non-persistent in-memory DAO
* implementations. This repository is only really intended for use in testing
* and rapid prototyping. In such settings you might find that
* {@link ResourcelessTransactionManager} is useful (as long as your business
* logic does not use a relational database).
*
* @author Robert Kasanicky
*/
public class MapJobRepositoryFactoryBean extends AbstractJobRepositoryFactoryBean {
/**
* Convenience method to clear all the map daos globally, removing all
* entities.
*/
public static void clear() {
MapJobInstanceDao.clear();
MapJobExecutionDao.clear();
MapStepExecutionDao.clear();
MapExecutionContextDao.clear();
}
@Override
protected JobExecutionDao createJobExecutionDao() throws Exception {
return new MapJobExecutionDao();
}
@Override
protected JobInstanceDao createJobInstanceDao() throws Exception {
return new MapJobInstanceDao();
}
@Override
protected StepExecutionDao createStepExecutionDao() throws Exception {
return new MapStepExecutionDao();
}
@Override
protected ExecutionContextDao createExecutionContextDao() throws Exception {
return new MapExecutionContextDao();
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.scope.context;
import java.util.Arrays;

View File

@@ -1,31 +1,47 @@
package org.springframework.batch.core.scope.context;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
/**
* Convenient aspect to wrap a single threaded step execution, where the
* implementation of the {@link Step} is not step scope aware (i.e. not the ones
* provided by the framework).
*
* @author Dave Syer
*
*/
@Aspect
public class StepScopeManager {
@Around("execution(void org.springframework.batch.core.Step+.execute(*)) && target(step) && args(stepExecution)")
public void execute(Step step, StepExecution stepExecution) throws JobInterruptedException {
StepSynchronizationManager.register(stepExecution);
try {
step.execute(stepExecution);
}
finally {
StepSynchronizationManager.release();
}
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.scope.context;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepExecution;
/**
* Convenient aspect to wrap a single threaded step execution, where the
* implementation of the {@link Step} is not step scope aware (i.e. not the ones
* provided by the framework).
*
* @author Dave Syer
*
*/
@Aspect
public class StepScopeManager {
@Around("execution(void org.springframework.batch.core.Step+.execute(*)) && target(step) && args(stepExecution)")
public void execute(Step step, StepExecution stepExecution) throws JobInterruptedException {
StepSynchronizationManager.register(stepExecution);
try {
step.execute(stepExecution);
}
finally {
StepSynchronizationManager.release();
}
}
}

View File

@@ -1,22 +1,38 @@
package org.springframework.batch.core.scope.util;
/**
* Interface to allow the context root for placeholder resolution to be switched
* at runtime. Useful for testing.
*
* @author Dave Syer
*
*/
public interface ContextFactory {
/**
* @return a root object to which placeholders resolve relatively
*/
Object getContext();
/**
* @return a unique identifier for this context
*/
String getContextId();
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.scope.util;
/**
* Interface to allow the context root for placeholder resolution to be switched
* at runtime. Useful for testing.
*
* @author Dave Syer
*
*/
public interface ContextFactory {
/**
* @return a root object to which placeholders resolve relatively
*/
Object getContext();
/**
* @return a unique identifier for this context
*/
String getContextId();
}

View File

@@ -1,166 +1,182 @@
package org.springframework.batch.core.scope.util;
import java.lang.reflect.Modifier;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.scope.DefaultScopedObject;
import org.springframework.aop.scope.ScopedObject;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.util.ClassUtils;
/**
* Factory bean for proxies that can replace placeholders in their target. Just
* a specialisation of {@link ScopedProxyFactoryBean}, with a different target
* source type.
*
* @author Dave Syer
*
*/
public class PlaceholderProxyFactoryBean extends ProxyConfig implements FactoryBean, BeanFactoryAware {
/** The TargetSource that manages scoping */
private final PlaceholderTargetSource scopedTargetSource = new PlaceholderTargetSource();
/** The name of the target bean */
private String targetBeanName;
/** The cached singleton proxy */
private Object proxy;
private final ContextFactory contextFactory;
/**
* Create a new FactoryBean instance.
*/
public PlaceholderProxyFactoryBean(ContextFactory contextFactory) {
this.contextFactory = contextFactory;
// setProxyTargetClass(false);
}
/**
* Set the name of the bean that is to be scoped.
*/
public void setTargetBeanName(String targetBeanName) {
this.targetBeanName = targetBeanName;
this.scopedTargetSource.setTargetBeanName(targetBeanName);
}
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
this.scopedTargetSource.setContextFactory(contextFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Class<?> beanType = beanFactory.getType(this.targetBeanName);
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName
+ "': Target type could not be determined at the time of proxy creation.");
}
if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
// Add an introduction that implements only the methods on ScopedObject.
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
// Add the AopInfrastructureBean marker to indicate that the scoped
// proxy itself is not subject to auto-proxying! Only its target bean
// is.
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
}
return this.proxy;
}
public Class<?> getObjectType() {
if (this.proxy != null) {
return this.proxy.getClass();
}
if (this.scopedTargetSource != null) {
return this.scopedTargetSource.getTargetClass();
}
return null;
}
public boolean isSingleton() {
return true;
}
/**
* Convenience method to create a {@link BeanDefinition} for a target
* wrapped in a placeholder tarrget source, able to defer binding of
* placeholders until the bean is used.
*
* @param definition a target bean definition
* @param registry a {@link BeanDefinitionRegistry}
* @param proxyTargetClass true if we need to use CGlib to create the
* proxies
* @return a {@link BeanDefinitionHolder} for a
* {@link PlaceholderProxyFactoryBean}
*/
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
// Create a proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition.
RootBeanDefinition proxyDefinition = new RootBeanDefinition(PlaceholderProxyFactoryBean.class);
proxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(new StepContextFactory());
proxyDefinition.setOriginatingBeanDefinition(definition.getBeanDefinition());
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String targetBeanName = "lazyBindingProxy." + originalBeanName;
proxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName);
if (proxyTargetClass) {
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ProxyFactoryBean's "proxyTargetClass" default is TRUE, so we
// don't need to set it explicitly here.
}
else {
proxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE);
}
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
// The target bean should be ignored in favor of the proxy.
targetDefinition.setAutowireCandidate(false);
// Register the target bean as separate bean in the factory.
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// Return the scoped proxy definition as primary bean definition
// (potentially an inner bean).
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.scope.util;
import java.lang.reflect.Modifier;
import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.aop.framework.ProxyConfig;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.framework.autoproxy.AutoProxyUtils;
import org.springframework.aop.scope.DefaultScopedObject;
import org.springframework.aop.scope.ScopedObject;
import org.springframework.aop.scope.ScopedProxyFactoryBean;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.util.ClassUtils;
/**
* Factory bean for proxies that can replace placeholders in their target. Just
* a specialisation of {@link ScopedProxyFactoryBean}, with a different target
* source type.
*
* @author Dave Syer
*
*/
public class PlaceholderProxyFactoryBean extends ProxyConfig implements FactoryBean, BeanFactoryAware {
/** The TargetSource that manages scoping */
private final PlaceholderTargetSource scopedTargetSource = new PlaceholderTargetSource();
/** The name of the target bean */
private String targetBeanName;
/** The cached singleton proxy */
private Object proxy;
private final ContextFactory contextFactory;
/**
* Create a new FactoryBean instance.
*/
public PlaceholderProxyFactoryBean(ContextFactory contextFactory) {
this.contextFactory = contextFactory;
// setProxyTargetClass(false);
}
/**
* Set the name of the bean that is to be scoped.
*/
public void setTargetBeanName(String targetBeanName) {
this.targetBeanName = targetBeanName;
this.scopedTargetSource.setTargetBeanName(targetBeanName);
}
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
this.scopedTargetSource.setContextFactory(contextFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Class<?> beanType = beanFactory.getType(this.targetBeanName);
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName
+ "': Target type could not be determined at the time of proxy creation.");
}
if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
// Add an introduction that implements only the methods on ScopedObject.
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
// Add the AopInfrastructureBean marker to indicate that the scoped
// proxy itself is not subject to auto-proxying! Only its target bean
// is.
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
public Object getObject() {
if (this.proxy == null) {
throw new FactoryBeanNotInitializedException();
}
return this.proxy;
}
public Class<?> getObjectType() {
if (this.proxy != null) {
return this.proxy.getClass();
}
if (this.scopedTargetSource != null) {
return this.scopedTargetSource.getTargetClass();
}
return null;
}
public boolean isSingleton() {
return true;
}
/**
* Convenience method to create a {@link BeanDefinition} for a target
* wrapped in a placeholder tarrget source, able to defer binding of
* placeholders until the bean is used.
*
* @param definition a target bean definition
* @param registry a {@link BeanDefinitionRegistry}
* @param proxyTargetClass true if we need to use CGlib to create the
* proxies
* @return a {@link BeanDefinitionHolder} for a
* {@link PlaceholderProxyFactoryBean}
*/
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
// Create a proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition.
RootBeanDefinition proxyDefinition = new RootBeanDefinition(PlaceholderProxyFactoryBean.class);
proxyDefinition.getConstructorArgumentValues().addGenericArgumentValue(new StepContextFactory());
proxyDefinition.setOriginatingBeanDefinition(definition.getBeanDefinition());
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String targetBeanName = "lazyBindingProxy." + originalBeanName;
proxyDefinition.getPropertyValues().addPropertyValue("targetBeanName", targetBeanName);
if (proxyTargetClass) {
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ProxyFactoryBean's "proxyTargetClass" default is TRUE, so we
// don't need to set it explicitly here.
}
else {
proxyDefinition.getPropertyValues().addPropertyValue("proxyTargetClass", Boolean.FALSE);
}
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
// The target bean should be ignored in favor of the proxy.
targetDefinition.setAutowireCandidate(false);
// Register the target bean as separate bean in the factory.
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// Return the scoped proxy definition as primary bean definition
// (potentially an inner bean).
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}
}

View File

@@ -1,23 +1,39 @@
package org.springframework.batch.core.scope.util;
import org.springframework.batch.core.scope.context.StepContext;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
/**
* Implementation of {@link ContextFactory} that provides the current
* {@link StepContext} as a contxt object.
*
* @author Dave Syer
*
*/
public class StepContextFactory implements ContextFactory {
public Object getContext() {
return StepSynchronizationManager.getContext();
}
public String getContextId() {
return (String) StepSynchronizationManager.getContext().getId();
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.scope.util;
import org.springframework.batch.core.scope.context.StepContext;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
/**
* Implementation of {@link ContextFactory} that provides the current
* {@link StepContext} as a contxt object.
*
* @author Dave Syer
*
*/
public class StepContextFactory implements ContextFactory {
public Object getContext() {
return StepSynchronizationManager.getContext();
}
public String getContextId() {
return (String) StepSynchronizationManager.getContext().getId();
}
}

View File

@@ -1,254 +1,270 @@
package org.springframework.batch.core.step.item;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.springframework.batch.retry.ExhaustedRetryException;
import org.springframework.batch.retry.RecoveryCallback;
import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.RetryListener;
import org.springframework.batch.retry.RetryOperations;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.RetryState;
import org.springframework.batch.retry.backoff.BackOffPolicy;
import org.springframework.batch.retry.context.RetryContextSupport;
import org.springframework.batch.retry.policy.RetryContextCache;
import org.springframework.batch.retry.support.DefaultRetryState;
import org.springframework.batch.retry.support.RetrySynchronizationManager;
import org.springframework.batch.retry.support.RetryTemplate;
import org.springframework.batch.support.Classifier;
/**
* A special purpose retry template that deals specifically with multi-valued
* stateful retry. This is useful in the case where the operation to be retried
* operates on multiple items, and when it fails there is no way to decide which
* (if any) of the items was responsible. The {@link RetryState} used in the
* execute methods is composite, and when a failure occurs, all of the keys in
* the composite are "tarred with the same brush". Subsequent attempts to
* execute with any of the keys that have failed previously results in a new
* attempt and the previous state is used to check the {@link RetryPolicy}. If
* one of the failed items eventually succeeds then the others in the current
* composite for that attempt will be cleared from the context cache (as
* normal), but there may still be entries in the cache for the original failed
* items. This might mean that an item that did not cause a failure is never
* retried because other items in the same batch fail fatally first.
*
* @author Dave Syer
*
*/
public class BatchRetryTemplate implements RetryOperations {
private class BatchRetryState extends DefaultRetryState {
private final Collection<RetryState> keys;
public BatchRetryState(Collection<RetryState> keys) {
super(keys);
this.keys = new ArrayList<RetryState>(keys);
}
}
private static class BatchRetryContext extends RetryContextSupport {
private final Collection<RetryContext> contexts;
public BatchRetryContext(RetryContext parent, Collection<RetryContext> contexts) {
super(parent);
this.contexts = contexts;
int count = 0;
for (RetryContext context : contexts) {
int retryCount = context.getRetryCount();
if (retryCount > count) {
count = retryCount;
registerThrowable(context.getLastThrowable());
}
}
}
}
private static class InnerRetryTemplate extends RetryTemplate {
@Override
protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) {
BatchRetryContext batchContext = (BatchRetryContext) context;
for (RetryContext nextContext : batchContext.contexts) {
if (!super.canRetry(retryPolicy, nextContext)) {
return false;
}
}
return true;
}
@Override
protected RetryContext open(RetryPolicy retryPolicy, RetryState state) {
BatchRetryState batchState = (BatchRetryState) state;
Collection<RetryContext> contexts = new ArrayList<RetryContext>();
for (RetryState retryState : batchState.keys) {
contexts.add(super.open(retryPolicy, retryState));
}
return new BatchRetryContext(RetrySynchronizationManager.getContext(), contexts);
}
@Override
protected void registerThrowable(RetryPolicy retryPolicy, RetryState state, RetryContext context, Exception e) {
BatchRetryState batchState = (BatchRetryState) state;
BatchRetryContext batchContext = (BatchRetryContext) context;
Iterator<RetryContext> contextIterator = batchContext.contexts.iterator();
for (RetryState retryState : batchState.keys) {
RetryContext nextContext = contextIterator.next();
super.registerThrowable(retryPolicy, retryState, nextContext, e);
}
}
@Override
protected void close(RetryPolicy retryPolicy, RetryContext context, RetryState state, boolean succeeded) {
BatchRetryState batchState = (BatchRetryState) state;
BatchRetryContext batchContext = (BatchRetryContext) context;
Iterator<RetryContext> contextIterator = batchContext.contexts.iterator();
for (RetryState retryState : batchState.keys) {
RetryContext nextContext = contextIterator.next();
super.close(retryPolicy, nextContext, retryState, succeeded);
}
}
@Override
protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback, RetryContext context,
RetryState state) throws Exception {
BatchRetryState batchState = (BatchRetryState) state;
BatchRetryContext batchContext = (BatchRetryContext) context;
// Accumulate exceptions to be thrown so all the keys get a crack
Exception rethrowable = null;
ExhaustedRetryException exhausted = null;
Iterator<RetryContext> contextIterator = batchContext.contexts.iterator();
for (RetryState retryState : batchState.keys) {
RetryContext nextContext = contextIterator.next();
try {
super.handleRetryExhausted(null, nextContext, retryState);
}
catch (ExhaustedRetryException e) {
exhausted = e;
}
catch (Exception e) {
rethrowable = e;
}
}
if (recoveryCallback != null) {
return recoveryCallback.recover(context);
}
if (exhausted != null) {
throw exhausted;
}
throw rethrowable;
}
}
private final InnerRetryTemplate delegate = new InnerRetryTemplate();
private final RetryTemplate regular = new RetryTemplate();
public <T> T execute(RetryCallback<T> retryCallback, Collection<RetryState> states) throws ExhaustedRetryException,
Exception {
RetryState batchState = new BatchRetryState(states);
return delegate.execute(retryCallback, batchState);
}
public <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
Collection<RetryState> states) throws ExhaustedRetryException, Exception {
RetryState batchState = new BatchRetryState(states);
return delegate.execute(retryCallback, recoveryCallback, batchState);
}
public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
RetryState retryState) throws Exception, ExhaustedRetryException {
return regular.execute(retryCallback, recoveryCallback, retryState);
}
public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback) throws Exception {
return regular.execute(retryCallback, recoveryCallback);
}
public final <T> T execute(RetryCallback<T> retryCallback, RetryState retryState) throws Exception,
ExhaustedRetryException {
return regular.execute(retryCallback, retryState);
}
public final <T> T execute(RetryCallback<T> retryCallback) throws Exception {
return regular.execute(retryCallback);
}
public static List<RetryState> createState(List<?> keys) {
List<RetryState> states = new ArrayList<RetryState>();
for (Object key : keys) {
states.add(new DefaultRetryState(key));
}
return states;
}
public static List<RetryState> createState(List<?> keys, Classifier<? super Throwable, Boolean> classifier) {
List<RetryState> states = new ArrayList<RetryState>();
for (Object key : keys) {
states.add(new DefaultRetryState(key, classifier));
}
return states;
}
public void registerListener(RetryListener listener) {
delegate.registerListener(listener);
regular.registerListener(listener);
}
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
delegate.setBackOffPolicy(backOffPolicy);
regular.setBackOffPolicy(backOffPolicy);
}
public void setListeners(RetryListener[] listeners) {
delegate.setListeners(listeners);
regular.setListeners(listeners);
}
public void setRetryContextCache(RetryContextCache retryContextCache) {
delegate.setRetryContextCache(retryContextCache);
regular.setRetryContextCache(retryContextCache);
}
public void setRetryPolicy(RetryPolicy retryPolicy) {
delegate.setRetryPolicy(retryPolicy);
regular.setRetryPolicy(retryPolicy);
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.springframework.batch.retry.ExhaustedRetryException;
import org.springframework.batch.retry.RecoveryCallback;
import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.RetryListener;
import org.springframework.batch.retry.RetryOperations;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.RetryState;
import org.springframework.batch.retry.backoff.BackOffPolicy;
import org.springframework.batch.retry.context.RetryContextSupport;
import org.springframework.batch.retry.policy.RetryContextCache;
import org.springframework.batch.retry.support.DefaultRetryState;
import org.springframework.batch.retry.support.RetrySynchronizationManager;
import org.springframework.batch.retry.support.RetryTemplate;
import org.springframework.batch.support.Classifier;
/**
* A special purpose retry template that deals specifically with multi-valued
* stateful retry. This is useful in the case where the operation to be retried
* operates on multiple items, and when it fails there is no way to decide which
* (if any) of the items was responsible. The {@link RetryState} used in the
* execute methods is composite, and when a failure occurs, all of the keys in
* the composite are "tarred with the same brush". Subsequent attempts to
* execute with any of the keys that have failed previously results in a new
* attempt and the previous state is used to check the {@link RetryPolicy}. If
* one of the failed items eventually succeeds then the others in the current
* composite for that attempt will be cleared from the context cache (as
* normal), but there may still be entries in the cache for the original failed
* items. This might mean that an item that did not cause a failure is never
* retried because other items in the same batch fail fatally first.
*
* @author Dave Syer
*
*/
public class BatchRetryTemplate implements RetryOperations {
private class BatchRetryState extends DefaultRetryState {
private final Collection<RetryState> keys;
public BatchRetryState(Collection<RetryState> keys) {
super(keys);
this.keys = new ArrayList<RetryState>(keys);
}
}
private static class BatchRetryContext extends RetryContextSupport {
private final Collection<RetryContext> contexts;
public BatchRetryContext(RetryContext parent, Collection<RetryContext> contexts) {
super(parent);
this.contexts = contexts;
int count = 0;
for (RetryContext context : contexts) {
int retryCount = context.getRetryCount();
if (retryCount > count) {
count = retryCount;
registerThrowable(context.getLastThrowable());
}
}
}
}
private static class InnerRetryTemplate extends RetryTemplate {
@Override
protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) {
BatchRetryContext batchContext = (BatchRetryContext) context;
for (RetryContext nextContext : batchContext.contexts) {
if (!super.canRetry(retryPolicy, nextContext)) {
return false;
}
}
return true;
}
@Override
protected RetryContext open(RetryPolicy retryPolicy, RetryState state) {
BatchRetryState batchState = (BatchRetryState) state;
Collection<RetryContext> contexts = new ArrayList<RetryContext>();
for (RetryState retryState : batchState.keys) {
contexts.add(super.open(retryPolicy, retryState));
}
return new BatchRetryContext(RetrySynchronizationManager.getContext(), contexts);
}
@Override
protected void registerThrowable(RetryPolicy retryPolicy, RetryState state, RetryContext context, Exception e) {
BatchRetryState batchState = (BatchRetryState) state;
BatchRetryContext batchContext = (BatchRetryContext) context;
Iterator<RetryContext> contextIterator = batchContext.contexts.iterator();
for (RetryState retryState : batchState.keys) {
RetryContext nextContext = contextIterator.next();
super.registerThrowable(retryPolicy, retryState, nextContext, e);
}
}
@Override
protected void close(RetryPolicy retryPolicy, RetryContext context, RetryState state, boolean succeeded) {
BatchRetryState batchState = (BatchRetryState) state;
BatchRetryContext batchContext = (BatchRetryContext) context;
Iterator<RetryContext> contextIterator = batchContext.contexts.iterator();
for (RetryState retryState : batchState.keys) {
RetryContext nextContext = contextIterator.next();
super.close(retryPolicy, nextContext, retryState, succeeded);
}
}
@Override
protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback, RetryContext context,
RetryState state) throws Exception {
BatchRetryState batchState = (BatchRetryState) state;
BatchRetryContext batchContext = (BatchRetryContext) context;
// Accumulate exceptions to be thrown so all the keys get a crack
Exception rethrowable = null;
ExhaustedRetryException exhausted = null;
Iterator<RetryContext> contextIterator = batchContext.contexts.iterator();
for (RetryState retryState : batchState.keys) {
RetryContext nextContext = contextIterator.next();
try {
super.handleRetryExhausted(null, nextContext, retryState);
}
catch (ExhaustedRetryException e) {
exhausted = e;
}
catch (Exception e) {
rethrowable = e;
}
}
if (recoveryCallback != null) {
return recoveryCallback.recover(context);
}
if (exhausted != null) {
throw exhausted;
}
throw rethrowable;
}
}
private final InnerRetryTemplate delegate = new InnerRetryTemplate();
private final RetryTemplate regular = new RetryTemplate();
public <T> T execute(RetryCallback<T> retryCallback, Collection<RetryState> states) throws ExhaustedRetryException,
Exception {
RetryState batchState = new BatchRetryState(states);
return delegate.execute(retryCallback, batchState);
}
public <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
Collection<RetryState> states) throws ExhaustedRetryException, Exception {
RetryState batchState = new BatchRetryState(states);
return delegate.execute(retryCallback, recoveryCallback, batchState);
}
public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
RetryState retryState) throws Exception, ExhaustedRetryException {
return regular.execute(retryCallback, recoveryCallback, retryState);
}
public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback) throws Exception {
return regular.execute(retryCallback, recoveryCallback);
}
public final <T> T execute(RetryCallback<T> retryCallback, RetryState retryState) throws Exception,
ExhaustedRetryException {
return regular.execute(retryCallback, retryState);
}
public final <T> T execute(RetryCallback<T> retryCallback) throws Exception {
return regular.execute(retryCallback);
}
public static List<RetryState> createState(List<?> keys) {
List<RetryState> states = new ArrayList<RetryState>();
for (Object key : keys) {
states.add(new DefaultRetryState(key));
}
return states;
}
public static List<RetryState> createState(List<?> keys, Classifier<? super Throwable, Boolean> classifier) {
List<RetryState> states = new ArrayList<RetryState>();
for (Object key : keys) {
states.add(new DefaultRetryState(key, classifier));
}
return states;
}
public void registerListener(RetryListener listener) {
delegate.registerListener(listener);
regular.registerListener(listener);
}
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
delegate.setBackOffPolicy(backOffPolicy);
regular.setBackOffPolicy(backOffPolicy);
}
public void setListeners(RetryListener[] listeners) {
delegate.setListeners(listeners);
regular.setListeners(listeners);
}
public void setRetryContextCache(RetryContextCache retryContextCache) {
delegate.setRetryContextCache(retryContextCache);
regular.setRetryContextCache(retryContextCache);
}
public void setRetryPolicy(RetryPolicy retryPolicy) {
delegate.setRetryPolicy(retryPolicy);
regular.setRetryPolicy(retryPolicy);
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import java.util.ArrayList;

View File

@@ -1,68 +1,84 @@
package org.springframework.batch.core.step.item;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
/**
* A {@link Tasklet} implementing variations on read-process-write item
* handling.
*
* @author Dave Syer
*
* @param <I> input item type
*/
public class ChunkOrientedTasklet<I> implements Tasklet {
private static final String INPUTS_KEY = "INPUTS";
private final ChunkProcessor<I> chunkProcessor;
private final ChunkProvider<I> chunkProvider;
private boolean buffering = true;
public ChunkOrientedTasklet(ChunkProvider<I> chunkProvider, ChunkProcessor<I> chunkProcessor) {
this.chunkProvider = chunkProvider;
this.chunkProcessor = chunkProcessor;
}
/**
* Flag to indicate that items should be buffered once read. Defaults to
* true, which is appropriate for forward-only, non-transactional item
* readers. Main (or only) use case for setting this flag to true is a
* transactional JMS item reader.
*
* @param buffering
*/
public void setBuffering(boolean buffering) {
this.buffering = buffering;
}
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
@SuppressWarnings("unchecked")
Chunk<I> inputs = (Chunk<I>) chunkContext.getAttribute(INPUTS_KEY);
if (inputs == null) {
inputs = chunkProvider.provide(contribution);
if (buffering) {
chunkContext.setAttribute(INPUTS_KEY, inputs);
}
}
chunkProcessor.process(contribution, inputs);
chunkContext.removeAttribute(INPUTS_KEY);
chunkContext.setComplete();
chunkProvider.postProcess(contribution, inputs);
if (!inputs.isEnd()) {
contribution.setExitStatus(ExitStatus.FINISHED);
}
return RepeatStatus.continueIf(!inputs.isEnd());
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
/**
* A {@link Tasklet} implementing variations on read-process-write item
* handling.
*
* @author Dave Syer
*
* @param <I> input item type
*/
public class ChunkOrientedTasklet<I> implements Tasklet {
private static final String INPUTS_KEY = "INPUTS";
private final ChunkProcessor<I> chunkProcessor;
private final ChunkProvider<I> chunkProvider;
private boolean buffering = true;
public ChunkOrientedTasklet(ChunkProvider<I> chunkProvider, ChunkProcessor<I> chunkProcessor) {
this.chunkProvider = chunkProvider;
this.chunkProcessor = chunkProcessor;
}
/**
* Flag to indicate that items should be buffered once read. Defaults to
* true, which is appropriate for forward-only, non-transactional item
* readers. Main (or only) use case for setting this flag to true is a
* transactional JMS item reader.
*
* @param buffering
*/
public void setBuffering(boolean buffering) {
this.buffering = buffering;
}
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
@SuppressWarnings("unchecked")
Chunk<I> inputs = (Chunk<I>) chunkContext.getAttribute(INPUTS_KEY);
if (inputs == null) {
inputs = chunkProvider.provide(contribution);
if (buffering) {
chunkContext.setAttribute(INPUTS_KEY, inputs);
}
}
chunkProcessor.process(contribution, inputs);
chunkContext.removeAttribute(INPUTS_KEY);
chunkContext.setComplete();
chunkProvider.postProcess(contribution, inputs);
if (!inputs.isEnd()) {
contribution.setExitStatus(ExitStatus.FINISHED);
}
return RepeatStatus.continueIf(!inputs.isEnd());
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import org.springframework.batch.core.StepContribution;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import org.springframework.batch.core.StepContribution;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import java.util.Collections;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import org.springframework.batch.core.StepContribution;

View File

@@ -1,299 +1,315 @@
package org.springframework.batch.core.step.item;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.springframework.batch.core.ItemProcessListener;
import org.springframework.batch.core.ItemReadListener;
import org.springframework.batch.core.ItemWriteListener;
import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy;
import org.springframework.batch.core.step.skip.NonSkippableReadException;
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
import org.springframework.batch.core.step.skip.SkipListenerFailedException;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.retry.RetryException;
import org.springframework.batch.retry.RetryListener;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.backoff.BackOffPolicy;
import org.springframework.batch.retry.policy.ExceptionClassifierRetryPolicy;
import org.springframework.batch.retry.policy.MapRetryContextCache;
import org.springframework.batch.retry.policy.RetryContextCache;
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
import org.springframework.batch.support.Classifier;
/**
* Factory bean for step that provides options for configuring skip behaviour.
* User can set {@link #setSkipLimit(int)} to set how many exceptions of
* {@link #setSkippableExceptionClasses(Collection)} types are tolerated.
* {@link #setFatalExceptionClasses(Collection)} will cause immediate
* termination of job - they are treated as higher priority than
* {@link #setSkippableExceptionClasses(Collection)}, so the two lists don't
* need to be exclusive.
*
* Skippable exceptions on write will by default cause transaction rollback - to
* avoid rollback for specific exception class include it in the transaction
* attribute as "no rollback for".
*
* @see SimpleStepFactoryBean
*
* @author Dave Syer
* @author Robert Kasanicky
*
*/
public class FaultTolerantStepFactoryBean<T, S> extends SimpleStepFactoryBean<T, S> {
private int skipLimit = 0;
private Collection<Class<? extends Throwable>> skippableExceptionClasses = new HashSet<Class<? extends Throwable>>();
private Collection<Class<? extends Throwable>> fatalExceptionClasses = new HashSet<Class<? extends Throwable>>();
{
fatalExceptionClasses.add(Error.class);
skippableExceptionClasses.add(Exception.class);
}
private int cacheCapacity = 0;
private int retryLimit = 0;
private Collection<Class<? extends Throwable>> retryableExceptionClasses = new HashSet<Class<? extends Throwable>>();
private BackOffPolicy backOffPolicy;
private RetryListener[] retryListeners;
private RetryPolicy retryPolicy;
private RetryContextCache retryContextCache;
private boolean isReaderTransactionalQueue = false;
public void setIsReaderTransactionalQueue(boolean isReaderTransactionalQueue) {
this.isReaderTransactionalQueue = isReaderTransactionalQueue;
}
/**
* Setter for the retry policy. If this is specified the other retry
* properties are ignored (retryLimit, backOffPolicy,
* retryableExceptionClasses).
*
* @param retryPolicy a stateless {@link RetryPolicy}
*/
public void setRetryPolicy(RetryPolicy retryPolicy) {
this.retryPolicy = retryPolicy;
}
/**
* Public setter for the retry limit. Each item can be retried up to this
* limit.
* @param retryLimit the retry limit to set
*/
public void setRetryLimit(int retryLimit) {
this.retryLimit = retryLimit;
}
/**
* Public setter for the capacity of the cache in the retry policy. If more
* items than this fail without being skipped or recovered an exception will
* be thrown. This is to guard against inadvertent infinite loops generated
* by item identity problems.<br/>
*
* The default value should be high enough and more for most purposes. To
* breach the limit in a single-threaded step typically you have to have
* this many failures in a single transaction. Defaults to the value in the
* {@link MapRetryContextCache}.<br/>
*
* This property is ignored if the
* {@link #setRetryContextCache(RetryContextCache)} is set directly.
*
* @param cacheCapacity the cache capacity to set (greater than 0 else
* ignored)
*/
public void setCacheCapacity(int cacheCapacity) {
this.cacheCapacity = cacheCapacity;
}
/**
* Override the default retry context cache for retry of chunk processing.
* If this property is set then {@link #setCacheCapacity(int)} is ignored.
*
* @param retryContextCache the {@link RetryContextCache} to set
*/
public void setRetryContextCache(RetryContextCache retryContextCache) {
this.retryContextCache = retryContextCache;
}
/**
* Public setter for the Class[].
* @param retryableExceptionClasses the retryableExceptionClasses to set
*/
public void setRetryableExceptionClasses(Collection<Class<? extends Throwable>> retryableExceptionClasses) {
this.retryableExceptionClasses = retryableExceptionClasses;
}
/**
* Public setter for the {@link BackOffPolicy}.
* @param backOffPolicy the {@link BackOffPolicy} to set
*/
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
this.backOffPolicy = backOffPolicy;
}
/**
* Public setter for the {@link RetryListener}s.
* @param retryListeners the {@link RetryListener}s to set
*/
public void setRetryListeners(RetryListener[] retryListeners) {
this.retryListeners = retryListeners;
}
/**
* Public setter for a limit that determines skip policy. If this value is
* positive then an exception in chunk processing will cause the item to be
* skipped and no exception propagated until the limit is reached. If it is
* zero then all exceptions will be propagated from the chunk and cause the
* step to abort.
*
* @param skipLimit the value to set. Default is 0 (never skip).
*/
public void setSkipLimit(int skipLimit) {
this.skipLimit = skipLimit;
}
/**
* Public setter for exception classes that when raised won't crash the job
* but will result in transaction rollback and the item which handling
* caused the exception will be skipped.
*
* @param exceptionClasses defaults to <code>Exception</code>
*/
public void setSkippableExceptionClasses(Collection<Class<? extends Throwable>> exceptionClasses) {
this.skippableExceptionClasses = exceptionClasses;
}
/**
* Public setter for exception classes that should cause immediate failure.
*
* @param fatalExceptionClasses {@link Error} by default
*/
public void setFatalExceptionClasses(Collection<Class<? extends Throwable>> fatalExceptionClasses) {
this.fatalExceptionClasses = fatalExceptionClasses;
}
/**
* Uses the {@link #setSkipLimit(int)} value to configure item handler and
* and exception handler.
*/
protected void applyConfiguration(TaskletStep step) {
super.applyConfiguration(step);
if (retryLimit > 0 || skipLimit > 0 || retryPolicy != null) {
addFatalExceptionIfMissing(SkipLimitExceededException.class);
addFatalExceptionIfMissing(NonSkippableReadException.class);
addFatalExceptionIfMissing(SkipListenerFailedException.class);
addFatalExceptionIfMissing(RetryException.class);
if (retryPolicy == null) {
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(retryLimit);
if (!retryableExceptionClasses.isEmpty()) { // otherwise we
// retry all exceptions
simpleRetryPolicy.setRetryableExceptionClasses(retryableExceptionClasses);
}
simpleRetryPolicy.setFatalExceptionClasses(fatalExceptionClasses);
ExceptionClassifierRetryPolicy classifierRetryPolicy = new ExceptionClassifierRetryPolicy();
HashMap<Class<? extends Throwable>, RetryPolicy> exceptionTypeMap = new HashMap<Class<? extends Throwable>, RetryPolicy>();
for (Class<? extends Throwable> cls : retryableExceptionClasses) {
exceptionTypeMap.put(cls, simpleRetryPolicy);
}
classifierRetryPolicy.setPolicyMap(exceptionTypeMap);
retryPolicy = classifierRetryPolicy;
}
BatchRetryTemplate batchRetryTemplate = new BatchRetryTemplate();
if (backOffPolicy != null) {
batchRetryTemplate.setBackOffPolicy(backOffPolicy);
}
batchRetryTemplate.setRetryPolicy(retryPolicy);
// Co-ordinate the retry policy with the exception handler:
RepeatOperations stepOperations = getStepOperations();
if (stepOperations instanceof RepeatTemplate) {
SimpleRetryExceptionHandler exceptionHandler = new SimpleRetryExceptionHandler(retryPolicy,
getExceptionHandler(), fatalExceptionClasses);
((RepeatTemplate) stepOperations).setExceptionHandler(exceptionHandler);
}
if (retryContextCache == null) {
if (cacheCapacity > 0) {
batchRetryTemplate.setRetryContextCache(new MapRetryContextCache(cacheCapacity));
}
}
else {
batchRetryTemplate.setRetryContextCache(retryContextCache);
}
if (retryListeners != null) {
batchRetryTemplate.setListeners(retryListeners);
}
List<Class<? extends Throwable>> exceptions = new ArrayList<Class<? extends Throwable>>(
skippableExceptionClasses);
SkipPolicy readSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, skippableExceptionClasses,
new ArrayList<Class<? extends Throwable>>(fatalExceptionClasses));
exceptions.addAll(new ArrayList<Class<? extends Throwable>>(retryableExceptionClasses));
SkipPolicy writeSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, exceptions,
new ArrayList<Class<? extends Throwable>>(fatalExceptionClasses));
Classifier<Throwable, Boolean> rollbackClassifier = new Classifier<Throwable, Boolean>() {
public Boolean classify(Throwable classifiable) {
return getTransactionAttribute().rollbackOn(classifiable);
}
};
FaultTolerantChunkProvider<T> chunkProvider = new FaultTolerantChunkProvider<T>(getItemReader(),
getChunkOperations());
chunkProvider.setSkipPolicy(readSkipPolicy);
chunkProvider.setListeners(BatchListenerFactoryHelper.<ItemReadListener<T>>getListeners(getListeners(), ItemReadListener.class));
chunkProvider.setListeners(BatchListenerFactoryHelper.<SkipListener<T,S>>getListeners(getListeners(), SkipListener.class));
FaultTolerantChunkProcessor<T, S> chunkProcessor = new FaultTolerantChunkProcessor<T, S>(getItemProcessor(), getItemWriter(), batchRetryTemplate);
chunkProcessor.setBuffering(!isReaderTransactionalQueue);
chunkProcessor.setWriteSkipPolicy(writeSkipPolicy);
chunkProcessor.setProcessSkipPolicy(writeSkipPolicy);
chunkProcessor.setRollbackClassifier(rollbackClassifier);
chunkProcessor.setListeners(BatchListenerFactoryHelper.<ItemProcessListener<T,S>>getListeners(getListeners(), ItemProcessListener.class));
chunkProcessor.setListeners(BatchListenerFactoryHelper.<ItemWriteListener<S>>getListeners(getListeners(), ItemWriteListener.class));
chunkProcessor.setListeners(BatchListenerFactoryHelper.<SkipListener<T,S>>getListeners(getListeners(), SkipListener.class));
ChunkOrientedTasklet<T> tasklet = new ChunkOrientedTasklet<T>(chunkProvider, chunkProcessor);
tasklet.setBuffering(!isReaderTransactionalQueue);
step.setTasklet(tasklet);
}
}
public void addFatalExceptionIfMissing(Class<? extends Throwable> cls) {
List<Class<? extends Throwable>> fatalExceptionList = new ArrayList<Class<? extends Throwable>>();
for (Class<? extends Throwable> exceptionClass : fatalExceptionClasses) {
fatalExceptionList.add(exceptionClass);
}
if (!fatalExceptionList.contains(cls)) {
fatalExceptionList.add(cls);
}
fatalExceptionClasses = fatalExceptionList;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.springframework.batch.core.ItemProcessListener;
import org.springframework.batch.core.ItemReadListener;
import org.springframework.batch.core.ItemWriteListener;
import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy;
import org.springframework.batch.core.step.skip.NonSkippableReadException;
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
import org.springframework.batch.core.step.skip.SkipListenerFailedException;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.retry.RetryException;
import org.springframework.batch.retry.RetryListener;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.backoff.BackOffPolicy;
import org.springframework.batch.retry.policy.ExceptionClassifierRetryPolicy;
import org.springframework.batch.retry.policy.MapRetryContextCache;
import org.springframework.batch.retry.policy.RetryContextCache;
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
import org.springframework.batch.support.Classifier;
/**
* Factory bean for step that provides options for configuring skip behaviour.
* User can set {@link #setSkipLimit(int)} to set how many exceptions of
* {@link #setSkippableExceptionClasses(Collection)} types are tolerated.
* {@link #setFatalExceptionClasses(Collection)} will cause immediate
* termination of job - they are treated as higher priority than
* {@link #setSkippableExceptionClasses(Collection)}, so the two lists don't
* need to be exclusive.
*
* Skippable exceptions on write will by default cause transaction rollback - to
* avoid rollback for specific exception class include it in the transaction
* attribute as "no rollback for".
*
* @see SimpleStepFactoryBean
*
* @author Dave Syer
* @author Robert Kasanicky
*
*/
public class FaultTolerantStepFactoryBean<T, S> extends SimpleStepFactoryBean<T, S> {
private int skipLimit = 0;
private Collection<Class<? extends Throwable>> skippableExceptionClasses = new HashSet<Class<? extends Throwable>>();
private Collection<Class<? extends Throwable>> fatalExceptionClasses = new HashSet<Class<? extends Throwable>>();
{
fatalExceptionClasses.add(Error.class);
skippableExceptionClasses.add(Exception.class);
}
private int cacheCapacity = 0;
private int retryLimit = 0;
private Collection<Class<? extends Throwable>> retryableExceptionClasses = new HashSet<Class<? extends Throwable>>();
private BackOffPolicy backOffPolicy;
private RetryListener[] retryListeners;
private RetryPolicy retryPolicy;
private RetryContextCache retryContextCache;
private boolean isReaderTransactionalQueue = false;
public void setIsReaderTransactionalQueue(boolean isReaderTransactionalQueue) {
this.isReaderTransactionalQueue = isReaderTransactionalQueue;
}
/**
* Setter for the retry policy. If this is specified the other retry
* properties are ignored (retryLimit, backOffPolicy,
* retryableExceptionClasses).
*
* @param retryPolicy a stateless {@link RetryPolicy}
*/
public void setRetryPolicy(RetryPolicy retryPolicy) {
this.retryPolicy = retryPolicy;
}
/**
* Public setter for the retry limit. Each item can be retried up to this
* limit.
* @param retryLimit the retry limit to set
*/
public void setRetryLimit(int retryLimit) {
this.retryLimit = retryLimit;
}
/**
* Public setter for the capacity of the cache in the retry policy. If more
* items than this fail without being skipped or recovered an exception will
* be thrown. This is to guard against inadvertent infinite loops generated
* by item identity problems.<br/>
*
* The default value should be high enough and more for most purposes. To
* breach the limit in a single-threaded step typically you have to have
* this many failures in a single transaction. Defaults to the value in the
* {@link MapRetryContextCache}.<br/>
*
* This property is ignored if the
* {@link #setRetryContextCache(RetryContextCache)} is set directly.
*
* @param cacheCapacity the cache capacity to set (greater than 0 else
* ignored)
*/
public void setCacheCapacity(int cacheCapacity) {
this.cacheCapacity = cacheCapacity;
}
/**
* Override the default retry context cache for retry of chunk processing.
* If this property is set then {@link #setCacheCapacity(int)} is ignored.
*
* @param retryContextCache the {@link RetryContextCache} to set
*/
public void setRetryContextCache(RetryContextCache retryContextCache) {
this.retryContextCache = retryContextCache;
}
/**
* Public setter for the Class[].
* @param retryableExceptionClasses the retryableExceptionClasses to set
*/
public void setRetryableExceptionClasses(Collection<Class<? extends Throwable>> retryableExceptionClasses) {
this.retryableExceptionClasses = retryableExceptionClasses;
}
/**
* Public setter for the {@link BackOffPolicy}.
* @param backOffPolicy the {@link BackOffPolicy} to set
*/
public void setBackOffPolicy(BackOffPolicy backOffPolicy) {
this.backOffPolicy = backOffPolicy;
}
/**
* Public setter for the {@link RetryListener}s.
* @param retryListeners the {@link RetryListener}s to set
*/
public void setRetryListeners(RetryListener[] retryListeners) {
this.retryListeners = retryListeners;
}
/**
* Public setter for a limit that determines skip policy. If this value is
* positive then an exception in chunk processing will cause the item to be
* skipped and no exception propagated until the limit is reached. If it is
* zero then all exceptions will be propagated from the chunk and cause the
* step to abort.
*
* @param skipLimit the value to set. Default is 0 (never skip).
*/
public void setSkipLimit(int skipLimit) {
this.skipLimit = skipLimit;
}
/**
* Public setter for exception classes that when raised won't crash the job
* but will result in transaction rollback and the item which handling
* caused the exception will be skipped.
*
* @param exceptionClasses defaults to <code>Exception</code>
*/
public void setSkippableExceptionClasses(Collection<Class<? extends Throwable>> exceptionClasses) {
this.skippableExceptionClasses = exceptionClasses;
}
/**
* Public setter for exception classes that should cause immediate failure.
*
* @param fatalExceptionClasses {@link Error} by default
*/
public void setFatalExceptionClasses(Collection<Class<? extends Throwable>> fatalExceptionClasses) {
this.fatalExceptionClasses = fatalExceptionClasses;
}
/**
* Uses the {@link #setSkipLimit(int)} value to configure item handler and
* and exception handler.
*/
protected void applyConfiguration(TaskletStep step) {
super.applyConfiguration(step);
if (retryLimit > 0 || skipLimit > 0 || retryPolicy != null) {
addFatalExceptionIfMissing(SkipLimitExceededException.class);
addFatalExceptionIfMissing(NonSkippableReadException.class);
addFatalExceptionIfMissing(SkipListenerFailedException.class);
addFatalExceptionIfMissing(RetryException.class);
if (retryPolicy == null) {
SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(retryLimit);
if (!retryableExceptionClasses.isEmpty()) { // otherwise we
// retry all exceptions
simpleRetryPolicy.setRetryableExceptionClasses(retryableExceptionClasses);
}
simpleRetryPolicy.setFatalExceptionClasses(fatalExceptionClasses);
ExceptionClassifierRetryPolicy classifierRetryPolicy = new ExceptionClassifierRetryPolicy();
HashMap<Class<? extends Throwable>, RetryPolicy> exceptionTypeMap = new HashMap<Class<? extends Throwable>, RetryPolicy>();
for (Class<? extends Throwable> cls : retryableExceptionClasses) {
exceptionTypeMap.put(cls, simpleRetryPolicy);
}
classifierRetryPolicy.setPolicyMap(exceptionTypeMap);
retryPolicy = classifierRetryPolicy;
}
BatchRetryTemplate batchRetryTemplate = new BatchRetryTemplate();
if (backOffPolicy != null) {
batchRetryTemplate.setBackOffPolicy(backOffPolicy);
}
batchRetryTemplate.setRetryPolicy(retryPolicy);
// Co-ordinate the retry policy with the exception handler:
RepeatOperations stepOperations = getStepOperations();
if (stepOperations instanceof RepeatTemplate) {
SimpleRetryExceptionHandler exceptionHandler = new SimpleRetryExceptionHandler(retryPolicy,
getExceptionHandler(), fatalExceptionClasses);
((RepeatTemplate) stepOperations).setExceptionHandler(exceptionHandler);
}
if (retryContextCache == null) {
if (cacheCapacity > 0) {
batchRetryTemplate.setRetryContextCache(new MapRetryContextCache(cacheCapacity));
}
}
else {
batchRetryTemplate.setRetryContextCache(retryContextCache);
}
if (retryListeners != null) {
batchRetryTemplate.setListeners(retryListeners);
}
List<Class<? extends Throwable>> exceptions = new ArrayList<Class<? extends Throwable>>(
skippableExceptionClasses);
SkipPolicy readSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, skippableExceptionClasses,
new ArrayList<Class<? extends Throwable>>(fatalExceptionClasses));
exceptions.addAll(new ArrayList<Class<? extends Throwable>>(retryableExceptionClasses));
SkipPolicy writeSkipPolicy = new LimitCheckingItemSkipPolicy(skipLimit, exceptions,
new ArrayList<Class<? extends Throwable>>(fatalExceptionClasses));
Classifier<Throwable, Boolean> rollbackClassifier = new Classifier<Throwable, Boolean>() {
public Boolean classify(Throwable classifiable) {
return getTransactionAttribute().rollbackOn(classifiable);
}
};
FaultTolerantChunkProvider<T> chunkProvider = new FaultTolerantChunkProvider<T>(getItemReader(),
getChunkOperations());
chunkProvider.setSkipPolicy(readSkipPolicy);
chunkProvider.setListeners(BatchListenerFactoryHelper.<ItemReadListener<T>>getListeners(getListeners(), ItemReadListener.class));
chunkProvider.setListeners(BatchListenerFactoryHelper.<SkipListener<T,S>>getListeners(getListeners(), SkipListener.class));
FaultTolerantChunkProcessor<T, S> chunkProcessor = new FaultTolerantChunkProcessor<T, S>(getItemProcessor(), getItemWriter(), batchRetryTemplate);
chunkProcessor.setBuffering(!isReaderTransactionalQueue);
chunkProcessor.setWriteSkipPolicy(writeSkipPolicy);
chunkProcessor.setProcessSkipPolicy(writeSkipPolicy);
chunkProcessor.setRollbackClassifier(rollbackClassifier);
chunkProcessor.setListeners(BatchListenerFactoryHelper.<ItemProcessListener<T,S>>getListeners(getListeners(), ItemProcessListener.class));
chunkProcessor.setListeners(BatchListenerFactoryHelper.<ItemWriteListener<S>>getListeners(getListeners(), ItemWriteListener.class));
chunkProcessor.setListeners(BatchListenerFactoryHelper.<SkipListener<T,S>>getListeners(getListeners(), SkipListener.class));
ChunkOrientedTasklet<T> tasklet = new ChunkOrientedTasklet<T>(chunkProvider, chunkProcessor);
tasklet.setBuffering(!isReaderTransactionalQueue);
step.setTasklet(tasklet);
}
}
public void addFatalExceptionIfMissing(Class<? extends Throwable> cls) {
List<Class<? extends Throwable>> fatalExceptionList = new ArrayList<Class<? extends Throwable>>();
for (Class<? extends Throwable> exceptionClass : fatalExceptionClasses) {
fatalExceptionList.add(exceptionClass);
}
if (!fatalExceptionList.contains(cls)) {
fatalExceptionList.add(cls);
}
fatalExceptionClasses = fatalExceptionList;
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import java.util.List;

View File

@@ -1,114 +1,130 @@
package org.springframework.batch.core.step.item;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.listener.MulticasterBatchListener;
import org.springframework.batch.core.step.skip.SkipListenerFailedException;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.RepeatStatus;
/**
*
* @author Dave Syer
*
* @param <I> input item type
*/
public class SimpleChunkProvider<I> implements ChunkProvider<I> {
protected final Log logger = LogFactory.getLog(getClass());
protected final ItemReader<? extends I> itemReader;
private final MulticasterBatchListener<I, ?> listener = new MulticasterBatchListener<I, Object>();
private final RepeatOperations repeatOperations;
public SimpleChunkProvider(ItemReader<? extends I> itemReader, RepeatOperations repeatOperations) {
this.itemReader = itemReader;
this.repeatOperations = repeatOperations;
}
/**
* Register some {@link StepListener}s with the handler. Each will get the
* callbacks in the order specified at the correct stage.
*
* @param listeners
*/
public void setListeners(List<? extends StepListener> listeners) {
for (StepListener listener : listeners) {
registerListener(listener);
}
}
/**
* Register a listener for callbacks at the appropriate stages in a process.
*
* @param listener a {@link StepListener}
*/
public void registerListener(StepListener listener) {
this.listener.register(listener);
}
/**
* Surrounds the read call with listener callbacks.
* @return item
* @throws Exception
*/
protected final I doRead() throws Exception {
try {
listener.beforeRead();
I item = itemReader.read();
listener.afterRead(item);
return item;
}
catch (Exception e) {
listener.onReadError(e);
throw e;
}
}
public Chunk<I> provide(final StepContribution contribution) throws Exception {
final Chunk<I> inputs = new Chunk<I>();
repeatOperations.iterate(new RepeatCallback() {
public RepeatStatus doInIteration(final RepeatContext context) throws Exception {
I item = read(contribution, inputs);
if (item == null) {
inputs.setEnd();
return RepeatStatus.FINISHED;
}
inputs.add(item);
contribution.incrementReadCount();
return RepeatStatus.CONTINUABLE;
}
});
return inputs;
}
public void postProcess(StepContribution contribution, Chunk<I> chunk) {
for (Exception e : chunk.getErrors()) {
try {
listener.onSkipInRead(e);
}
catch (RuntimeException ex) {
throw new SkipListenerFailedException("Fatal exception in SkipListener.", ex, e);
}
}
}
protected I read(StepContribution contribution, Chunk<I> chunk) throws Exception {
return doRead();
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.listener.MulticasterBatchListener;
import org.springframework.batch.core.step.skip.SkipListenerFailedException;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.repeat.RepeatCallback;
import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.batch.repeat.RepeatStatus;
/**
*
* @author Dave Syer
*
* @param <I> input item type
*/
public class SimpleChunkProvider<I> implements ChunkProvider<I> {
protected final Log logger = LogFactory.getLog(getClass());
protected final ItemReader<? extends I> itemReader;
private final MulticasterBatchListener<I, ?> listener = new MulticasterBatchListener<I, Object>();
private final RepeatOperations repeatOperations;
public SimpleChunkProvider(ItemReader<? extends I> itemReader, RepeatOperations repeatOperations) {
this.itemReader = itemReader;
this.repeatOperations = repeatOperations;
}
/**
* Register some {@link StepListener}s with the handler. Each will get the
* callbacks in the order specified at the correct stage.
*
* @param listeners
*/
public void setListeners(List<? extends StepListener> listeners) {
for (StepListener listener : listeners) {
registerListener(listener);
}
}
/**
* Register a listener for callbacks at the appropriate stages in a process.
*
* @param listener a {@link StepListener}
*/
public void registerListener(StepListener listener) {
this.listener.register(listener);
}
/**
* Surrounds the read call with listener callbacks.
* @return item
* @throws Exception
*/
protected final I doRead() throws Exception {
try {
listener.beforeRead();
I item = itemReader.read();
listener.afterRead(item);
return item;
}
catch (Exception e) {
listener.onReadError(e);
throw e;
}
}
public Chunk<I> provide(final StepContribution contribution) throws Exception {
final Chunk<I> inputs = new Chunk<I>();
repeatOperations.iterate(new RepeatCallback() {
public RepeatStatus doInIteration(final RepeatContext context) throws Exception {
I item = read(contribution, inputs);
if (item == null) {
inputs.setEnd();
return RepeatStatus.FINISHED;
}
inputs.add(item);
contribution.incrementReadCount();
return RepeatStatus.CONTINUABLE;
}
});
return inputs;
}
public void postProcess(StepContribution contribution, Chunk<I> chunk) {
for (Exception e : chunk.getErrors()) {
try {
listener.onSkipInRead(e);
}
catch (RuntimeException ex) {
throw new SkipListenerFailedException("Fatal exception in SkipListener.", ex, e);
}
}
}
protected I read(StepContribution contribution, Chunk<I> chunk) throws Exception {
return doRead();
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.item;
/**

View File

@@ -1,11 +1,27 @@
package org.springframework.batch.core.step.skip;
import org.springframework.core.NestedRuntimeException;
public class NonSkippableReadException extends NestedRuntimeException {
public NonSkippableReadException(String msg, Throwable cause) {
super(msg, cause);
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.skip;
import org.springframework.core.NestedRuntimeException;
public class NonSkippableReadException extends NestedRuntimeException {
public NonSkippableReadException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -1,40 +1,56 @@
package org.springframework.batch.core.step.tasklet;
import java.util.Map;
import org.springframework.batch.core.ExitStatus;
import org.springframework.util.Assert;
/**
* Maps exit codes to {@link org.springframework.batch.core.ExitStatus}
* according to injected map. The injected map is required to contain a value
* for 'else' key, this value will be returned if the injected map
* does not contain value for the exit code returned by the system process.
*
* @author Robert Kasanicky
*/
public class ConfigurableSystemProcessExitCodeMapper implements SystemProcessExitCodeMapper {
public static final String ELSE_KEY = "else";
private Map<Object, ExitStatus> mappings;
public ExitStatus getExitStatus(int exitCode) {
ExitStatus exitStatus = mappings.get(exitCode);
if (exitStatus != null) {
return exitStatus;
} else {
return mappings.get(ELSE_KEY);
}
}
/**
* @param mappings <code>Integer</code> exit code keys to
* {@link org.springframework.batch.core.ExitStatus} values.
*/
public void setMappings(Map<Object, ExitStatus> mappings) {
Assert.notNull(mappings.get(ELSE_KEY));
this.mappings = mappings;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.tasklet;
import java.util.Map;
import org.springframework.batch.core.ExitStatus;
import org.springframework.util.Assert;
/**
* Maps exit codes to {@link org.springframework.batch.core.ExitStatus}
* according to injected map. The injected map is required to contain a value
* for 'else' key, this value will be returned if the injected map
* does not contain value for the exit code returned by the system process.
*
* @author Robert Kasanicky
*/
public class ConfigurableSystemProcessExitCodeMapper implements SystemProcessExitCodeMapper {
public static final String ELSE_KEY = "else";
private Map<Object, ExitStatus> mappings;
public ExitStatus getExitStatus(int exitCode) {
ExitStatus exitStatus = mappings.get(exitCode);
if (exitStatus != null) {
return exitStatus;
} else {
return mappings.get(ELSE_KEY);
}
}
/**
* @param mappings <code>Integer</code> exit code keys to
* {@link org.springframework.batch.core.ExitStatus} values.
*/
public void setMappings(Map<Object, ExitStatus> mappings) {
Assert.notNull(mappings.get(ELSE_KEY));
this.mappings = mappings;
}
}

View File

@@ -1,22 +1,38 @@
package org.springframework.batch.core.step.tasklet;
import org.springframework.batch.core.ExitStatus;
/**
* Simple {@link SystemProcessExitCodeMapper} implementation that performs following mapping:
*
* 0 -> ExitStatus.FINISHED
* else -> ExitStatus.FAILED
*
* @author Robert Kasanicky
*/
public class SimpleSystemProcessExitCodeMapper implements SystemProcessExitCodeMapper {
public ExitStatus getExitStatus(int exitCode) {
if (exitCode == 0) {
return ExitStatus.FINISHED;
} else {
return ExitStatus.FAILED;
}
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.tasklet;
import org.springframework.batch.core.ExitStatus;
/**
* Simple {@link SystemProcessExitCodeMapper} implementation that performs following mapping:
*
* 0 -> ExitStatus.FINISHED
* else -> ExitStatus.FAILED
*
* @author Robert Kasanicky
*/
public class SimpleSystemProcessExitCodeMapper implements SystemProcessExitCodeMapper {
public ExitStatus getExitStatus(int exitCode) {
if (exitCode == 0) {
return ExitStatus.FINISHED;
} else {
return ExitStatus.FAILED;
}
}
}

View File

@@ -1,18 +1,34 @@
package org.springframework.batch.core.step.tasklet;
/**
* Exception indicating failed execution of system command.
*/
public class SystemCommandException extends RuntimeException {
// generated
private static final long serialVersionUID = 5139355923336176733L;
public SystemCommandException(String message) {
super(message);
}
public SystemCommandException(String message, Throwable cause) {
super(message, cause);
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.tasklet;
/**
* Exception indicating failed execution of system command.
*/
public class SystemCommandException extends RuntimeException {
// generated
private static final long serialVersionUID = 5139355923336176733L;
public SystemCommandException(String message) {
super(message);
}
public SystemCommandException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,199 +1,215 @@
package org.springframework.batch.core.step.tasklet;
import java.io.File;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.util.Assert;
/**
* {@link Tasklet} that executes a system command.
*
* The system command is executed asynchronously using injected
* {@link #setTaskExecutor(TaskExecutor)} - timeout value is required to be set,
* so that the batch job does not hang forever if the external process hangs.
*
* Tasklet periodically checks for termination status (i.e.
* {@link #setCommand(String)} finished its execution or
* {@link #setTimeout(long)} expired or job was interrupted). The check interval
* is given by {@link #setTerminationCheckInterval(long)}.
*
* When job interrupt is detected tasklet's execution is terminated immediately
* by throwing {@link JobInterruptedException}.
*
* {@link #setInterruptOnCancel(boolean)} specifies whether the tasklet should
* attempt to interrupt the thread that executes the system command if it is
* still running when tasklet exits (abnormally).
*
* @author Robert Kasanicky
*/
public class SystemCommandTasklet extends StepExecutionListenerSupport implements Tasklet, InitializingBean {
protected static final Log logger = LogFactory.getLog(SystemCommandTasklet.class);
private String command;
private String[] environmentParams = null;
private File workingDirectory = null;
private SystemProcessExitCodeMapper systemProcessExitCodeMapper = new SimpleSystemProcessExitCodeMapper();
private long timeout = 0;
private long checkInterval = 1000;
private StepExecution execution = null;
private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
private boolean interruptOnCancel = false;
/**
* Execute system command and map its exit code to {@link ExitStatus} using
* {@link SystemProcessExitCodeMapper}.
*/
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
FutureTask<Integer> systemCommandTask = new FutureTask<Integer>(new Callable<Integer>() {
public Integer call() throws Exception {
Process process = Runtime.getRuntime().exec(command, environmentParams, workingDirectory);
return process.waitFor();
}
});
StopWatch stopWatch = new StopWatch();
stopWatch.start();
taskExecutor.execute(systemCommandTask);
try {
while (true) {
Thread.sleep(checkInterval);
if (systemCommandTask.isDone()) {
contribution.setExitStatus(systemProcessExitCodeMapper.getExitStatus(systemCommandTask.get()));
return RepeatStatus.FINISHED;
}
else if (stopWatch.getTime() > timeout) {
systemCommandTask.cancel(interruptOnCancel);
throw new SystemCommandException("Execution of system command did not finish within the timeout");
}
else if (execution.isTerminateOnly()) {
systemCommandTask.cancel(interruptOnCancel);
throw new JobInterruptedException("Job interrupted while executing system command '" + command
+ "'");
}
}
}
finally {
stopWatch.stop();
}
}
/**
* @param command command to be executed in a separate system process
*/
public void setCommand(String command) {
this.command = command;
}
/**
* @param envp environment parameter values, inherited from parent process
* when not set (or set to null).
*/
public void setEnvironmentParams(String[] envp) {
this.environmentParams = envp;
}
/**
* @param dir working directory of the spawned process, inherited from
* parent process when not set (or set to null).
*/
public void setWorkingDirectory(String dir) {
if (dir == null) {
this.workingDirectory = null;
return;
}
this.workingDirectory = new File(dir);
Assert.isTrue(workingDirectory.exists(), "working directory must exist");
Assert.isTrue(workingDirectory.isDirectory(), "working directory value must be a directory");
}
public void afterPropertiesSet() throws Exception {
Assert.hasLength(command, "'command' property value is required");
Assert.notNull(systemProcessExitCodeMapper, "SystemProcessExitCodeMapper must be set");
Assert.isTrue(timeout > 0, "timeout value must be greater than zero");
Assert.notNull(taskExecutor, "taskExecutor is required");
}
/**
* @param systemProcessExitCodeMapper maps system process return value to
* <code>ExitStatus</code> returned by Tasklet.
* {@link SimpleSystemProcessExitCodeMapper} is used by default.
*/
public void setSystemProcessExitCodeMapper(SystemProcessExitCodeMapper systemProcessExitCodeMapper) {
this.systemProcessExitCodeMapper = systemProcessExitCodeMapper;
}
/**
* @param timeout upper limit for how long the execution of the external
* program is allowed to last.
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* The time interval how often the tasklet will check for termination
* status.
*
* @param checkInterval time interval in milliseconds (1 second by default).
*/
public void setTerminationCheckInterval(long checkInterval) {
this.checkInterval = checkInterval;
}
/**
* Get a reference to {@link StepExecution} for interrupt checks during
* system command execution.
*/
@Override
public void beforeStep(StepExecution stepExecution) {
this.execution = stepExecution;
}
/**
* Sets the task executor that will be used to execute the system command
* NB! Avoid using a synchronous task executor
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
/**
* If <code>true</code> tasklet will attempt to interrupt the thread
* executing the system command if {@link #setTimeout(long)} has been
* exceeded or user interrupts the job. <code>false</code> by default
*/
public void setInterruptOnCancel(boolean interruptOnCancel) {
this.interruptOnCancel = interruptOnCancel;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.tasklet;
import java.io.File;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.util.Assert;
/**
* {@link Tasklet} that executes a system command.
*
* The system command is executed asynchronously using injected
* {@link #setTaskExecutor(TaskExecutor)} - timeout value is required to be set,
* so that the batch job does not hang forever if the external process hangs.
*
* Tasklet periodically checks for termination status (i.e.
* {@link #setCommand(String)} finished its execution or
* {@link #setTimeout(long)} expired or job was interrupted). The check interval
* is given by {@link #setTerminationCheckInterval(long)}.
*
* When job interrupt is detected tasklet's execution is terminated immediately
* by throwing {@link JobInterruptedException}.
*
* {@link #setInterruptOnCancel(boolean)} specifies whether the tasklet should
* attempt to interrupt the thread that executes the system command if it is
* still running when tasklet exits (abnormally).
*
* @author Robert Kasanicky
*/
public class SystemCommandTasklet extends StepExecutionListenerSupport implements Tasklet, InitializingBean {
protected static final Log logger = LogFactory.getLog(SystemCommandTasklet.class);
private String command;
private String[] environmentParams = null;
private File workingDirectory = null;
private SystemProcessExitCodeMapper systemProcessExitCodeMapper = new SimpleSystemProcessExitCodeMapper();
private long timeout = 0;
private long checkInterval = 1000;
private StepExecution execution = null;
private TaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
private boolean interruptOnCancel = false;
/**
* Execute system command and map its exit code to {@link ExitStatus} using
* {@link SystemProcessExitCodeMapper}.
*/
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
FutureTask<Integer> systemCommandTask = new FutureTask<Integer>(new Callable<Integer>() {
public Integer call() throws Exception {
Process process = Runtime.getRuntime().exec(command, environmentParams, workingDirectory);
return process.waitFor();
}
});
StopWatch stopWatch = new StopWatch();
stopWatch.start();
taskExecutor.execute(systemCommandTask);
try {
while (true) {
Thread.sleep(checkInterval);
if (systemCommandTask.isDone()) {
contribution.setExitStatus(systemProcessExitCodeMapper.getExitStatus(systemCommandTask.get()));
return RepeatStatus.FINISHED;
}
else if (stopWatch.getTime() > timeout) {
systemCommandTask.cancel(interruptOnCancel);
throw new SystemCommandException("Execution of system command did not finish within the timeout");
}
else if (execution.isTerminateOnly()) {
systemCommandTask.cancel(interruptOnCancel);
throw new JobInterruptedException("Job interrupted while executing system command '" + command
+ "'");
}
}
}
finally {
stopWatch.stop();
}
}
/**
* @param command command to be executed in a separate system process
*/
public void setCommand(String command) {
this.command = command;
}
/**
* @param envp environment parameter values, inherited from parent process
* when not set (or set to null).
*/
public void setEnvironmentParams(String[] envp) {
this.environmentParams = envp;
}
/**
* @param dir working directory of the spawned process, inherited from
* parent process when not set (or set to null).
*/
public void setWorkingDirectory(String dir) {
if (dir == null) {
this.workingDirectory = null;
return;
}
this.workingDirectory = new File(dir);
Assert.isTrue(workingDirectory.exists(), "working directory must exist");
Assert.isTrue(workingDirectory.isDirectory(), "working directory value must be a directory");
}
public void afterPropertiesSet() throws Exception {
Assert.hasLength(command, "'command' property value is required");
Assert.notNull(systemProcessExitCodeMapper, "SystemProcessExitCodeMapper must be set");
Assert.isTrue(timeout > 0, "timeout value must be greater than zero");
Assert.notNull(taskExecutor, "taskExecutor is required");
}
/**
* @param systemProcessExitCodeMapper maps system process return value to
* <code>ExitStatus</code> returned by Tasklet.
* {@link SimpleSystemProcessExitCodeMapper} is used by default.
*/
public void setSystemProcessExitCodeMapper(SystemProcessExitCodeMapper systemProcessExitCodeMapper) {
this.systemProcessExitCodeMapper = systemProcessExitCodeMapper;
}
/**
* @param timeout upper limit for how long the execution of the external
* program is allowed to last.
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* The time interval how often the tasklet will check for termination
* status.
*
* @param checkInterval time interval in milliseconds (1 second by default).
*/
public void setTerminationCheckInterval(long checkInterval) {
this.checkInterval = checkInterval;
}
/**
* Get a reference to {@link StepExecution} for interrupt checks during
* system command execution.
*/
@Override
public void beforeStep(StepExecution stepExecution) {
this.execution = stepExecution;
}
/**
* Sets the task executor that will be used to execute the system command
* NB! Avoid using a synchronous task executor
*/
public void setTaskExecutor(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
/**
* If <code>true</code> tasklet will attempt to interrupt the thread
* executing the system command if {@link #setTimeout(long)} has been
* exceeded or user interrupts the job. <code>false</code> by default
*/
public void setInterruptOnCancel(boolean interruptOnCancel) {
this.interruptOnCancel = interruptOnCancel;
}
}

View File

@@ -1,20 +1,36 @@
package org.springframework.batch.core.step.tasklet;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.step.tasklet.SystemCommandTasklet;
/**
* Maps the exit code of a system process to ExitStatus value
* returned by a system command. Designed for use with the
* {@link SystemCommandTasklet}.
*
* @author Robert Kasanicky
*/
public interface SystemProcessExitCodeMapper {
/**
* @param exitCode exit code returned by the system process
* @return ExitStatus appropriate for the <code>systemExitCode</code> parameter value
*/
ExitStatus getExitStatus(int exitCode);
}
/*
* Copyright 2006-2007 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 org.springframework.batch.core.step.tasklet;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.step.tasklet.SystemCommandTasklet;
/**
* Maps the exit code of a system process to ExitStatus value
* returned by a system command. Designed for use with the
* {@link SystemCommandTasklet}.
*
* @author Robert Kasanicky
*/
public interface SystemProcessExitCodeMapper {
/**
* @param exitCode exit code returned by the system process
* @return ExitStatus appropriate for the <code>systemExitCode</code> parameter value
*/
ExitStatus getExitStatus(int exitCode);
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item;
/**

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.adapter;
import org.springframework.util.MethodInvoker;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.database;
import java.util.List;

View File

@@ -1,6 +1,19 @@
/**
*
/*
* Copyright 2006-2007 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 org.springframework.batch.item.database.support;
import java.sql.PreparedStatement;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.database.support;
import java.util.List;

View File

@@ -1,18 +1,34 @@
package org.springframework.batch.item.file;
import java.io.Writer;
import java.io.IOException;
/**
* Callback interface for writing a footer to a file.
*
* @author Robert Kasanicky
*/
public interface FlatFileFooterCallback {
/**
* Write contents to a file using the supplied {@link Writer}. It is not
* required to flush the writer inside this method.
*/
void writeFooter(Writer writer) throws IOException;
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
import java.io.Writer;
import java.io.IOException;
/**
* Callback interface for writing a footer to a file.
*
* @author Robert Kasanicky
*/
public interface FlatFileFooterCallback {
/**
* Write contents to a file using the supplied {@link Writer}. It is not
* required to flush the writer inside this method.
*/
void writeFooter(Writer writer) throws IOException;
}

View File

@@ -1,18 +1,34 @@
package org.springframework.batch.item.file;
import java.io.Writer;
import java.io.IOException;
/**
* Callback interface for writing to a header to a file.
*
* @author Robert Kasanicky
*/
public interface FlatFileHeaderCallback {
/**
* Write contents to a file using the supplied {@link Writer}. It is not
* required to flush the writer inside this method.
*/
void writeHeader(Writer writer) throws IOException;
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
import java.io.Writer;
import java.io.IOException;
/**
* Callback interface for writing to a header to a file.
*
* @author Robert Kasanicky
*/
public interface FlatFileHeaderCallback {
/**
* Write contents to a file using the supplied {@link Writer}. It is not
* required to flush the writer inside this method.
*/
void writeHeader(Writer writer) throws IOException;
}

View File

@@ -1,227 +1,243 @@
package org.springframework.batch.item.file;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ReaderNotOpenException;
import org.springframework.batch.item.file.mapping.LineMapper;
import org.springframework.batch.item.file.separator.RecordSeparatorPolicy;
import org.springframework.batch.item.file.separator.SimpleRecordSeparatorPolicy;
import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Restartable {@link ItemReader} that reads lines from input
* {@link #setResource(Resource)}. Line is defined by the
* {@link #setRecordSeparatorPolicy(RecordSeparatorPolicy)} and mapped to item
* using {@link #setLineMapper(LineMapper)}.
*
* @author Robert Kasanicky
*/
public class FlatFileItemReader<T> extends AbstractItemCountingItemStreamItemReader<T> implements
ResourceAwareItemReaderItemStream<T>, InitializingBean {
private static final Log logger = LogFactory.getLog(FlatFileItemReader.class);
// default encoding for input files
public static final String DEFAULT_CHARSET = Charset.defaultCharset().name();
private RecordSeparatorPolicy recordSeparatorPolicy = new SimpleRecordSeparatorPolicy();
private Resource resource;
private BufferedReader reader;
private int lineCount = 0;
private String[] comments = new String[] { "#" };
private boolean noInput = false;
private String encoding = DEFAULT_CHARSET;
private LineMapper<T> lineMapper;
private int linesToSkip = 0;
private LineCallbackHandler skippedLinesCallback;
public FlatFileItemReader() {
setName(ClassUtils.getShortName(FlatFileItemReader.class));
}
/**
* @param skippedLinesCallback will be called for each one of the initial skipped
* lines before any items are read.
*/
public void setSkippedLinesCallback(LineCallbackHandler skippedLinesCallback) {
this.skippedLinesCallback = skippedLinesCallback;
}
/**
* Public setter for the number of lines to skip at the start of a file. Can
* be used if the file contains a header without useful (column name)
* information, and without a comment delimiter at the beginning of the
* lines.
*
* @param linesToSkip the number of lines to skip
*/
public void setLinesToSkip(int linesToSkip) {
this.linesToSkip = linesToSkip;
}
/**
* Setter for line mapper. This property is required to be set.
* @param lineMapper maps line to item
*/
public void setLineMapper(LineMapper<T> lineMapper) {
this.lineMapper = lineMapper;
}
/**
* Setter for the encoding for this input source. Default value is
* {@link #DEFAULT_CHARSET}.
*
* @param encoding a properties object which possibly contains the encoding
* for this input file;
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* Setter for comment prefixes. Can be used to ignore header lines as well
* by using e.g. the first couple of column names as a prefix.
*
* @param comments an array of comment line prefixes.
*/
public void setComments(String[] comments) {
this.comments = new String[comments.length];
System.arraycopy(comments, 0, this.comments, 0, comments.length);
}
/**
* Public setter for the input resource.
*/
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* Public setter for the recordSeparatorPolicy. Used to determine where the
* line endings are and do things like continue over a line ending if inside
* a quoted string.
*
* @param recordSeparatorPolicy the recordSeparatorPolicy to set
*/
public void setRecordSeparatorPolicy(RecordSeparatorPolicy recordSeparatorPolicy) {
this.recordSeparatorPolicy = recordSeparatorPolicy;
}
/**
* @return string corresponding to logical record according to
* {@link #setRecordSeparatorPolicy(RecordSeparatorPolicy)} (might span
* multiple lines in file).
*/
@Override
protected T doRead() throws Exception {
if (noInput) {
return null;
}
String line = readLine();
String record = line;
if (line != null) {
while (line != null && !recordSeparatorPolicy.isEndOfRecord(record)) {
record = recordSeparatorPolicy.preProcess(record) + (line = readLine());
}
}
String logicalLine = recordSeparatorPolicy.postProcess(record);
if (logicalLine == null) {
return null;
}
else {
return lineMapper.mapLine(logicalLine, lineCount);
}
}
/**
* @return next line (skip comments).
*/
private String readLine() {
if (reader == null) {
throw new ReaderNotOpenException("Reader must be open before it can be read.");
}
String line = null;
try {
line = this.reader.readLine();
if (line == null) {
return null;
}
lineCount++;
while (isComment(line)) {
line = reader.readLine();
if (line == null) {
return null;
}
lineCount++;
}
}
catch (IOException e) {
throw new FlatFileParseException("Unable to read from resource: [" + resource + "]", e, line, lineCount );
}
return line;
}
private boolean isComment(String line) {
for (String prefix : comments) {
if (line.startsWith(prefix)) {
return true;
}
}
return false;
}
@Override
protected void doClose() throws Exception {
lineCount = 0;
reader.close();
}
@Override
protected void doOpen() throws Exception {
Assert.notNull(resource, "Input resource must be set");
Assert.notNull(recordSeparatorPolicy, "RecordSeparatorPolicy must be set");
noInput = false;
if (!resource.exists()) {
noInput = true;
logger.warn("Input resource does not exist");
return;
}
reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), encoding));
for (int i = 0; i < linesToSkip; i++) {
String line = readLine();
if (skippedLinesCallback != null) {
skippedLinesCallback.handleLine(line);
}
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(lineMapper, "LineMapper is required");
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ReaderNotOpenException;
import org.springframework.batch.item.file.mapping.LineMapper;
import org.springframework.batch.item.file.separator.RecordSeparatorPolicy;
import org.springframework.batch.item.file.separator.SimpleRecordSeparatorPolicy;
import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Restartable {@link ItemReader} that reads lines from input
* {@link #setResource(Resource)}. Line is defined by the
* {@link #setRecordSeparatorPolicy(RecordSeparatorPolicy)} and mapped to item
* using {@link #setLineMapper(LineMapper)}.
*
* @author Robert Kasanicky
*/
public class FlatFileItemReader<T> extends AbstractItemCountingItemStreamItemReader<T> implements
ResourceAwareItemReaderItemStream<T>, InitializingBean {
private static final Log logger = LogFactory.getLog(FlatFileItemReader.class);
// default encoding for input files
public static final String DEFAULT_CHARSET = Charset.defaultCharset().name();
private RecordSeparatorPolicy recordSeparatorPolicy = new SimpleRecordSeparatorPolicy();
private Resource resource;
private BufferedReader reader;
private int lineCount = 0;
private String[] comments = new String[] { "#" };
private boolean noInput = false;
private String encoding = DEFAULT_CHARSET;
private LineMapper<T> lineMapper;
private int linesToSkip = 0;
private LineCallbackHandler skippedLinesCallback;
public FlatFileItemReader() {
setName(ClassUtils.getShortName(FlatFileItemReader.class));
}
/**
* @param skippedLinesCallback will be called for each one of the initial skipped
* lines before any items are read.
*/
public void setSkippedLinesCallback(LineCallbackHandler skippedLinesCallback) {
this.skippedLinesCallback = skippedLinesCallback;
}
/**
* Public setter for the number of lines to skip at the start of a file. Can
* be used if the file contains a header without useful (column name)
* information, and without a comment delimiter at the beginning of the
* lines.
*
* @param linesToSkip the number of lines to skip
*/
public void setLinesToSkip(int linesToSkip) {
this.linesToSkip = linesToSkip;
}
/**
* Setter for line mapper. This property is required to be set.
* @param lineMapper maps line to item
*/
public void setLineMapper(LineMapper<T> lineMapper) {
this.lineMapper = lineMapper;
}
/**
* Setter for the encoding for this input source. Default value is
* {@link #DEFAULT_CHARSET}.
*
* @param encoding a properties object which possibly contains the encoding
* for this input file;
*/
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* Setter for comment prefixes. Can be used to ignore header lines as well
* by using e.g. the first couple of column names as a prefix.
*
* @param comments an array of comment line prefixes.
*/
public void setComments(String[] comments) {
this.comments = new String[comments.length];
System.arraycopy(comments, 0, this.comments, 0, comments.length);
}
/**
* Public setter for the input resource.
*/
public void setResource(Resource resource) {
this.resource = resource;
}
/**
* Public setter for the recordSeparatorPolicy. Used to determine where the
* line endings are and do things like continue over a line ending if inside
* a quoted string.
*
* @param recordSeparatorPolicy the recordSeparatorPolicy to set
*/
public void setRecordSeparatorPolicy(RecordSeparatorPolicy recordSeparatorPolicy) {
this.recordSeparatorPolicy = recordSeparatorPolicy;
}
/**
* @return string corresponding to logical record according to
* {@link #setRecordSeparatorPolicy(RecordSeparatorPolicy)} (might span
* multiple lines in file).
*/
@Override
protected T doRead() throws Exception {
if (noInput) {
return null;
}
String line = readLine();
String record = line;
if (line != null) {
while (line != null && !recordSeparatorPolicy.isEndOfRecord(record)) {
record = recordSeparatorPolicy.preProcess(record) + (line = readLine());
}
}
String logicalLine = recordSeparatorPolicy.postProcess(record);
if (logicalLine == null) {
return null;
}
else {
return lineMapper.mapLine(logicalLine, lineCount);
}
}
/**
* @return next line (skip comments).
*/
private String readLine() {
if (reader == null) {
throw new ReaderNotOpenException("Reader must be open before it can be read.");
}
String line = null;
try {
line = this.reader.readLine();
if (line == null) {
return null;
}
lineCount++;
while (isComment(line)) {
line = reader.readLine();
if (line == null) {
return null;
}
lineCount++;
}
}
catch (IOException e) {
throw new FlatFileParseException("Unable to read from resource: [" + resource + "]", e, line, lineCount );
}
return line;
}
private boolean isComment(String line) {
for (String prefix : comments) {
if (line.startsWith(prefix)) {
return true;
}
}
return false;
}
@Override
protected void doClose() throws Exception {
lineCount = 0;
reader.close();
}
@Override
protected void doOpen() throws Exception {
Assert.notNull(resource, "Input resource must be set");
Assert.notNull(recordSeparatorPolicy, "RecordSeparatorPolicy must be set");
noInput = false;
if (!resource.exists()) {
noInput = true;
logger.warn("Input resource does not exist");
return;
}
reader = new BufferedReader(new InputStreamReader(resource.getInputStream(), encoding));
for (int i = 0; i < linesToSkip; i++) {
String line = readLine();
if (skippedLinesCallback != null) {
skippedLinesCallback.handleLine(line);
}
}
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(lineMapper, "LineMapper is required");
}
}

View File

@@ -1,12 +1,28 @@
package org.springframework.batch.item.file;
/**
* Callback interface for handling a line from file. Useful e.g. for header
* processing.
*
* @author Robert Kasanicky
*/
public interface LineCallbackHandler {
void handleLine(String line);
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
/**
* Callback interface for handling a line from file. Useful e.g. for header
* processing.
*
* @author Robert Kasanicky
*/
public interface LineCallbackHandler {
void handleLine(String line);
}

View File

@@ -1,245 +1,261 @@
package org.springframework.batch.item.file;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.NoWorkFoundException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.item.util.ExecutionContextUserSupport;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Reads items from multiple resources sequentially - resource list is given by
* {@link #setResources(Resource[])}, the actual reading is delegated to
* {@link #setDelegate(ResourceAwareItemReaderItemStream)}.
*
* Input resources are ordered using {@link #setComparator(Comparator)} to make
* sure resource ordering is preserved between job runs in restart scenario.
*
* Reset (rollback) capability is implemented by item buffering.
*
*
* @author Robert Kasanicky
*/
public class MultiResourceItemReader<T> implements ItemReader<T>, ItemStream {
private static final Log logger = LogFactory.getLog(MultiResourceItemReader.class);
private final ExecutionContextUserSupport executionContextUserSupport = new ExecutionContextUserSupport();
private ResourceAwareItemReaderItemStream<? extends T> delegate;
private Resource[] resources;
private MultiResourceIndex index = new MultiResourceIndex();
private boolean saveState = true;
// signals there are no resources to read -> just return null on first read
private boolean noInput;
private Comparator<Resource> comparator = new Comparator<Resource>() {
/**
* Compares resource filenames.
*/
public int compare(Resource r1, Resource r2) {
return r1.getFilename().compareTo(r2.getFilename());
}
};
public MultiResourceItemReader() {
executionContextUserSupport.setName(ClassUtils.getShortName(MultiResourceItemReader.class));
}
/**
* Reads the next item, jumping to next resource if necessary.
*/
public T read() throws Exception, UnexpectedInputException, NoWorkFoundException, ParseException {
if (noInput) {
return null;
}
T item;
item = readNextItem();
index.incrementItemCount();
return item;
}
/**
* Use the delegate to read the next item, jump to next resource if current
* one is exhausted. Items are appended to the buffer.
* @return next item from input
*/
private T readNextItem() throws Exception {
T item = delegate.read();
while (item == null) {
index.incrementResourceCount();
if (index.currentResource >= resources.length) {
return null;
}
delegate.close();
delegate.setResource(resources[index.currentResource]);
delegate.open(new ExecutionContext());
item = delegate.read();
}
return item;
}
/**
* Close the {@link #setDelegate(ResourceAwareItemReaderItemStream)} reader
* and reset instance variable values.
*/
public void close() throws ItemStreamException {
index = new MultiResourceIndex();
delegate.close();
noInput = false;
}
/**
* Figure out which resource to start with in case of restart, open the
* delegate and restore delegate's position in the resource.
*/
public void open(ExecutionContext executionContext) throws ItemStreamException {
Assert.notNull(resources, "Resources must be set");
noInput = false;
if (resources.length == 0) {
logger.warn("No resources to read");
noInput = true;
return;
}
Arrays.sort(resources, comparator);
index.open(executionContext);
delegate.setResource(resources[index.currentResource]);
delegate.open(new ExecutionContext());
try {
for (int i = 0; i < index.currentItem; i++) {
delegate.read();
}
}
catch (Exception e) {
throw new ItemStreamException("Could not restore position on restart", e);
}
}
/**
* Store the current resource index and position in the resource.
*/
public void update(ExecutionContext executionContext) throws ItemStreamException {
if (saveState) {
index.update(executionContext);
}
}
/**
* @param delegate reads items from single {@link Resource}.
*/
public void setDelegate(ResourceAwareItemReaderItemStream<? extends T> delegate) {
this.delegate = delegate;
}
/**
* Set the boolean indicating whether or not state should be saved in the
* provided {@link ExecutionContext} during the {@link ItemStream} call to
* update.
*
* @param saveState
*/
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
/**
* @param comparator used to order the injected resources, by default
* compares {@link Resource#getFilename()} values.
*/
public void setComparator(Comparator<Resource> comparator) {
this.comparator = comparator;
}
/**
* @param resources input resources
*/
public void setResources(Resource[] resources) {
this.resources = resources;
}
/**
* Facilitates keeping track of the position within multi-resource input.
*/
private class MultiResourceIndex {
private static final String RESOURCE_KEY = "resourceIndex";
private static final String ITEM_KEY = "itemIndex";
private int currentResource = 0;
private int markedResource = 0;
private int currentItem = 0;
private int markedItem = 0;
public void incrementItemCount() {
currentItem++;
}
public void incrementResourceCount() {
currentResource++;
currentItem = 0;
}
public void mark() {
markedResource = currentResource;
markedItem = currentItem;
}
public void reset() {
currentResource = markedResource;
currentItem = markedItem;
}
public void open(ExecutionContext ctx) {
if (ctx.containsKey(executionContextUserSupport.getKey(RESOURCE_KEY))) {
currentResource = ctx.getInt(executionContextUserSupport.getKey(RESOURCE_KEY));
}
if (ctx.containsKey(executionContextUserSupport.getKey(ITEM_KEY))) {
currentItem = ctx.getInt(executionContextUserSupport.getKey(ITEM_KEY));
}
}
public void update(ExecutionContext ctx) {
ctx.putInt(executionContextUserSupport.getKey(RESOURCE_KEY), index.currentResource);
ctx.putInt(executionContextUserSupport.getKey(ITEM_KEY), index.currentItem);
}
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
import java.util.Arrays;
import java.util.Comparator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.NoWorkFoundException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.item.util.ExecutionContextUserSupport;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Reads items from multiple resources sequentially - resource list is given by
* {@link #setResources(Resource[])}, the actual reading is delegated to
* {@link #setDelegate(ResourceAwareItemReaderItemStream)}.
*
* Input resources are ordered using {@link #setComparator(Comparator)} to make
* sure resource ordering is preserved between job runs in restart scenario.
*
* Reset (rollback) capability is implemented by item buffering.
*
*
* @author Robert Kasanicky
*/
public class MultiResourceItemReader<T> implements ItemReader<T>, ItemStream {
private static final Log logger = LogFactory.getLog(MultiResourceItemReader.class);
private final ExecutionContextUserSupport executionContextUserSupport = new ExecutionContextUserSupport();
private ResourceAwareItemReaderItemStream<? extends T> delegate;
private Resource[] resources;
private MultiResourceIndex index = new MultiResourceIndex();
private boolean saveState = true;
// signals there are no resources to read -> just return null on first read
private boolean noInput;
private Comparator<Resource> comparator = new Comparator<Resource>() {
/**
* Compares resource filenames.
*/
public int compare(Resource r1, Resource r2) {
return r1.getFilename().compareTo(r2.getFilename());
}
};
public MultiResourceItemReader() {
executionContextUserSupport.setName(ClassUtils.getShortName(MultiResourceItemReader.class));
}
/**
* Reads the next item, jumping to next resource if necessary.
*/
public T read() throws Exception, UnexpectedInputException, NoWorkFoundException, ParseException {
if (noInput) {
return null;
}
T item;
item = readNextItem();
index.incrementItemCount();
return item;
}
/**
* Use the delegate to read the next item, jump to next resource if current
* one is exhausted. Items are appended to the buffer.
* @return next item from input
*/
private T readNextItem() throws Exception {
T item = delegate.read();
while (item == null) {
index.incrementResourceCount();
if (index.currentResource >= resources.length) {
return null;
}
delegate.close();
delegate.setResource(resources[index.currentResource]);
delegate.open(new ExecutionContext());
item = delegate.read();
}
return item;
}
/**
* Close the {@link #setDelegate(ResourceAwareItemReaderItemStream)} reader
* and reset instance variable values.
*/
public void close() throws ItemStreamException {
index = new MultiResourceIndex();
delegate.close();
noInput = false;
}
/**
* Figure out which resource to start with in case of restart, open the
* delegate and restore delegate's position in the resource.
*/
public void open(ExecutionContext executionContext) throws ItemStreamException {
Assert.notNull(resources, "Resources must be set");
noInput = false;
if (resources.length == 0) {
logger.warn("No resources to read");
noInput = true;
return;
}
Arrays.sort(resources, comparator);
index.open(executionContext);
delegate.setResource(resources[index.currentResource]);
delegate.open(new ExecutionContext());
try {
for (int i = 0; i < index.currentItem; i++) {
delegate.read();
}
}
catch (Exception e) {
throw new ItemStreamException("Could not restore position on restart", e);
}
}
/**
* Store the current resource index and position in the resource.
*/
public void update(ExecutionContext executionContext) throws ItemStreamException {
if (saveState) {
index.update(executionContext);
}
}
/**
* @param delegate reads items from single {@link Resource}.
*/
public void setDelegate(ResourceAwareItemReaderItemStream<? extends T> delegate) {
this.delegate = delegate;
}
/**
* Set the boolean indicating whether or not state should be saved in the
* provided {@link ExecutionContext} during the {@link ItemStream} call to
* update.
*
* @param saveState
*/
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
/**
* @param comparator used to order the injected resources, by default
* compares {@link Resource#getFilename()} values.
*/
public void setComparator(Comparator<Resource> comparator) {
this.comparator = comparator;
}
/**
* @param resources input resources
*/
public void setResources(Resource[] resources) {
this.resources = resources;
}
/**
* Facilitates keeping track of the position within multi-resource input.
*/
private class MultiResourceIndex {
private static final String RESOURCE_KEY = "resourceIndex";
private static final String ITEM_KEY = "itemIndex";
private int currentResource = 0;
private int markedResource = 0;
private int currentItem = 0;
private int markedItem = 0;
public void incrementItemCount() {
currentItem++;
}
public void incrementResourceCount() {
currentResource++;
currentItem = 0;
}
public void mark() {
markedResource = currentResource;
markedItem = currentItem;
}
public void reset() {
currentResource = markedResource;
currentItem = markedItem;
}
public void open(ExecutionContext ctx) {
if (ctx.containsKey(executionContextUserSupport.getKey(RESOURCE_KEY))) {
currentResource = ctx.getInt(executionContextUserSupport.getKey(RESOURCE_KEY));
}
if (ctx.containsKey(executionContextUserSupport.getKey(ITEM_KEY))) {
currentItem = ctx.getInt(executionContextUserSupport.getKey(ITEM_KEY));
}
}
public void update(ExecutionContext ctx) {
ctx.putInt(executionContextUserSupport.getKey(RESOURCE_KEY), index.currentResource);
ctx.putInt(executionContextUserSupport.getKey(ITEM_KEY), index.currentItem);
}
}
}

View File

@@ -1,141 +1,157 @@
package org.springframework.batch.item.file;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.util.ExecutionContextUserSupport;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Wraps a {@link ResourceAwareItemWriterItemStream} and creates a new output
* resource when the count of items written in current resource exceeds
* {@link #setItemCountLimitPerResource(int)}. Suffix creation can be customized
* with {@link #setResourceSuffixCreator(ResourceSuffixCreator)}.
*
* Note that new resources are created only at chunk boundaries i.e. the number
* of items written into one resource is between the limit set by
* {@link #setItemCountLimitPerResource(int)} and (limit + chunk size).
*
* @param <T> item type
*
* @author Robert Kasanicky
*/
public class MultiResourceItemWriter<T> extends ExecutionContextUserSupport implements ItemWriter<T>, ItemStream {
final static private String RESOURCE_INDEX_KEY = "resource.index";
final static private String CURRENT_RESOURCE_ITEM_COUNT = "resource.item.count";
private Resource resource;
private ResourceAwareItemWriterItemStream<? super T> delegate;
private int itemCountLimitPerResource = Integer.MAX_VALUE;
private int currentResourceItemCount = 0;
private int resourceIndex = 1;
private ResourceSuffixCreator suffixCreator = new SimpleResourceSuffixCreator();
private boolean saveState = true;
public MultiResourceItemWriter() {
setName(ClassUtils.getShortName(MultiResourceItemWriter.class));
}
public void write(List<? extends T> items) throws Exception {
if (currentResourceItemCount >= itemCountLimitPerResource) {
delegate.close();
resourceIndex++;
currentResourceItemCount = 0;
setResourceToDelegate();
delegate.open(new ExecutionContext());
}
delegate.write(items);
currentResourceItemCount += items.size();
}
/**
* Allows customization of the suffix of the created resources based on the
* index.
*/
public void setResourceSuffixCreator(ResourceSuffixCreator suffixCreator) {
this.suffixCreator = suffixCreator;
}
/**
* After this limit is exceeded the next chunk will be written into newly
* created resource.
*/
public void setItemCountLimitPerResource(int itemCountLimitPerResource) {
this.itemCountLimitPerResource = itemCountLimitPerResource;
}
/**
* Delegate used for actual writing of the output.
*/
public void setDelegate(ResourceAwareItemWriterItemStream<? super T> delegate) {
this.delegate = delegate;
}
/**
* Prototype for output resources. Actual output files will be created in
* the same directory and use the same name as this prototype with appended
* suffix (according to
* {@link #setResourceSuffixCreator(ResourceSuffixCreator)}.
*/
public void setResource(Resource resource) {
this.resource = resource;
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
public void close() throws ItemStreamException {
resourceIndex = 1;
currentResourceItemCount = 0;
delegate.close();
}
public void open(ExecutionContext executionContext) throws ItemStreamException {
resourceIndex = executionContext.getInt(getKey(RESOURCE_INDEX_KEY), 1);
currentResourceItemCount = executionContext.getInt(getKey(CURRENT_RESOURCE_ITEM_COUNT), 0);
try {
setResourceToDelegate();
}
catch (IOException e) {
throw new ItemStreamException("Couldn't open resource", e);
}
delegate.open(executionContext);
}
public void update(ExecutionContext executionContext) throws ItemStreamException {
if (saveState) {
delegate.update(executionContext);
executionContext.putInt(getKey(CURRENT_RESOURCE_ITEM_COUNT), currentResourceItemCount);
executionContext.putInt(getKey(RESOURCE_INDEX_KEY), resourceIndex);
}
}
/**
* Create output resource (if necessary) and point the delegate to it.
*/
private void setResourceToDelegate() throws IOException {
String path = resource.getFile().getAbsolutePath() + suffixCreator.getSuffix(resourceIndex);
File file = new File(path);
file.createNewFile();
Assert.state(file.canWrite(), "Output resource " + path + " must be writable");
delegate.setResource(new FileSystemResource(file));
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.util.ExecutionContextUserSupport;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Wraps a {@link ResourceAwareItemWriterItemStream} and creates a new output
* resource when the count of items written in current resource exceeds
* {@link #setItemCountLimitPerResource(int)}. Suffix creation can be customized
* with {@link #setResourceSuffixCreator(ResourceSuffixCreator)}.
*
* Note that new resources are created only at chunk boundaries i.e. the number
* of items written into one resource is between the limit set by
* {@link #setItemCountLimitPerResource(int)} and (limit + chunk size).
*
* @param <T> item type
*
* @author Robert Kasanicky
*/
public class MultiResourceItemWriter<T> extends ExecutionContextUserSupport implements ItemWriter<T>, ItemStream {
final static private String RESOURCE_INDEX_KEY = "resource.index";
final static private String CURRENT_RESOURCE_ITEM_COUNT = "resource.item.count";
private Resource resource;
private ResourceAwareItemWriterItemStream<? super T> delegate;
private int itemCountLimitPerResource = Integer.MAX_VALUE;
private int currentResourceItemCount = 0;
private int resourceIndex = 1;
private ResourceSuffixCreator suffixCreator = new SimpleResourceSuffixCreator();
private boolean saveState = true;
public MultiResourceItemWriter() {
setName(ClassUtils.getShortName(MultiResourceItemWriter.class));
}
public void write(List<? extends T> items) throws Exception {
if (currentResourceItemCount >= itemCountLimitPerResource) {
delegate.close();
resourceIndex++;
currentResourceItemCount = 0;
setResourceToDelegate();
delegate.open(new ExecutionContext());
}
delegate.write(items);
currentResourceItemCount += items.size();
}
/**
* Allows customization of the suffix of the created resources based on the
* index.
*/
public void setResourceSuffixCreator(ResourceSuffixCreator suffixCreator) {
this.suffixCreator = suffixCreator;
}
/**
* After this limit is exceeded the next chunk will be written into newly
* created resource.
*/
public void setItemCountLimitPerResource(int itemCountLimitPerResource) {
this.itemCountLimitPerResource = itemCountLimitPerResource;
}
/**
* Delegate used for actual writing of the output.
*/
public void setDelegate(ResourceAwareItemWriterItemStream<? super T> delegate) {
this.delegate = delegate;
}
/**
* Prototype for output resources. Actual output files will be created in
* the same directory and use the same name as this prototype with appended
* suffix (according to
* {@link #setResourceSuffixCreator(ResourceSuffixCreator)}.
*/
public void setResource(Resource resource) {
this.resource = resource;
}
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
public void close() throws ItemStreamException {
resourceIndex = 1;
currentResourceItemCount = 0;
delegate.close();
}
public void open(ExecutionContext executionContext) throws ItemStreamException {
resourceIndex = executionContext.getInt(getKey(RESOURCE_INDEX_KEY), 1);
currentResourceItemCount = executionContext.getInt(getKey(CURRENT_RESOURCE_ITEM_COUNT), 0);
try {
setResourceToDelegate();
}
catch (IOException e) {
throw new ItemStreamException("Couldn't open resource", e);
}
delegate.open(executionContext);
}
public void update(ExecutionContext executionContext) throws ItemStreamException {
if (saveState) {
delegate.update(executionContext);
executionContext.putInt(getKey(CURRENT_RESOURCE_ITEM_COUNT), currentResourceItemCount);
executionContext.putInt(getKey(RESOURCE_INDEX_KEY), resourceIndex);
}
}
/**
* Create output resource (if necessary) and point the delegate to it.
*/
private void setResourceToDelegate() throws IOException {
String path = resource.getFile().getAbsolutePath() + suffixCreator.getSuffix(resourceIndex);
File file = new File(path);
file.createNewFile();
Assert.state(file.canWrite(), "Output resource " + path + " must be writable");
delegate.setResource(new FileSystemResource(file));
}
}

View File

@@ -1,16 +1,32 @@
package org.springframework.batch.item.file;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.core.io.Resource;
/**
* Interface for {@link ItemReader}s that implement {@link ItemStream} and read
* input from {@link Resource}.
*
* @author Robert Kasanicky
*/
public interface ResourceAwareItemReaderItemStream<T> extends ItemReader<T>, ItemStream {
void setResource(Resource resource);
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.core.io.Resource;
/**
* Interface for {@link ItemReader}s that implement {@link ItemStream} and read
* input from {@link Resource}.
*
* @author Robert Kasanicky
*/
public interface ResourceAwareItemReaderItemStream<T> extends ItemReader<T>, ItemStream {
void setResource(Resource resource);
}

View File

@@ -1,16 +1,32 @@
package org.springframework.batch.item.file;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemWriter;
import org.springframework.core.io.Resource;
/**
* Interface for {@link ItemWriter}s that implement {@link ItemStream} and write
* output to {@link Resource}.
*
* @author Robert Kasanicky
*/
public interface ResourceAwareItemWriterItemStream<T> extends ItemStream, ItemWriter<T> {
void setResource(Resource resource);
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemWriter;
import org.springframework.core.io.Resource;
/**
* Interface for {@link ItemWriter}s that implement {@link ItemStream} and write
* output to {@link Resource}.
*
* @author Robert Kasanicky
*/
public interface ResourceAwareItemWriterItemStream<T> extends ItemStream, ItemWriter<T> {
void setResource(Resource resource);
}

View File

@@ -1,15 +1,31 @@
package org.springframework.batch.item.file;
/**
* Strategy interface for translating resource index into unique filename
* suffix.
*
* @see MultiResourceItemWriter
* @see SimpleResourceSuffixCreator
*
* @author Robert Kasanicky
*/
public interface ResourceSuffixCreator {
String getSuffix(int index);
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
/**
* Strategy interface for translating resource index into unique filename
* suffix.
*
* @see MultiResourceItemWriter
* @see SimpleResourceSuffixCreator
*
* @author Robert Kasanicky
*/
public interface ResourceSuffixCreator {
String getSuffix(int index);
}

View File

@@ -1,15 +1,31 @@
package org.springframework.batch.item.file;
/**
* Trivial implementation of {@link ResourceSuffixCreator} that uses the index
* itself as suffix, separated by dot.
*
* @author Robert Kasanicky
*/
public class SimpleResourceSuffixCreator implements ResourceSuffixCreator {
public String getSuffix(int index) {
return "." + index;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file;
/**
* Trivial implementation of {@link ResourceSuffixCreator} that uses the index
* itself as suffix, separated by dot.
*
* @author Robert Kasanicky
*/
public class SimpleResourceSuffixCreator implements ResourceSuffixCreator {
public String getSuffix(int index) {
return "." + index;
}
}

View File

@@ -1,39 +1,55 @@
package org.springframework.batch.item.file.mapping;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.batch.item.file.transform.LineTokenizer;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Two-phase {@link LineMapper} implementation consisting of tokenization of the
* line into {@link FieldSet} followed by mapping to item.
*
* @author Robert Kasanicky
*
* @param <T> type of the item
*/
public class DefaultLineMapper<T> implements LineMapper<T>, InitializingBean {
private LineTokenizer tokenizer;
private FieldSetMapper<T> fieldSetMapper;
public T mapLine(String line, int lineNumber) throws Exception {
return fieldSetMapper.mapFieldSet(tokenizer.tokenize(line));
}
public void setLineTokenizer(LineTokenizer tokenizer) {
this.tokenizer = tokenizer;
}
public void setFieldSetMapper(FieldSetMapper<T> fieldSetMapper) {
this.fieldSetMapper = fieldSetMapper;
}
public void afterPropertiesSet() {
Assert.notNull(tokenizer, "The LineTokenizer must be set");
Assert.notNull(fieldSetMapper, "The FieldSetMapper must be set");
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file.mapping;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.batch.item.file.transform.LineTokenizer;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Two-phase {@link LineMapper} implementation consisting of tokenization of the
* line into {@link FieldSet} followed by mapping to item.
*
* @author Robert Kasanicky
*
* @param <T> type of the item
*/
public class DefaultLineMapper<T> implements LineMapper<T>, InitializingBean {
private LineTokenizer tokenizer;
private FieldSetMapper<T> fieldSetMapper;
public T mapLine(String line, int lineNumber) throws Exception {
return fieldSetMapper.mapFieldSet(tokenizer.tokenize(line));
}
public void setLineTokenizer(LineTokenizer tokenizer) {
this.tokenizer = tokenizer;
}
public void setFieldSetMapper(FieldSetMapper<T> fieldSetMapper) {
this.fieldSetMapper = fieldSetMapper;
}
public void afterPropertiesSet() {
Assert.notNull(tokenizer, "The LineTokenizer must be set");
Assert.notNull(fieldSetMapper, "The FieldSetMapper must be set");
}
}

View File

@@ -1,31 +1,47 @@
package org.springframework.batch.item.file.mapping;
import org.springframework.batch.item.file.transform.LineTokenizer;
/**
* Interface for mapping lines (strings) to domain objects typically used to map lines read from a file to domain objects
* on a per line basis. Implementations of this interface perform the actual
* work of parsing a line without having to deal with how the line was
* obtained.
*
* @author Robert Kasanicky
* @param <T> type of the domain object
* @see FieldSetMapper
* @see LineTokenizer
* @since 2.0
*/
public interface LineMapper<T> {
/**
* Implementations must implement this method to map the provided line to
* the parameter type T. The line number represents the number of lines
* into a file the current line resides.
*
* @param line to be mapped
* @param lineNumber of the current line
* @return mapped object of type T
* @throws Exception if error occured while parsing.
*/
T mapLine(String line, int lineNumber) throws Exception;
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file.mapping;
import org.springframework.batch.item.file.transform.LineTokenizer;
/**
* Interface for mapping lines (strings) to domain objects typically used to map lines read from a file to domain objects
* on a per line basis. Implementations of this interface perform the actual
* work of parsing a line without having to deal with how the line was
* obtained.
*
* @author Robert Kasanicky
* @param <T> type of the domain object
* @see FieldSetMapper
* @see LineTokenizer
* @since 2.0
*/
public interface LineMapper<T> {
/**
* Implementations must implement this method to map the provided line to
* the parameter type T. The line number represents the number of lines
* into a file the current line resides.
*
* @param line to be mapped
* @param lineNumber of the current line
* @return mapped object of type T
* @throws Exception if error occured while parsing.
*/
T mapLine(String line, int lineNumber) throws Exception;
}

View File

@@ -1,9 +1,25 @@
package org.springframework.batch.item.file.mapping;
public class PassThroughLineMapper implements LineMapper<String>{
public String mapLine(String line, int lineNumber) throws Exception {
return line;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file.mapping;
public class PassThroughLineMapper implements LineMapper<String>{
public String mapLine(String line, int lineNumber) throws Exception {
return line;
}
}

View File

@@ -1,15 +1,31 @@
package org.springframework.batch.item.file.transform;
public class PassThroughLineAggregator<T> implements LineAggregator<T> {
/**
* Simply convert to a String with toString().
*
* @see org.springframework.batch.item.file.transform.LineAggregator#aggregate(java.lang.Object)
*/
public String aggregate(T item) {
return item.toString();
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file.transform;
public class PassThroughLineAggregator<T> implements LineAggregator<T> {
/**
* Simply convert to a String with toString().
*
* @see org.springframework.batch.item.file.transform.LineAggregator#aggregate(java.lang.Object)
*/
public String aggregate(T item) {
return item.toString();
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file.transform;
import org.springframework.util.Assert;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file.transform;
import java.beans.PropertyEditorSupport;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.file.transform;
import java.util.Collection;

View File

@@ -1,129 +1,145 @@
package org.springframework.batch.item.support;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.item.util.ExecutionContextUserSupport;
import org.springframework.util.Assert;
/**
* Abstract superclass for {@link ItemReader}s that supports restart by storing
* item count in the {@link ExecutionContext} (therefore requires item ordering
* to be preserved between runs).
*
* Subclasses are inherently *not* thread-safe.
*
* @author Robert Kasanicky
*/
public abstract class AbstractItemCountingItemStreamItemReader<T> implements ItemReader<T>, ItemStream {
private static final String READ_COUNT = "read.count";
private int currentItemCount = 0;
private ExecutionContextUserSupport ecSupport = new ExecutionContextUserSupport();
private boolean saveState = true;
/**
* Read next item from input.
* @return item
* @throws Exception
*/
protected abstract T doRead() throws Exception;
/**
* Open resources necessary to start reading input.
*/
protected abstract void doOpen() throws Exception;
/**
* Close the resources opened in {@link #doOpen()}.
*/
protected abstract void doClose() throws Exception;
/**
* Move to the given item index. Subclasses should override this method if
* there is a more efficient way of moving to given index than re-reading
* the input using {@link #doRead()}.
*/
protected void jumpToItem(int itemIndex) throws Exception {
for (int i = 0; i < itemIndex; i++) {
doRead();
}
}
public T read() throws Exception, UnexpectedInputException, ParseException {
currentItemCount++;
return doRead();
}
protected int getCurrentItemCount() {
return currentItemCount;
}
protected void setCurrentItemCount(int count) {
this.currentItemCount = count;
}
public void close() throws ItemStreamException {
currentItemCount = 0;
try {
doClose();
}
catch (Exception e) {
throw new ItemStreamException("Error while closing item reader", e);
}
}
public void open(ExecutionContext executionContext) throws ItemStreamException {
try {
doOpen();
}
catch (Exception e) {
throw new ItemStreamException("Failed to initialize the reader", e);
}
if (executionContext.containsKey(ecSupport.getKey(READ_COUNT))) {
int itemCount = executionContext.getInt(ecSupport.getKey(READ_COUNT));
try {
jumpToItem(itemCount);
}
catch (Exception e) {
throw new ItemStreamException("Could not move to stored position on restart", e);
}
currentItemCount = itemCount;
}
}
public void update(ExecutionContext executionContext) throws ItemStreamException {
if (saveState) {
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putInt(ecSupport.getKey(READ_COUNT), currentItemCount);
}
}
public void setName(String name) {
ecSupport.setName(name);
}
/**
* Set the flag that determines whether to save internal data for
* {@link ExecutionContext}. Only switch this to false if you don't want to
* save any state from this stream, and you don't need it to be restartable.
*
* @param saveState flag value (default true).
*/
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.support;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.item.util.ExecutionContextUserSupport;
import org.springframework.util.Assert;
/**
* Abstract superclass for {@link ItemReader}s that supports restart by storing
* item count in the {@link ExecutionContext} (therefore requires item ordering
* to be preserved between runs).
*
* Subclasses are inherently *not* thread-safe.
*
* @author Robert Kasanicky
*/
public abstract class AbstractItemCountingItemStreamItemReader<T> implements ItemReader<T>, ItemStream {
private static final String READ_COUNT = "read.count";
private int currentItemCount = 0;
private ExecutionContextUserSupport ecSupport = new ExecutionContextUserSupport();
private boolean saveState = true;
/**
* Read next item from input.
* @return item
* @throws Exception
*/
protected abstract T doRead() throws Exception;
/**
* Open resources necessary to start reading input.
*/
protected abstract void doOpen() throws Exception;
/**
* Close the resources opened in {@link #doOpen()}.
*/
protected abstract void doClose() throws Exception;
/**
* Move to the given item index. Subclasses should override this method if
* there is a more efficient way of moving to given index than re-reading
* the input using {@link #doRead()}.
*/
protected void jumpToItem(int itemIndex) throws Exception {
for (int i = 0; i < itemIndex; i++) {
doRead();
}
}
public T read() throws Exception, UnexpectedInputException, ParseException {
currentItemCount++;
return doRead();
}
protected int getCurrentItemCount() {
return currentItemCount;
}
protected void setCurrentItemCount(int count) {
this.currentItemCount = count;
}
public void close() throws ItemStreamException {
currentItemCount = 0;
try {
doClose();
}
catch (Exception e) {
throw new ItemStreamException("Error while closing item reader", e);
}
}
public void open(ExecutionContext executionContext) throws ItemStreamException {
try {
doOpen();
}
catch (Exception e) {
throw new ItemStreamException("Failed to initialize the reader", e);
}
if (executionContext.containsKey(ecSupport.getKey(READ_COUNT))) {
int itemCount = executionContext.getInt(ecSupport.getKey(READ_COUNT));
try {
jumpToItem(itemCount);
}
catch (Exception e) {
throw new ItemStreamException("Could not move to stored position on restart", e);
}
currentItemCount = itemCount;
}
}
public void update(ExecutionContext executionContext) throws ItemStreamException {
if (saveState) {
Assert.notNull(executionContext, "ExecutionContext must not be null");
executionContext.putInt(ecSupport.getKey(READ_COUNT), currentItemCount);
}
}
public void setName(String name) {
ecSupport.setName(name);
}
/**
* Set the flag that determines whether to save internal data for
* {@link ExecutionContext}. Only switch this to false if you don't want to
* save any state from this stream, and you don't need it to be restartable.
*
* @param saveState flag value (default true).
*/
public void setSaveState(boolean saveState) {
this.saveState = saveState;
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.support;
import java.util.List;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.support;
import java.util.Arrays;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.support;
import org.springframework.batch.item.ItemProcessor;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.util;
import java.io.File;

View File

@@ -1,42 +1,58 @@
package org.springframework.batch.item.validator;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.util.Assert;
/**
* Simple implementation of {@link ItemProcessor} validates and input and
* returns it without modifications.
*
* @author Robert Kasanicky
*
*/
public class ValidatingItemProcessor<T> implements ItemProcessor<T, T> {
private Validator validator;
public ValidatingItemProcessor(Validator validator){
Assert.notNull(validator, "Validator must not be null.");
this.validator = validator;
}
/**
* Set the validator used to validate each item.
*
* @param validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* Validate the item and return it unmodified
*
* @return the input item
* @throws ValidationException if validation fails
*/
public T process(T item) throws ValidationException {
validator.validate(item);
return item;
}
}
/*
* Copyright 2006-2007 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 org.springframework.batch.item.validator;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.util.Assert;
/**
* Simple implementation of {@link ItemProcessor} validates and input and
* returns it without modifications.
*
* @author Robert Kasanicky
*
*/
public class ValidatingItemProcessor<T> implements ItemProcessor<T, T> {
private Validator validator;
public ValidatingItemProcessor(Validator validator){
Assert.notNull(validator, "Validator must not be null.");
this.validator = validator;
}
/**
* Set the validator used to validate each item.
*
* @param validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* Validate the item and return it unmodified
*
* @return the input item
* @throws ValidationException if validation fails
*/
public T process(T item) throws ValidationException {
validator.validate(item);
return item;
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.xml;
import java.io.InputStream;

View File

@@ -1,3 +1,19 @@
/*
* Copyright 2006-2007 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 org.springframework.batch.item.xml;
import java.io.File;

Some files were not shown because too many files have changed in this diff Show More