/* * Copyright (c) 2014 Red Hat, Inc. and/or its affiliates. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cheng Fang - Initial API and implementation */ package org.jberet.support.io; import java.io.Serializable; import javax.batch.api.BatchProperty; import javax.batch.api.chunk.ItemReader; import javax.enterprise.context.Dependent; import javax.inject.Inject; import javax.inject.Named; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.io.InputDecorator; import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule; import com.fasterxml.jackson.dataformat.xml.deser.FromXmlParser; import org.jberet.support._private.SupportLogger; import org.jberet.support._private.SupportMessages; /** * An implementation of {@code javax.batch.api.chunk.ItemReader} that reads a collection of data from XML resource. * Users of this class should provide a bean class that represents individual data item in the source XML, and the * {@link #readItem()} method reads one item at a time and binds it to the provided bean type. * * @see XmlItemWriter * @see XmlItemReaderWriterBase * @since 1.0.2 */ @Named @Dependent public class XmlItemReader extends XmlItemReaderWriterBase implements ItemReader { /** * The bean class that represents individual data item in the {@link #resource} XML, and the {@link #readItem()} * method reads one item at a time and binds it to the provided bean type. Required property. For example, * <p> * <ul> * <li>{@code org.jberet.support.io.StockTrade} * <li>{@code org.jberet.support.io.Person} * <li>{@code my.own.custom.ItemBean} * </ul> */ @Inject @BatchProperty protected Class beanType; /** * Specifies the start position (a positive integer starting from 1) to read the data. If reading from the beginning * of the input XML, there is no need to specify this property. */ @Inject @BatchProperty protected int start; /** * Specify the end position in the data set (inclusive). Optional property, and defaults to {@code Integer.MAX_VALUE}. * If reading till the end of the input XML, there is no need to specify this property. */ @Inject @BatchProperty protected int end; /** * Fully-qualified name of a class implementing {@code com.fasterxml.jackson.core.io.InputDecorator}, which * can be used to decorate input sources. Optional property, and defaults to null. * * @see "com.fasterxml.jackson.core.io.InputDecorator" * @see "com.fasterxml.jackson.core.JsonFactory#setInputDecorator(com.fasterxml.jackson.core.io.InputDecorator)" */ @Inject @BatchProperty protected Class inputDecorator; /** * Alternate "virtual name" to use for XML CDATA segments; that is, text values. * Optional property and defaults to null (empty String, "", is used as the virtual name). * * @see "com.fasterxml.jackson.dataformat.xml.JacksonXmlModule#setXMLTextElementName(java.lang.String)" */ @Inject @BatchProperty protected String xmlTextElementName; private FromXmlParser fromXmlParser; private JsonToken token; private int rowNumber; @Override public void open(final Serializable checkpoint) throws Exception { if (end == 0) { end = Integer.MAX_VALUE; } if (checkpoint != null) { start = (Integer) checkpoint; } if (start > end) { throw SupportMessages.MESSAGES.invalidStartPosition((Integer) checkpoint, start, end); } super.initXmlFactory(); if (inputDecorator != null) { xmlFactory.setInputDecorator((InputDecorator) inputDecorator.newInstance()); } fromXmlParser = (FromXmlParser) xmlFactory.createParser(getInputStream(resource, false)); SupportLogger.LOGGER.openingResource(resource, this.getClass()); token = fromXmlParser.nextToken(); } @Override public Object readItem() throws Exception { if (rowNumber >= end) { return null; } int nestedObjectLevel = 0; do { token = fromXmlParser.nextToken(); if (token == null) { return null; } else if (token == JsonToken.START_OBJECT) { nestedObjectLevel++; if (nestedObjectLevel == 1) { rowNumber++; } else if (nestedObjectLevel < 1) { throw SupportMessages.MESSAGES.unexpectedJsonContent(fromXmlParser.getCurrentLocation()); } if (rowNumber >= start) { break; } } else if (token == JsonToken.END_OBJECT) { nestedObjectLevel--; } } while (true); final Object readValue = xmlMapper.readValue(fromXmlParser, beanType); if (!skipBeanValidation) { ItemReaderWriterBase.validate(readValue); } return readValue; } @Override public Serializable checkpointInfo() throws Exception { return rowNumber; } @Override public void close() throws Exception { if (fromXmlParser != null) { SupportLogger.LOGGER.closingResource(resource, this.getClass()); fromXmlParser.close(); fromXmlParser = null; } } @Override protected void initXmlModule() { if (xmlTextElementName != null && !xmlTextElementName.isEmpty()) { xmlModule = new JacksonXmlModule(); xmlModule.setXMLTextElementName(xmlTextElementName); } } }