diff --git a/spring-batch-infrastructure/pom.xml b/spring-batch-infrastructure/pom.xml index 38afe48f4..95f4503ce 100644 --- a/spring-batch-infrastructure/pom.xml +++ b/spring-batch-infrastructure/pom.xml @@ -145,15 +145,17 @@ ${spring.oxm.artifact} true - + org.springframework - spring-aop - true - - - org.springframework - spring-core - + spring-core + + + + + org.springframework + spring-aop + true + org.springframework spring-context diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java index 047bd983b..d143fe3ee 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemReader.java @@ -38,7 +38,6 @@ import org.springframework.core.io.Resource; import org.springframework.oxm.Unmarshaller; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.xml.transform.StaxSource; /** * Item reader for reading XML input based on StAX. @@ -52,8 +51,7 @@ import org.springframework.xml.transform.StaxSource; * * @author Robert Kasanicky */ -public class StaxEventItemReader extends AbstractItemCountingItemStreamItemReader implements - ResourceAwareItemReaderItemStream, InitializingBean { +public class StaxEventItemReader extends AbstractItemCountingItemStreamItemReader implements ResourceAwareItemReaderItemStream, InitializingBean { private static final Log logger = LogFactory.getLog(StaxEventItemReader.class); @@ -232,7 +230,7 @@ public class StaxEventItemReader extends AbstractItemCountingItemStreamItemRe fragmentReader.markStartFragment(); @SuppressWarnings("unchecked") - T mappedFragment = (T) unmarshaller.unmarshal(new StaxSource(fragmentReader)); + T mappedFragment = (T) unmarshaller.unmarshal(StaxUtils.getSource(fragmentReader)); item = mappedFragment; fragmentReader.markFragmentProcessed(); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java index c90c59d58..9f51d5051 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxEventItemWriter.java @@ -31,6 +31,8 @@ import javax.xml.stream.XMLEventFactory; import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; +import javax.xml.transform.Result; +import javax.xml.transform.Source; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -52,7 +54,6 @@ import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import org.springframework.xml.transform.StaxResult; /** * An implementation of {@link ItemWriter} which uses StAX and @@ -563,14 +564,15 @@ public class StaxEventItemWriter extends ExecutionContextUserSupport implemen * @throws IOException * @throws XmlMappingException */ - public void write(List items) throws XmlMappingException, IOException { + public void write(List items) throws XmlMappingException, Exception { currentRecordCount += items.size(); for (Object object : items) { Assert.state(marshaller.supports(object.getClass()), "Marshaller must support the class of the marshalled object"); - marshaller.marshal(object, new StaxResult(eventWriter)); + Result result = StaxUtils.getResult(eventWriter); + marshaller.marshal(object, result ); } try { eventWriter.flush(); diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxUtils.java new file mode 100644 index 000000000..ec2fdf971 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/xml/StaxUtils.java @@ -0,0 +1,146 @@ +/* + * 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 org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLEventWriter; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * This class provides a little bit of indirection to avoid ugly conditional object creation. It is unfortunately + * a bit redundant assuming a Spring 3.0 environment, but is necessary to work with Spring WS 1.5.x. + *

+ * The returned object determines whether the environment has Spring OXM as included in the Spring 3.x series of relies + * or whether it has Spring OXM from Spring WS 1.5x and factories a StaxSource instance appropriately. + *

+ * As the only class state maintained is to cache java reflection metadata, which is thread safe, this class is thread-safe. + * + * @author Josh Long + * + * @see org.springframework.util.xml.StaxUtils + * @see org.springframework.util.xml.StaxSource + * @see org.springframework.xml.transform.StaxSource + */ +public abstract class StaxUtils { + + private static final Log logger = LogFactory.getLog(StaxUtils.class); + + private static ClassLoader defaultClassLoader = ClassUtils.getDefaultClassLoader(); + + // regular object. + private static String staxSourceClassNameOnSpringWs15 = "org.springframework.xml.transform.StaxSource"; + private static String staxResultClassNameOnSpringOxm15 = "org.springframework.xml.transform.StaxResult"; + + // in Spring 3, StaxUtils is package private, so use static utility StaxUtils#createStaxSource / StaxUtils#createStaxResult + private static String staxSourceClassNameOnSpringOxm30 = "org.springframework.util.xml.StaxUtils"; + + private static boolean hasSpringWs15StaxSupport = ClassUtils.isPresent(staxSourceClassNameOnSpringWs15, defaultClassLoader); + + private static boolean hasSpring30StaxSupport = ClassUtils.isPresent(staxSourceClassNameOnSpringOxm30, defaultClassLoader); + + private static Method staxUtilsSourceMethodOnSpring30, staxUtilsResultMethodOnSpring30; + + private static Constructor staxSourceClassCtorOnSpringWs15, staxResultClassCtorOnSpringWs15; + + static { + try { + + // cache the factory method / constructor so that we spend as little time in reflection as possible + if (hasSpring30StaxSupport) { + Class clzz = ClassUtils.forName(staxSourceClassNameOnSpringOxm30, defaultClassLoader); + + // javax.xml.transform.Source + staxUtilsSourceMethodOnSpring30 = ClassUtils.getStaticMethod(clzz, "createStaxSource", new Class[]{ XMLEventReader.class}); + + // javax.xml.transform.Result + staxUtilsResultMethodOnSpring30 = ClassUtils.getStaticMethod(clzz, "createStaxResult", new Class[]{XMLEventWriter.class}); + } else if (hasSpringWs15StaxSupport) { + + // javax.xml.transform.Source + Class staxSourceClassOnSpringWs15 = ClassUtils.forName(staxSourceClassNameOnSpringWs15, defaultClassLoader); + staxSourceClassCtorOnSpringWs15 = staxSourceClassOnSpringWs15.getConstructor(XMLEventReader.class); + + // javax.xml.transform.Result + Class staxResultClassOnSpringWs15 = ClassUtils.forName(staxResultClassNameOnSpringOxm15, defaultClassLoader); + staxResultClassCtorOnSpringWs15 = staxResultClassOnSpringWs15.getConstructor(XMLEventWriter.class); + } else { + + logger.debug("'StaxSource' was not detected in Spring 3.0's OXM support or Spring WS 1.5's OXM support. " + + "This is a problem if you intend to use the " +StaxEventItemWriter.class.getName() + " or " + + StaxEventItemReader.class.getName()+". Please add the appropriate dependencies."); + + } + } catch (Exception ex) { + logger.error("Could not precache required class and method metadata in " + StaxUtils.class.getName()); + } + } + + public static Source getSource(XMLEventReader r) throws Exception { + if (hasSpring30StaxSupport) { + // org.springframework.util.xml.StaxUtils.createStaxSource(r) + Object result = staxUtilsSourceMethodOnSpring30.invoke(null,r); + Assert.isInstanceOf(Source.class, result, "the result should be assignable to " + Source.class.getName()); + return (Source) result; + } else if (hasSpringWs15StaxSupport) { + Object result = staxSourceClassCtorOnSpringWs15.newInstance(r); + Assert.isInstanceOf(Source.class, result, "the result should be assignable to " + Source.class.getName()); + return (Source) result; + } + // maybe you don't have either environment? + return null; + } + + public static Result getResult(XMLEventWriter w) throws Exception { + if (hasSpring30StaxSupport) { + Object result = staxUtilsResultMethodOnSpring30.invoke(null,w); + Assert.isInstanceOf(Result.class, result, "the result should be assignable to " + Result.class.getName()); + return (Result) result; + } else if (hasSpringWs15StaxSupport) { + Object result = staxResultClassCtorOnSpringWs15.newInstance(w); + Assert.isInstanceOf(Result.class, result, "the result should be assignable to " + Result.class.getName()); + return (Result) result; + } + // maybe you don't have either environment? + return null; + } + + public static XMLEventWriter getXmlEventWriter(Result r) throws Exception { + Method m = ClassUtils.getMethodIfAvailable(r.getClass(), "getXMLEventWriter", new Class[]{}); + boolean accessible=m.isAccessible(); + m.setAccessible(true); + Object result = m.invoke(r); + m.setAccessible(accessible); + return (XMLEventWriter) result; + } + + public static XMLEventReader getXmlEventReader(Source s) throws Exception { + Method m = ClassUtils.getMethodIfAvailable(s.getClass(), "getXMLEventReader", new Class[]{}); + boolean accessible=m.isAccessible(); + m.setAccessible(true); + Object result = m.invoke(s); + m.setAccessible(accessible); + return (XMLEventReader) result; + } +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemReaderXmlTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemReaderXmlTests.java index ba0b2f41b..eca44efdb 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemReaderXmlTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemReaderXmlTests.java @@ -1,16 +1,6 @@ package org.springframework.batch.item.file; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; -import java.util.Comparator; - -import javax.xml.stream.XMLEventReader; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.events.Attribute; -import javax.xml.stream.events.StartElement; -import javax.xml.transform.Source; - +import junit.framework.Assert; import org.junit.internal.runners.JUnit4ClassRunner; import org.junit.runner.RunWith; import org.springframework.batch.item.AbstractItemStreamItemReaderTests; @@ -18,11 +8,20 @@ import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.sample.Foo; import org.springframework.batch.item.xml.StaxEventItemReader; +import org.springframework.batch.item.xml.StaxUtils; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; import org.springframework.oxm.Unmarshaller; import org.springframework.oxm.XmlMappingException; -import org.springframework.xml.transform.StaxSource; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.StartElement; +import javax.xml.transform.Source; +import java.io.IOException; +import java.util.Comparator; +//import org.springframework.xml.transform.StaxSource; @RunWith(JUnit4ClassRunner.class) public class MultiResourceItemReaderXmlTests extends AbstractItemStreamItemReaderTests { @@ -35,15 +34,16 @@ public class MultiResourceItemReaderXmlTests extends AbstractItemStreamItemReade reader.setFragmentRootElementName("foo"); reader.setUnmarshaller(new Unmarshaller() { public Object unmarshal(Source source) throws XmlMappingException, IOException { - StaxSource staxSource = (StaxSource) source; - XMLEventReader eventReader = staxSource.getXMLEventReader(); + + Attribute attr; try { - assertTrue(eventReader.nextEvent().isStartDocument()); + XMLEventReader eventReader = StaxUtils.getXmlEventReader(source ); + Assert.assertTrue(eventReader.nextEvent().isStartDocument()); StartElement event = eventReader.nextEvent().asStartElement(); attr = (Attribute) event.getAttributes().next(); } - catch (XMLStreamException e) { + catch ( Exception e) { throw new RuntimeException(e); } Foo foo = new Foo(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterXmlTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterXmlTests.java index 5c2b2f760..cdf72d2a6 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterXmlTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/MultiResourceItemWriterXmlTests.java @@ -14,10 +14,10 @@ import javax.xml.transform.Result; import org.junit.Before; import org.junit.Test; import org.springframework.batch.item.xml.StaxEventItemWriter; +import org.springframework.batch.item.xml.StaxUtils; import org.springframework.oxm.Marshaller; import org.springframework.oxm.XmlMappingException; import org.springframework.util.Assert; -import org.springframework.xml.transform.StaxResult; /** * Tests for {@link MultiResourceItemWriter} delegating to @@ -43,18 +43,17 @@ public class MultiResourceItemWriterXmlTests extends AbstractMultiResourceItemWr private static class SimpleMarshaller implements Marshaller { public void marshal(Object graph, Result result) throws XmlMappingException, IOException { - Assert.isInstanceOf(StaxResult.class, result); + Assert.isInstanceOf(Result.class, result); - StaxResult staxResult = (StaxResult) result; try { XMLEventFactory factory = XMLEventFactory.newInstance(); - XMLEventWriter writer = staxResult.getXMLEventWriter(); + XMLEventWriter writer = StaxUtils.getXmlEventWriter(result); writer.add(factory.createStartDocument("UTF-8")); writer.add(factory.createStartElement("prefix", "namespace", graph.toString())); writer.add(factory.createEndElement("prefix", "namespace", graph.toString())); writer.add(factory.createEndDocument()); } - catch (XMLStreamException e) { + catch ( Exception e) { throw new RuntimeException("Exception while writing to output file", e); } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderCommonTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderCommonTests.java index 82f7ffe76..b79c2677c 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderCommonTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderCommonTests.java @@ -20,7 +20,6 @@ import org.springframework.batch.item.sample.Foo; import org.springframework.core.io.ByteArrayResource; import org.springframework.oxm.Unmarshaller; import org.springframework.oxm.XmlMappingException; -import org.springframework.xml.transform.StaxSource; @RunWith(JUnit4ClassRunner.class) public class StaxEventItemReaderCommonTests extends AbstractItemStreamItemReaderTests { @@ -33,15 +32,14 @@ public class StaxEventItemReaderCommonTests extends AbstractItemStreamItemReader reader.setFragmentRootElementName("foo"); reader.setUnmarshaller(new Unmarshaller() { public Object unmarshal(Source source) throws XmlMappingException, IOException { - StaxSource staxSource = (StaxSource) source; - XMLEventReader eventReader = staxSource.getXMLEventReader(); - Attribute attr; + Attribute attr = null ; try { + XMLEventReader eventReader = StaxUtils.getXmlEventReader( source); assertTrue(eventReader.nextEvent().isStartDocument()); StartElement event = eventReader.nextEvent().asStartElement(); attr = (Attribute) event.getAttributes().next(); } - catch (XMLStreamException e) { + catch (Exception e) { throw new RuntimeException(e); } Foo foo = new Foo(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java index 07da58b95..91b032b14 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemReaderTests.java @@ -27,7 +27,6 @@ import org.springframework.core.io.Resource; import org.springframework.oxm.Unmarshaller; import org.springframework.oxm.XmlMappingException; import org.springframework.util.ClassUtils; -import org.springframework.xml.transform.StaxSource; /** * Tests for {@link StaxEventItemReader}. @@ -387,10 +386,11 @@ public class StaxEventItemReaderTests { * @return list of the events from fragment body */ public Object unmarshal(Source source) throws XmlMappingException, IOException { - StaxSource staxSource = (StaxSource) source; - XMLEventReader eventReader = staxSource.getXMLEventReader(); + List fragmentContent; try { + XMLEventReader eventReader = StaxUtils.getXmlEventReader( source); + // first event should be StartDocument XMLEvent event1 = eventReader.nextEvent(); assertTrue(event1.isStartDocument()); @@ -413,7 +413,7 @@ public class StaxEventItemReaderTests { assertTrue(event4.isEndDocument()); } - catch (XMLStreamException e) { + catch (Exception e) { throw new RuntimeException("Error occured in FragmentDeserializer", e); } return fragmentContent; diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java index 91a8beac9..78ed7b85f 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/StaxEventItemWriterTests.java @@ -35,7 +35,6 @@ import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; -import org.springframework.xml.transform.StaxResult; /** * Tests for {@link StaxEventItemWriter}. @@ -405,16 +404,12 @@ public class StaxEventItemWriterTests { } public void marshal(Object graph, Result result) throws XmlMappingException, IOException { - Assert.isInstanceOf(StaxResult.class, result); - - StaxResult staxResult = (StaxResult) result; + Assert.isInstanceOf( Result.class, result); try { - staxResult.getXMLEventWriter().add( - XMLEventFactory.newInstance().createStartElement(namespacePrefix, namespace, graph.toString())); - staxResult.getXMLEventWriter().add( - XMLEventFactory.newInstance().createEndElement(namespacePrefix, namespace, graph.toString())); + StaxUtils.getXmlEventWriter( result ).add( XMLEventFactory.newInstance().createStartElement(namespacePrefix, namespace, graph.toString())); + StaxUtils.getXmlEventWriter( result ).add( XMLEventFactory.newInstance().createEndElement(namespacePrefix, namespace, graph.toString())); } - catch (XMLStreamException e) { + catch ( Exception e) { throw new RuntimeException("Exception while writing to output file", e); } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/TransactionalStaxEventItemWriterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/TransactionalStaxEventItemWriterTests.java index 5e2ac697e..d1432a4cf 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/TransactionalStaxEventItemWriterTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/xml/TransactionalStaxEventItemWriterTests.java @@ -30,7 +30,6 @@ import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; -import org.springframework.xml.transform.StaxResult; /** * Tests for {@link StaxEventItemWriter}. @@ -77,7 +76,7 @@ public class TransactionalStaxEventItemWriterTests { try { writer.write(items); } - catch (IOException e) { + catch ( Exception e) { throw new RuntimeException(e); } return null; @@ -115,7 +114,7 @@ public class TransactionalStaxEventItemWriterTests { try { writer.write(items); } - catch (IOException e) { + catch (Exception e) { throw new RuntimeException(e); } throw new RuntimeException("Planned"); @@ -133,7 +132,7 @@ public class TransactionalStaxEventItemWriterTests { try { writer.write(items); } - catch (IOException e) { + catch (Exception e) { throw new RuntimeException(e); } return null; @@ -171,7 +170,7 @@ public class TransactionalStaxEventItemWriterTests { try { writer.write(items); } - catch (IOException e) { + catch (Exception e) { throw new RuntimeException(e); } return null; @@ -186,7 +185,7 @@ public class TransactionalStaxEventItemWriterTests { try { writer.write(items); } - catch (IOException e) { + catch (Exception e) { throw new RuntimeException(e); } throw new RuntimeException("Planned"); @@ -215,13 +214,10 @@ public class TransactionalStaxEventItemWriterTests { */ private static class SimpleMarshaller implements Marshaller { public void marshal(Object graph, Result result) throws XmlMappingException, IOException { - Assert.isInstanceOf(StaxResult.class, result); - - StaxResult staxResult = (StaxResult) result; try { - staxResult.getXMLEventWriter().add(XMLEventFactory.newInstance().createComment(graph.toString())); + StaxUtils.getXmlEventWriter(result).add(XMLEventFactory.newInstance().createComment(graph.toString())); } - catch (XMLStreamException e) { + catch ( Exception e) { throw new RuntimeException("Exception while writing to output file", e); } }