001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.http.codec.xml; 018 019import java.io.InputStream; 020import java.util.ArrayList; 021import java.util.Iterator; 022import java.util.List; 023import java.util.Map; 024import java.util.function.Function; 025 026import javax.xml.stream.XMLInputFactory; 027import javax.xml.stream.XMLStreamException; 028import javax.xml.stream.events.XMLEvent; 029import javax.xml.stream.util.XMLEventAllocator; 030 031import com.fasterxml.aalto.AsyncByteBufferFeeder; 032import com.fasterxml.aalto.AsyncXMLInputFactory; 033import com.fasterxml.aalto.AsyncXMLStreamReader; 034import com.fasterxml.aalto.evt.EventAllocatorImpl; 035import com.fasterxml.aalto.stax.InputFactoryImpl; 036import org.reactivestreams.Publisher; 037import reactor.core.Exceptions; 038import reactor.core.publisher.Flux; 039 040import org.springframework.core.ResolvableType; 041import org.springframework.core.codec.AbstractDecoder; 042import org.springframework.core.io.buffer.DataBuffer; 043import org.springframework.core.io.buffer.DataBufferLimitException; 044import org.springframework.core.io.buffer.DataBufferUtils; 045import org.springframework.http.MediaType; 046import org.springframework.lang.Nullable; 047import org.springframework.util.ClassUtils; 048import org.springframework.util.MimeType; 049import org.springframework.util.MimeTypeUtils; 050import org.springframework.util.xml.StaxUtils; 051 052/** 053 * Decodes a {@link DataBuffer} stream into a stream of {@link XMLEvent XMLEvents}. 054 * 055 * <p>Given the following XML: 056 * 057 * <pre class="code"> 058 * <root> 059 * <child>foo</child> 060 * <child>bar</child> 061 * </root> 062 * </pre> 063 * 064 * this decoder will produce a {@link Flux} with the following events: 065 * 066 * <ol> 067 * <li>{@link javax.xml.stream.events.StartDocument}</li> 068 * <li>{@link javax.xml.stream.events.StartElement} {@code root}</li> 069 * <li>{@link javax.xml.stream.events.StartElement} {@code child}</li> 070 * <li>{@link javax.xml.stream.events.Characters} {@code foo}</li> 071 * <li>{@link javax.xml.stream.events.EndElement} {@code child}</li> 072 * <li>{@link javax.xml.stream.events.StartElement} {@code child}</li> 073 * <li>{@link javax.xml.stream.events.Characters} {@code bar}</li> 074 * <li>{@link javax.xml.stream.events.EndElement} {@code child}</li> 075 * <li>{@link javax.xml.stream.events.EndElement} {@code root}</li> 076 * </ol> 077 * 078 * <p>Note that this decoder is not registered by default but is used internally 079 * by other decoders which are registered by default. 080 * 081 * @author Arjen Poutsma 082 * @author Sam Brannen 083 * @since 5.0 084 */ 085public class XmlEventDecoder extends AbstractDecoder<XMLEvent> { 086 087 private static final XMLInputFactory inputFactory = StaxUtils.createDefensiveInputFactory(); 088 089 private static final boolean aaltoPresent = ClassUtils.isPresent( 090 "com.fasterxml.aalto.AsyncXMLStreamReader", XmlEventDecoder.class.getClassLoader()); 091 092 boolean useAalto = aaltoPresent; 093 094 private int maxInMemorySize = 256 * 1024; 095 096 097 public XmlEventDecoder() { 098 super(MimeTypeUtils.APPLICATION_XML, MimeTypeUtils.TEXT_XML, new MediaType("application", "*+xml")); 099 } 100 101 102 /** 103 * Set the max number of bytes that can be buffered by this decoder. This 104 * is either the size the entire input when decoding as a whole, or when 105 * using async parsing via Aalto XML, it is size one top-level XML tree. 106 * When the limit is exceeded, {@link DataBufferLimitException} is raised. 107 * <p>By default this is set to 256K. 108 * @param byteCount the max number of bytes to buffer, or -1 for unlimited 109 * @since 5.1.11 110 */ 111 public void setMaxInMemorySize(int byteCount) { 112 this.maxInMemorySize = byteCount; 113 } 114 115 /** 116 * Return the {@link #setMaxInMemorySize configured} byte count limit. 117 * @since 5.1.11 118 */ 119 public int getMaxInMemorySize() { 120 return this.maxInMemorySize; 121 } 122 123 124 @Override 125 @SuppressWarnings({"rawtypes", "unchecked", "cast"}) // XMLEventReader is Iterator<Object> on JDK 9 126 public Flux<XMLEvent> decode(Publisher<DataBuffer> input, ResolvableType elementType, 127 @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) { 128 129 if (this.useAalto) { 130 AaltoDataBufferToXmlEvent mapper = new AaltoDataBufferToXmlEvent(this.maxInMemorySize); 131 return Flux.from(input) 132 .flatMapIterable(mapper) 133 .doFinally(signalType -> mapper.endOfInput()); 134 } 135 else { 136 return DataBufferUtils.join(input, this.maxInMemorySize) 137 .flatMapIterable(buffer -> { 138 try { 139 InputStream is = buffer.asInputStream(); 140 Iterator eventReader = inputFactory.createXMLEventReader(is); 141 List<XMLEvent> result = new ArrayList<>(); 142 eventReader.forEachRemaining(event -> result.add((XMLEvent) event)); 143 return result; 144 } 145 catch (XMLStreamException ex) { 146 throw Exceptions.propagate(ex); 147 } 148 finally { 149 DataBufferUtils.release(buffer); 150 } 151 }); 152 } 153 } 154 155 156 /* 157 * Separate static class to isolate Aalto dependency. 158 */ 159 private static class AaltoDataBufferToXmlEvent implements Function<DataBuffer, List<? extends XMLEvent>> { 160 161 private static final AsyncXMLInputFactory inputFactory = 162 StaxUtils.createDefensiveInputFactory(InputFactoryImpl::new); 163 164 private final AsyncXMLStreamReader<AsyncByteBufferFeeder> streamReader = 165 inputFactory.createAsyncForByteBuffer(); 166 167 private final XMLEventAllocator eventAllocator = EventAllocatorImpl.getDefaultInstance(); 168 169 private final int maxInMemorySize; 170 171 private int byteCount; 172 173 private int elementDepth; 174 175 176 public AaltoDataBufferToXmlEvent(int maxInMemorySize) { 177 this.maxInMemorySize = maxInMemorySize; 178 } 179 180 181 @Override 182 public List<? extends XMLEvent> apply(DataBuffer dataBuffer) { 183 try { 184 increaseByteCount(dataBuffer); 185 this.streamReader.getInputFeeder().feedInput(dataBuffer.asByteBuffer()); 186 List<XMLEvent> events = new ArrayList<>(); 187 while (true) { 188 if (this.streamReader.next() == AsyncXMLStreamReader.EVENT_INCOMPLETE) { 189 // no more events with what currently has been fed to the reader 190 break; 191 } 192 else { 193 XMLEvent event = this.eventAllocator.allocate(this.streamReader); 194 events.add(event); 195 if (event.isEndDocument()) { 196 break; 197 } 198 checkDepthAndResetByteCount(event); 199 } 200 } 201 if (this.maxInMemorySize > 0 && this.byteCount > this.maxInMemorySize) { 202 raiseLimitException(); 203 } 204 return events; 205 } 206 catch (XMLStreamException ex) { 207 throw Exceptions.propagate(ex); 208 } 209 finally { 210 DataBufferUtils.release(dataBuffer); 211 } 212 } 213 214 private void increaseByteCount(DataBuffer dataBuffer) { 215 if (this.maxInMemorySize > 0) { 216 if (dataBuffer.readableByteCount() > Integer.MAX_VALUE - this.byteCount) { 217 raiseLimitException(); 218 } 219 else { 220 this.byteCount += dataBuffer.readableByteCount(); 221 } 222 } 223 } 224 225 private void checkDepthAndResetByteCount(XMLEvent event) { 226 if (this.maxInMemorySize > 0) { 227 if (event.isStartElement()) { 228 this.byteCount = this.elementDepth == 1 ? 0 : this.byteCount; 229 this.elementDepth++; 230 } 231 else if (event.isEndElement()) { 232 this.elementDepth--; 233 this.byteCount = this.elementDepth == 1 ? 0 : this.byteCount; 234 } 235 } 236 } 237 238 private void raiseLimitException() { 239 throw new DataBufferLimitException( 240 "Exceeded limit on max bytes per XML top-level node: " + this.maxInMemorySize); 241 } 242 243 public void endOfInput() { 244 this.streamReader.getInputFeeder().endOfInput(); 245 } 246 } 247 248 249 250}