RESOLVED - issue BATCH-165: delegating ItemProvider and ItemProcessor
http://opensource.atlassian.com/projects/spring/browse/BATCH-165 Patch applied from JIRA.
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
<configs>
|
||||
<config>src/test/resources/org/springframework/batch/io/file/support/mapping/bean-wrapper.xml</config>
|
||||
<config>src/test/resources/org/springframework/batch/io/sql/data-source-context.xml</config>
|
||||
<config>src/test/resources/org/springframework/batch/item/processor/delegating-item-processor.xml</config>
|
||||
<config>src/test/resources/org/springframework/batch/item/processor/pe-delegating-item-processor.xml</config>
|
||||
<config>src/test/resources/org/springframework/batch/item/provider/delegating-item-provider.xml</config>
|
||||
<config>src/test/resources/org/springframework/batch/retry/aop/retry-transaction-test.xml</config>
|
||||
</configs>
|
||||
<configSets>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.springframework.batch.io.exception;
|
||||
|
||||
import org.springframework.util.MethodInvoker;
|
||||
|
||||
/**
|
||||
* Indicates an error has been encountered
|
||||
* while trying to dynamically call a method e.g. using {@link MethodInvoker}.
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class DynamicMethodInvocationException extends RuntimeException {
|
||||
|
||||
//generated value
|
||||
private static final long serialVersionUID = -6056786139731564040L;
|
||||
|
||||
public DynamicMethodInvocationException(Throwable cause){
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DynamicMethodInvocationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.processor;
|
||||
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.support.AbstractDelegator;
|
||||
|
||||
|
||||
/**
|
||||
* Delegates item processing to a custom method -
|
||||
* passes the item as an argument for the delegate method.
|
||||
*
|
||||
* @see PropertyExtractingDelegatingItemProcessor
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class DelegatingItemProcessor extends AbstractDelegator implements ItemProcessor {
|
||||
|
||||
public void process(Object item) throws Exception {
|
||||
invokeDelegateMethodWithArgument(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.processor;
|
||||
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.support.AbstractDelegator;
|
||||
import org.springframework.beans.BeanWrapper;
|
||||
import org.springframework.beans.BeanWrapperImpl;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Delegates processing to a custom method - extracts property values
|
||||
* from item object and uses them as arguments for the delegate method.
|
||||
*
|
||||
* @see DelegatingItemProcessor
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class PropertyExtractingDelegatingItemProcessor extends AbstractDelegator implements ItemProcessor {
|
||||
|
||||
private String[] fieldsUsedAsTargetMethodArguments;
|
||||
|
||||
/**
|
||||
* Extracts values from item's fields named in fieldsUsedAsTargetMethodArguments
|
||||
* and passes them as arguments to the delegate method.
|
||||
*/
|
||||
public void process(Object item) throws Exception {
|
||||
// helper for extracting property values from a bean
|
||||
BeanWrapper beanWrapper = new BeanWrapperImpl();
|
||||
beanWrapper.setWrappedInstance(item);
|
||||
|
||||
Object[] methodArguments = new Object[fieldsUsedAsTargetMethodArguments.length];
|
||||
for (int i = 0; i < fieldsUsedAsTargetMethodArguments.length; i++) {
|
||||
methodArguments[i] = beanWrapper.getPropertyValue(fieldsUsedAsTargetMethodArguments[i]);
|
||||
}
|
||||
|
||||
invokeDelegateMethodWithArguments(methodArguments);
|
||||
}
|
||||
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
super.afterPropertiesSet();
|
||||
Assert.notEmpty(fieldsUsedAsTargetMethodArguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fieldsUsedAsTargetMethodArguments the values of the these item's fields
|
||||
* will be used as arguments for the delegate method. Nested property values are
|
||||
* supported, e.g. <code>address.city</code>
|
||||
*/
|
||||
public void setFieldsUsedAsTargetMethodArguments(String[] fieldsUsedAsMethodArguments) {
|
||||
this.fieldsUsedAsTargetMethodArguments = fieldsUsedAsMethodArguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.provider;
|
||||
|
||||
import org.springframework.batch.item.ItemProvider;
|
||||
import org.springframework.batch.support.AbstractDelegator;
|
||||
|
||||
/**
|
||||
* Invokes a custom method which provides an item.
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class DelegatingItemProvider extends AbstractDelegator implements ItemProvider {
|
||||
|
||||
/**
|
||||
* @return return value of the target method.
|
||||
*/
|
||||
public Object next() throws Exception {
|
||||
return invokeDelegateMethod();
|
||||
}
|
||||
|
||||
//harmless implementation of method required by ItemProvider interface
|
||||
public Object getKey(Object item) {
|
||||
return item;
|
||||
}
|
||||
|
||||
//harmless implementation of method required by ItemProvider interface
|
||||
public boolean recover(Object data, Throwable cause) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.support;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.batch.io.exception.DynamicMethodInvocationException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MethodInvoker;
|
||||
|
||||
/**
|
||||
* Superclass for delegating classes which dynamically call a
|
||||
* custom method of injected object.
|
||||
* Provides convenient API for dynamic method invocation shielding
|
||||
* subclasses from low-level details and exception handling.
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class AbstractDelegator implements InitializingBean {
|
||||
|
||||
private Object targetObject;
|
||||
|
||||
private String targetMethod;
|
||||
|
||||
private Object[] arguments;
|
||||
|
||||
/**
|
||||
* Invoker the target method with no arguments.
|
||||
* @return object returned by invoked method
|
||||
* @throws DynamicMethodInvocationException if the {@link MethodInvoker} used throws exception
|
||||
*/
|
||||
protected Object invokeDelegateMethod() {
|
||||
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
|
||||
invoker.setArguments(arguments);
|
||||
return doInvoke(invoker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the target method with given argument.
|
||||
* @param object argument for the target method
|
||||
* @return object returned by target method
|
||||
* @throws DynamicMethodInvocationException if the {@link MethodInvoker} used throws exception
|
||||
*/
|
||||
protected Object invokeDelegateMethodWithArgument(Object object) {
|
||||
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
|
||||
invoker.setArguments(new Object[]{object});
|
||||
return doInvoke(invoker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the target method with given arguments.
|
||||
* @param args arguments for the invoked method
|
||||
* @return object returned by invoked method
|
||||
* @throws DynamicMethodInvocationException if the {@link MethodInvoker} used throws exception
|
||||
*/
|
||||
protected Object invokeDelegateMethodWithArguments(Object[] args) {
|
||||
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
|
||||
invoker.setArguments(args);
|
||||
return doInvoke(invoker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new configured instance of {@link MethodInvoker}.
|
||||
*/
|
||||
private MethodInvoker createMethodInvoker(Object targetObject, String targetMethod) {
|
||||
MethodInvoker invoker = new MethodInvoker();
|
||||
invoker.setTargetObject(targetObject);
|
||||
invoker.setTargetMethod(targetMethod);
|
||||
invoker.setArguments(arguments);
|
||||
return invoker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare and invoke the invoker, rethrow checked exceptions as unchecked.
|
||||
* @param invoker configured invoker
|
||||
* @return return value of the invoked method
|
||||
*/
|
||||
private Object doInvoke(MethodInvoker invoker) {
|
||||
try {
|
||||
invoker.prepare();
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new DynamicMethodInvocationException(e);
|
||||
}
|
||||
catch (NoSuchMethodException e) {
|
||||
throw new DynamicMethodInvocationException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
return invoker.invoke();
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new DynamicMethodInvocationException(e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new DynamicMethodInvocationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(targetObject);
|
||||
Assert.hasLength(targetMethod);
|
||||
Assert.state(targetClassDeclaresTargetMethod(),
|
||||
"target class must declare a method with name matching the target method");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if target class declares a method matching target method name
|
||||
* with given number of arguments of appropriate type.
|
||||
*/
|
||||
private boolean targetClassDeclaresTargetMethod() {
|
||||
MethodInvoker invoker = createMethodInvoker(targetObject, targetMethod);
|
||||
Method[] methods = invoker.getTargetClass().getDeclaredMethods();
|
||||
String targetMethodName = invoker.getTargetMethod();
|
||||
|
||||
for (int i=0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(targetMethodName)) {
|
||||
Class[] params = methods[i].getParameterTypes();
|
||||
if (arguments == null) {
|
||||
return true;
|
||||
} else if (arguments.length == params.length) {
|
||||
boolean argumentsMatchParameters = true;
|
||||
for (int j = 0; j < params.length; j++) {
|
||||
if (!(params[j].isAssignableFrom(arguments[j].getClass()))) {
|
||||
argumentsMatchParameters = false;
|
||||
}
|
||||
}
|
||||
if (argumentsMatchParameters) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param targetObject the delegate - bean id can be used to set this value in Spring configuration
|
||||
*/
|
||||
public void setTargetObject(Object targetObject) {
|
||||
this.targetObject = targetObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param targetMethod name of the method to be invoked on {@link #targetObject}.
|
||||
*/
|
||||
public void setTargetMethod(String targetMethod) {
|
||||
this.targetMethod = targetMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param arguments arguments values for the {{@link #targetMethod}.
|
||||
* These are not expected to change during the lifetime of the delegator
|
||||
* and will be used only when the subclass tries to invoke the target method
|
||||
* without providing explicit argument values.
|
||||
*/
|
||||
public void setArguments(Object[] arguments) {
|
||||
this.arguments = arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.springframework.batch.io.sample.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Custom class that contains the logic of providing and processing {@link Foo}
|
||||
* objects. It serves the purpose to show how providing/processing logic contained in a
|
||||
* custom class can be reused by the framework.
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class FooService {
|
||||
|
||||
public static final int GENERATION_LIMIT = 10;
|
||||
|
||||
private int counter = 0;
|
||||
private List generatedFoos = new ArrayList(GENERATION_LIMIT);
|
||||
private List processedFoos = new ArrayList(GENERATION_LIMIT);
|
||||
private List processedFooNameValuePairs = new ArrayList(GENERATION_LIMIT);
|
||||
|
||||
public Foo generateFoo() {
|
||||
if (counter++ >= GENERATION_LIMIT) return null;
|
||||
|
||||
Foo foo = new Foo(counter, "foo" + counter, counter);
|
||||
generatedFoos.add(foo);
|
||||
return foo;
|
||||
|
||||
}
|
||||
|
||||
public void processFoo(Foo foo) {
|
||||
processedFoos.add(foo);
|
||||
}
|
||||
|
||||
public void processNameValuePair(String name, int value) {
|
||||
processedFooNameValuePairs.add(new Foo(0, name, value));
|
||||
}
|
||||
|
||||
public List getGeneratedFoos() {
|
||||
return generatedFoos;
|
||||
}
|
||||
|
||||
public List getProcessedFoos() {
|
||||
return processedFoos;
|
||||
}
|
||||
|
||||
public List getProcessedFooNameValuePairs() {
|
||||
return processedFooNameValuePairs;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.springframework.batch.item.processor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.io.sample.domain.Foo;
|
||||
import org.springframework.batch.io.sample.domain.FooService;
|
||||
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
|
||||
|
||||
/**
|
||||
* Tests for {@link DelegatingItemProcessor}.
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class DelegatingItemProcessorIntegrationTests extends AbstractDependencyInjectionSpringContextTests {
|
||||
|
||||
private DelegatingItemProcessor processor;
|
||||
|
||||
private FooService fooService;
|
||||
|
||||
|
||||
protected String getConfigPath() {
|
||||
return "delegating-item-processor.xml";
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular usage scenario - input object should be passed to
|
||||
* the service the injected invoker points to.
|
||||
*/
|
||||
public void testProcess() throws Exception {
|
||||
Foo foo;
|
||||
while ((foo = fooService.generateFoo()) != null) {
|
||||
processor.process(foo);
|
||||
}
|
||||
|
||||
List input = fooService.getGeneratedFoos();
|
||||
List processed = fooService.getProcessedFoos();
|
||||
assertEquals(input.size(), processed.size());
|
||||
assertFalse(fooService.getProcessedFoos().isEmpty());
|
||||
|
||||
for (int i = 0; i < input.size(); i++) {
|
||||
assertSame(input.get(i), processed.get(i));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//setter for auto-injection
|
||||
public void setProcessor(DelegatingItemProcessor processor) {
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
//setter for auto-injection
|
||||
public void setFooService(FooService fooService) {
|
||||
this.fooService = fooService;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.springframework.batch.item.processor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.io.sample.domain.Foo;
|
||||
import org.springframework.batch.io.sample.domain.FooService;
|
||||
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
|
||||
|
||||
/**
|
||||
* Tests for {@link PropertyExtractingDelegatingItemProcessor}
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class PropertyExtractingDelegatingItemProccessorIntegrationTests
|
||||
extends AbstractDependencyInjectionSpringContextTests {
|
||||
|
||||
private PropertyExtractingDelegatingItemProcessor processor;
|
||||
|
||||
private FooService fooService;
|
||||
|
||||
protected String getConfigPath() {
|
||||
return "pe-delegating-item-processor.xml";
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular usage scenario - input object should be passed to
|
||||
* the service the injected invoker points to.
|
||||
*/
|
||||
public void testProcess() throws Exception {
|
||||
Foo foo;
|
||||
while ((foo = fooService.generateFoo()) != null) {
|
||||
processor.process(foo);
|
||||
}
|
||||
|
||||
List input = fooService.getGeneratedFoos();
|
||||
List processed = fooService.getProcessedFooNameValuePairs();
|
||||
assertEquals(input.size(), processed.size());
|
||||
assertFalse(fooService.getProcessedFooNameValuePairs().isEmpty());
|
||||
|
||||
for (int i = 0; i < input.size(); i++) {
|
||||
Foo inputFoo = (Foo) input.get(i);
|
||||
Foo outputFoo = (Foo) processed.get(i);
|
||||
assertEquals(inputFoo.getName(), outputFoo.getName());
|
||||
assertEquals(inputFoo.getValue(), outputFoo.getValue());
|
||||
assertEquals(0, outputFoo.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void setProcessor(PropertyExtractingDelegatingItemProcessor processor) {
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
public void setFooService(FooService fooService) {
|
||||
this.fooService = fooService;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.springframework.batch.item.provider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.io.sample.domain.FooService;
|
||||
import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
|
||||
|
||||
/**
|
||||
* Tests for {@link DelegatingItemProvider}.
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class DelegatingItemProviderIntegrationTests extends AbstractDependencyInjectionSpringContextTests {
|
||||
|
||||
private DelegatingItemProvider provider;
|
||||
|
||||
private FooService fooService;
|
||||
|
||||
|
||||
protected String getConfigPath() {
|
||||
return "delegating-item-provider.xml";
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular usage scenario - items are retrieved from
|
||||
* the service injected invoker points to.
|
||||
*/
|
||||
public void testNext() throws Exception {
|
||||
List returnedItems = new ArrayList();
|
||||
Object item;
|
||||
while ((item = provider.next()) != null) {
|
||||
returnedItems.add(item);
|
||||
}
|
||||
|
||||
List input = fooService.getGeneratedFoos();
|
||||
assertEquals(input.size(), returnedItems.size());
|
||||
assertFalse(returnedItems.isEmpty());
|
||||
|
||||
for (int i = 0; i<input.size(); i++) {
|
||||
assertSame(input.get(i), returnedItems.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getKey(..) is implemented trivially.
|
||||
*/
|
||||
public void testGetKey() {
|
||||
Object item = new Object();
|
||||
assertSame(item, provider.getKey(item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover not supported.
|
||||
*/
|
||||
public void testRecover() {
|
||||
assertFalse(provider.recover(null, null));
|
||||
}
|
||||
|
||||
public void setProvider(DelegatingItemProvider provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public void setFooService(FooService fooService) {
|
||||
this.fooService = fooService;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package org.springframework.batch.support;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.batch.io.exception.DynamicMethodInvocationException;
|
||||
import org.springframework.batch.io.sample.domain.Foo;
|
||||
import org.springframework.batch.io.sample.domain.FooService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Tests for {@link AbstractDelegator}
|
||||
*
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class AbstractDelegatorTests extends TestCase {
|
||||
|
||||
private static class ConcreteDelegator extends AbstractDelegator {
|
||||
}
|
||||
|
||||
private AbstractDelegator delegator = new ConcreteDelegator();
|
||||
|
||||
private Foo foo = new Foo(0, "foo", 1);
|
||||
|
||||
protected void setUp() throws Exception {
|
||||
delegator.setTargetObject(foo);
|
||||
delegator.setArguments(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular use - calling methods directly and via delegator leads to same
|
||||
* results
|
||||
*/
|
||||
public void testDelegation() throws Exception {
|
||||
delegator.setTargetMethod("getName");
|
||||
delegator.afterPropertiesSet();
|
||||
|
||||
assertEquals(foo.getName(), delegator.invokeDelegateMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular use - calling methods directly and via delegator leads to same
|
||||
* results
|
||||
*/
|
||||
public void testDelegationWithArgument() throws Exception {
|
||||
delegator.setTargetMethod("setName");
|
||||
final String NEW_FOO_NAME = "newFooName";
|
||||
delegator.afterPropertiesSet();
|
||||
|
||||
delegator.invokeDelegateMethodWithArgument(NEW_FOO_NAME);
|
||||
assertEquals(NEW_FOO_NAME, foo.getName());
|
||||
|
||||
// using the arguments setter should work equally well
|
||||
foo.setName("foo");
|
||||
Assert.state(!foo.getName().equals(NEW_FOO_NAME));
|
||||
delegator.setArguments(new Object[] { NEW_FOO_NAME });
|
||||
delegator.afterPropertiesSet();
|
||||
delegator.invokeDelegateMethod();
|
||||
assertEquals(NEW_FOO_NAME, foo.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular use - calling methods directly and via delegator leads to same
|
||||
* results
|
||||
*/
|
||||
public void testDelegationWithMultipleArguments() throws Exception {
|
||||
FooService fooService = new FooService();
|
||||
delegator.setTargetObject(fooService);
|
||||
delegator.setTargetMethod("processNameValuePair");
|
||||
delegator.afterPropertiesSet();
|
||||
|
||||
final String FOO_NAME = "fooName";
|
||||
final int FOO_VALUE = 12345;
|
||||
|
||||
delegator.invokeDelegateMethodWithArguments(new Object[]{FOO_NAME, new Integer(FOO_VALUE)});
|
||||
Foo foo = (Foo) fooService.getProcessedFooNameValuePairs().get(0);
|
||||
assertEquals(FOO_NAME, foo.getName());
|
||||
assertEquals(FOO_VALUE, foo.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception scenario - target method is not declared by target object.
|
||||
*/
|
||||
public void testInvalidMethodName() throws Exception {
|
||||
delegator.setTargetMethod("not-existing-method-name");
|
||||
|
||||
try {
|
||||
delegator.afterPropertiesSet();
|
||||
fail();
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
delegator.invokeDelegateMethod();
|
||||
fail();
|
||||
}
|
||||
catch (DynamicMethodInvocationException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception scenario - target method is called with invalid arguments.
|
||||
*/
|
||||
public void testInvalidArgumentsForExistingMethod() throws Exception {
|
||||
delegator.setTargetMethod("setName");
|
||||
delegator.afterPropertiesSet();
|
||||
try {
|
||||
delegator.invokeDelegateMethodWithArgument(new Object());
|
||||
fail();
|
||||
}
|
||||
catch (DynamicMethodInvocationException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception scenario - target method is called with incorrect number of
|
||||
* arguments.
|
||||
*/
|
||||
public void testIncorrectArgumentCount() throws Exception {
|
||||
delegator.setTargetMethod("setName");
|
||||
delegator.afterPropertiesSet();
|
||||
try {
|
||||
// single argument expected but none provided
|
||||
delegator.invokeDelegateMethod();
|
||||
fail();
|
||||
}
|
||||
catch (DynamicMethodInvocationException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
try {
|
||||
// single argument expected but two provided
|
||||
delegator.invokeDelegateMethodWithArguments(new Object[]{"name", "anotherName"});
|
||||
fail();
|
||||
}
|
||||
catch (DynamicMethodInvocationException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception scenario - incorrect static arguments set.
|
||||
*/
|
||||
public void testIncorrectNumberOfStaticArguments() throws Exception {
|
||||
delegator.setTargetMethod("setName");
|
||||
|
||||
// incorrect argument count
|
||||
delegator.setArguments(new Object[]{"first", "second"});
|
||||
try {
|
||||
delegator.afterPropertiesSet();
|
||||
fail();
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
// correct argument count, but invalid argument type
|
||||
delegator.setArguments(new Object[]{new Object()});
|
||||
try {
|
||||
delegator.afterPropertiesSet();
|
||||
fail();
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
|
||||
|
||||
<bean id="processor" class="org.springframework.batch.item.processor.DelegatingItemProcessor">
|
||||
<property name="targetObject" ref="fooService" />
|
||||
<property name="targetMethod" value="processFoo" />
|
||||
</bean>
|
||||
|
||||
<bean id="fooService" class="org.springframework.batch.io.sample.domain.FooService" />
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
|
||||
|
||||
<bean id="processor" class="org.springframework.batch.item.processor.PropertyExtractingDelegatingItemProcessor">
|
||||
<property name="targetObject" ref="fooService" />
|
||||
<property name="targetMethod" value="processNameValuePair" />
|
||||
<property name="fieldsUsedAsTargetMethodArguments" value="name,value" />
|
||||
</bean>
|
||||
|
||||
<bean id="fooService" class="org.springframework.batch.io.sample.domain.FooService" />
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
|
||||
|
||||
<bean id="provider" class="org.springframework.batch.item.provider.DelegatingItemProvider">
|
||||
<property name="targetObject" ref="fooService" />
|
||||
<property name="targetMethod" value="generateFoo" />
|
||||
</bean>
|
||||
|
||||
<bean id="fooService" class="org.springframework.batch.io.sample.domain.FooService" />
|
||||
|
||||
</beans>
|
||||
@@ -1,10 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beansProjectDescription>
|
||||
<version>1</version>
|
||||
<pluginVersion><![CDATA[2.0.2.v200710312100]]></pluginVersion>
|
||||
<configSuffixes>
|
||||
<configSuffix><![CDATA[xml]]></configSuffix>
|
||||
</configSuffixes>
|
||||
<configExtensions>
|
||||
<configExtension>xml</configExtension>
|
||||
</configExtensions>
|
||||
<configs>
|
||||
<config>src/main/resources/jobs/fixedLengthImportJob.xml</config>
|
||||
<config>src/main/resources/jobs/multilineJob.xml</config>
|
||||
@@ -28,6 +26,7 @@
|
||||
<config>src/main/resources/jobs/nflJob.xml</config>
|
||||
<config>src/main/resources/jobs/simpleTaskletJob.xml</config>
|
||||
<config>src/main/resources/beanRefContext.xml</config>
|
||||
<config>src/main/resources/jobs/delegatingJob.xml</config>
|
||||
</configs>
|
||||
<configSets>
|
||||
<configSet>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.sample.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.batch.item.ItemProvider;
|
||||
|
||||
|
||||
/**
|
||||
* Custom class that contains logic that would normally be
|
||||
* be contained in {@link ItemProvider} (<code>getData()</code>) and
|
||||
* {@link ItemProcessor} (<code>processData(..)</code>).
|
||||
*
|
||||
* @author tomas.slanina
|
||||
* @author Robert Kasanicky
|
||||
*/
|
||||
public class PersonService {
|
||||
|
||||
private static final int GENERATION_LIMIT = 10;
|
||||
|
||||
private int generatedCounter = 0;
|
||||
private int processedCounter = 0;
|
||||
|
||||
public Person getData() {
|
||||
if (generatedCounter >= GENERATION_LIMIT) return null;
|
||||
|
||||
Person person = new Person();
|
||||
Address address = new Address();
|
||||
Child child = new Child();
|
||||
List children = new ArrayList(1);
|
||||
|
||||
children.add(child);
|
||||
|
||||
person.setFirstName("John" + generatedCounter);
|
||||
person.setAge(20 + generatedCounter);
|
||||
address.setCity("Johnsville" + generatedCounter);
|
||||
child.setName("Little Johny" + generatedCounter);
|
||||
|
||||
person.setAddress(address);
|
||||
person.setChildren(children);
|
||||
|
||||
generatedCounter++;
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
/**
|
||||
* Badly designed method signature which accepts multiple implicitly related
|
||||
* arguments instead of a single Person argument.
|
||||
*/
|
||||
public void processPerson(String name, String city) {
|
||||
processedCounter++;
|
||||
}
|
||||
|
||||
public int getReturnedCount() {
|
||||
return generatedCounter;
|
||||
}
|
||||
|
||||
public int getReceivedCount() {
|
||||
return processedCounter;
|
||||
}
|
||||
}
|
||||
52
samples/src/main/resources/jobs/delegatingJob.xml
Normal file
52
samples/src/main/resources/jobs/delegatingJob.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
|
||||
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
|
||||
|
||||
<description>
|
||||
The intent is to to give an example of how existing bean
|
||||
definitions (e.g. from custom application's domain layer)
|
||||
can be integrated into a batch job.
|
||||
</description>
|
||||
|
||||
<bean class="org.springframework.batch.execution.configuration.JobConfigurationRegistryBeanPostProcessor">
|
||||
<property name="jobConfigurationRegistry" ref="jobConfigurationRegistry"/>
|
||||
</bean>
|
||||
|
||||
<bean id="jobConfiguration" parent="simpleJob">
|
||||
<property name="steps">
|
||||
<bean id="step1" parent="simpleStep">
|
||||
<property name="tasklet">
|
||||
<bean
|
||||
class="org.springframework.batch.execution.tasklet.ItemProviderProcessTasklet">
|
||||
<property name="itemProvider">
|
||||
<bean class="org.springframework.batch.item.provider.DelegatingItemProvider">
|
||||
<property name="targetObject" ref="delegatingObject" />
|
||||
<property name="targetMethod" value="getData" />
|
||||
</bean>
|
||||
</property>
|
||||
<property name="itemProcessor">
|
||||
<bean class="org.springframework.batch.item.processor.PropertyExtractingDelegatingItemProcessor">
|
||||
<property name="targetObject" ref="delegatingObject" />
|
||||
<property name="targetMethod" value="processPerson" />
|
||||
<property name="fieldsUsedAsTargetMethodArguments">
|
||||
<list>
|
||||
<value>firstName</value>
|
||||
<value>address.city</value>
|
||||
</list>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="delegatingObject" class="org.springframework.batch.sample.domain.PersonService" />
|
||||
|
||||
</beans>
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.springframework.batch.sample;
|
||||
|
||||
import org.springframework.batch.sample.domain.PersonService;
|
||||
|
||||
public class DelegatingJobFunctionalTests extends AbstractValidatingBatchLauncherTests {
|
||||
|
||||
private PersonService personService;
|
||||
|
||||
protected String[] getConfigLocations() {
|
||||
return new String[] {"jobs/delegatingJob.xml"};
|
||||
}
|
||||
|
||||
protected void validatePostConditions() throws Exception {
|
||||
assertTrue(personService.getReturnedCount() > 0);
|
||||
assertEquals(personService.getReturnedCount(), personService.getReceivedCount());
|
||||
|
||||
}
|
||||
|
||||
// setter for auto-injection
|
||||
public void setPersonService(PersonService personService) {
|
||||
this.personService = personService;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user