Java tutorial
/* * Copyright 2015 Adaptris Ltd. * * 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 com.adaptris.core.services.jdbc; import java.sql.SQLException; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import javax.validation.Valid; import javax.validation.constraints.NotNull; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import com.adaptris.annotation.AdvancedConfig; import com.adaptris.annotation.AutoPopulated; import com.adaptris.annotation.DisplayOrder; import com.adaptris.annotation.InputFieldDefault; import com.adaptris.core.AdaptrisConnection; import com.adaptris.core.AdaptrisMessage; import com.adaptris.core.AdaptrisMessageFactory; import com.adaptris.core.AdaptrisMessageProducer; import com.adaptris.core.CoreConstants; import com.adaptris.core.CoreException; import com.adaptris.core.NullConnection; import com.adaptris.core.NullMessageProducer; import com.adaptris.core.ServiceException; import com.adaptris.core.util.Args; import com.adaptris.core.util.DocumentBuilderFactoryBuilder; import com.adaptris.core.util.ExceptionHelper; import com.adaptris.core.util.LifecycleHelper; import com.adaptris.core.util.XmlHelper; import com.adaptris.jdbc.JdbcResult; import com.adaptris.jdbc.JdbcResultRow; import com.adaptris.jdbc.JdbcResultSet; import com.adaptris.util.NumberUtils; import com.thoughtworks.xstream.annotations.XStreamAlias; /** * Translate the ResultSet contents into some number of XML messages. * * <p> * The format of the output messages is as follows: * * <pre> * {@code * <Results> * <Row> * <column1>...</column1> * <column2>...</column2> * ... * </Row> * <Row> ... </Row> * </Results> * } * </pre> * </p> * <p> * Note that column1, column2, etc. is replaced by the actual column name as returned in the query. As such, the column name must be * a valid XML element name. If the actual name (in the database table definition) is not valid, the query should specify an alias * name in the query. E.g: <code>SELECT "col 1" AS "col1" FROM mytable;</code> * </p> * <p> * The output messages will be constructed by the same MessageFactory as the incoming message. If a MessageFactory has been configured * then that one will be used instead (useful in case the incoming message is file-backed, for example). * </p> * <p> * If you want to see how many rows were processed you can set one/both of the following; * <table> * <tr> * <th>Item</th> * <th>Description</th> * <th>Value</th> * </tr> * <tr> * <td>result-count-metadata-item</td><td>If set to a String metadata item name will specify the metadata item to contain the number of rows returned by your query</td><td>Metadata item name</td> * </tr> * <tr> * <td>update-count-metadata-item</td><td>If set to a String metadata item name will specify the metadata item to contain the number of rows updated by your SQL statement</td><td>Metadata item name</td> * </tr> * </table> * <p> * * @config jdbc-splitting-xml-payload-translator * * @author gdries * */ @XStreamAlias("jdbc-splitting-xml-payload-translator") @DisplayOrder(order = { "maxRowsPerMessage", "copyMetadata", "producer", "columnNameStyle", "columnTranslators", "mergeImplementation", "outputMessageEncoding", "stripIllegalXmlChars", "xmlColumnPrefix", "xmlColumnRegexp", "cdataColumnRegexp", "messageFactory" }) public class SplittingXmlPayloadTranslator extends XmlPayloadTranslatorImpl { @NotNull(message = "Connection for split messages may not be null") @AutoPopulated @Valid private AdaptrisConnection connection; @NotNull(message = "Producer for split messages may not be null") @AutoPopulated @Valid private AdaptrisMessageProducer producer; @AdvancedConfig private AdaptrisMessageFactory messageFactory; @InputFieldDefault(value = "false") private Boolean copyMetadata; @InputFieldDefault(value = "1000") private Integer maxRowsPerMessage; public SplittingXmlPayloadTranslator() { super(); setConnection(new NullConnection()); setProducer(new NullMessageProducer()); } @Override public void init() throws CoreException { super.init(); connection.addMessageProducer(producer); LifecycleHelper.init(getConnection()); LifecycleHelper.init(getProducer()); } @Override public void start() throws CoreException { super.start(); LifecycleHelper.start(getConnection()); LifecycleHelper.start(getProducer()); } @Override public void prepare() throws CoreException { super.prepare(); LifecycleHelper.prepare(getConnection()); LifecycleHelper.prepare(getProducer()); } @Override public void stop() { super.stop(); LifecycleHelper.stop(getConnection()); LifecycleHelper.stop(getProducer()); } @Override public void close() { super.close(); LifecycleHelper.close(getConnection()); LifecycleHelper.close(getProducer()); } /** * Split the JdbcResult into possibly multiple output messages. Each ResultSet will start in a new message if there are multiple. */ @Override public long translateResult(JdbcResult source, AdaptrisMessage inputMessage) throws SQLException, ServiceException { long resultSetCount = 0; try { // Handle each ResultSet separately and create as many messages as required for (JdbcResultSet rs : source.getResultSets()) { // While we still have rows in this ResultSet final Iterator<JdbcResultRow> rows = rs.getRows().iterator(); while (rows.hasNext()) { AdaptrisMessage outputMessage = newMessage(inputMessage); // Fill the message with the requisite number of rows if this ResultSet has enough of them // We stick this in a try-catch-resources simply to get coverage... // LimitedResultSet#close() should be doing nothing, since it's only purpose // is limit the number of iterations you make. try (LimitedResultSet lrs = new LimitedResultSet(rows, maxRowsPerMessage())) { DocumentWrapper doc = toDocument(outputMessage, lrs); XmlHelper.writeXmlDocument(doc.document, outputMessage, getOutputMessageEncoding()); // Use the configured producer to send the message on its way getProducer().produce(outputMessage); resultSetCount += doc.resultSetCount; } } } } catch (Exception e) { throw ExceptionHelper.wrapServiceException("Failed to process message", e); } return resultSetCount; } private AdaptrisMessage newMessage(AdaptrisMessage original) { AdaptrisMessageFactory factory = ObjectUtils.defaultIfNull(getMessageFactory(), original.getFactory()); AdaptrisMessage result = factory.newMessage(); if (copyMetadata()) { result.setMetadata(original.getMetadata()); } result.addMetadata(CoreConstants.PARENT_UNIQUE_ID_KEY, original.getUniqueId()); return result; } private DocumentWrapper toDocument(AdaptrisMessage msg, JdbcResultSet rSet) throws ParserConfigurationException, SQLException { DocumentBuilderFactoryBuilder factoryBuilder = documentFactoryBuilder(msg); DocumentBuilderFactory factory = factoryBuilder.configure(DocumentBuilderFactory.newInstance()); DocumentBuilder builder = factoryBuilder.configure(factory.newDocumentBuilder()); Document doc = builder.newDocument(); DocumentWrapper result = new DocumentWrapper(doc, 0); ColumnStyle elementNameStyle = getColumnNameStyle(); Element results = doc.createElement(elementNameStyle.format(ELEMENT_NAME_RESULTS)); doc.appendChild(results); List<Element> elements = createListFromResultSet(builder, doc, rSet); for (Element element : elements) { results.appendChild(element); } result.resultSetCount += elements.size(); return result; } private DocumentBuilderFactoryBuilder documentFactoryBuilder(AdaptrisMessage msg) { DocumentBuilderFactoryBuilder factoryBuilder = (DocumentBuilderFactoryBuilder) msg.getObjectHeaders() .get(JdbcDataQueryService.KEY_DOCBUILDER_FAC); return DocumentBuilderFactoryBuilder.newInstance(factoryBuilder); } public Integer getMaxRowsPerMessage() { return maxRowsPerMessage; } public void setMaxRowsPerMessage(Integer maxRowsPerMessage) { this.maxRowsPerMessage = maxRowsPerMessage; } private int maxRowsPerMessage() { return NumberUtils.toIntDefaultIfNull(getMaxRowsPerMessage(), 1000); } /** * <p> * Sets the <code>AdaptrisConnection</code> to use for producing split * messages. * </p> * * @param conn the <code>AdaptrisConnection</code> to use for producing split * messages, may not be null */ public void setConnection(AdaptrisConnection conn) { connection = Args.notNull(conn, "connection"); } /** * <p> * Returns the <code>AdaptrisConnection</code> to use for producing split * messages. * </p> * * @return the <code>AdaptrisConnection</code> to use for producing split * messages */ public AdaptrisConnection getConnection() { return connection; } /** * <p> * Sets the <code>AdaptrisMessageProducer</code> to use for producing split * messages. * </p> * * @param prod the <code>AdaptrisMessageProducer</code> to use for producing * split messages, may not be null */ public void setProducer(AdaptrisMessageProducer prod) { producer = Args.notNull(prod, "producer"); } /** * <p> * Returns the <code>AdaptrisMessageProducer</code> to use for producing split * messages. * </p> * * @return the <code>AdaptrisMessageProducer</code> to use for producing split * messages */ public AdaptrisMessageProducer getProducer() { return producer; } public AdaptrisMessageFactory getMessageFactory() { return messageFactory; } public void setMessageFactory(AdaptrisMessageFactory messageFactory) { this.messageFactory = messageFactory; } /** * Whether to copy metadata from the original message to the split messages. * <p> * Note that object metadata is never copied since the nested action is to produce the message * somewhere. * </p> * * @param b whether to copy metadata from the original message to the split messages (default * true) */ public void setCopyMetadata(Boolean b) { copyMetadata = b; } public Boolean getCopyMetadata() { return copyMetadata; } private boolean copyMetadata() { // False for backwards compatible behaviour. return BooleanUtils.toBooleanDefaultIfNull(getCopyMetadata(), false); } public SplittingXmlPayloadTranslator withCopyMetadata(Boolean b) { setCopyMetadata(b); return this; } public SplittingXmlPayloadTranslator withProducer(AdaptrisMessageProducer b) { setProducer(b); return this; } public SplittingXmlPayloadTranslator withConnection(AdaptrisConnection b) { setConnection(b); return this; } public SplittingXmlPayloadTranslator withMaxRowsPerMessage(Integer b) { setMaxRowsPerMessage(b); return this; } public SplittingXmlPayloadTranslator withMessageFactory(AdaptrisMessageFactory b) { setMessageFactory(b); return this; } /** * Wrap a JdbcResultSet to limit the number of rows it will return. */ private static class LimitedResultSet implements JdbcResultSet { private final Iterator<JdbcResultRow> rows; public LimitedResultSet(Iterator<JdbcResultRow> rows, int rowLimit) { this.rows = new LimitedIterator<JdbcResultRow>(rows, rowLimit); } @Override public Iterable<JdbcResultRow> getRows() { return new Iterable<JdbcResultRow>() { @Override public Iterator<JdbcResultRow> iterator() { return rows; } }; } @Override public void close() { } } /** * Wraps an Iterator and limits the number of objects produced by it * * @param <T> */ private static class LimitedIterator<T> implements Iterator<T> { private final Iterator<? extends T> delegate; private final int limit; private int objectsProduced; public LimitedIterator(Iterator<? extends T> delegate, int limit) { this.delegate = delegate; this.limit = limit; } @Override public boolean hasNext() { return objectsProduced < limit && delegate.hasNext(); } @Override public T next() { if (objectsProduced >= limit) { throw new NoSuchElementException(); } final T tmp = delegate.next(); objectsProduced++; return tmp; } } }