Skip to content

Commit a077a9b

Browse files
committed
Ensuring an unmarshalled InputStream is not closed
Accepting an existing InputStream for consuming, should not assume that the InputStream can be closed.
1 parent c85ba76 commit a077a9b

File tree

5 files changed

+158
-24
lines changed

5 files changed

+158
-24
lines changed

jaxb/src/main/java/no/digipost/signature/jaxb/JaxbMarshaller.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
import no.digipost.signature.xsd.SignatureApiSchemas;
1919
import no.digipost.xml.bind.MarshallingCustomization;
2020
import no.digipost.xml.parsers.SaxParserProvider;
21+
import no.digipost.xml.transform.sax.SaxInputSources;
2122
import org.w3c.dom.Document;
2223
import org.w3c.dom.Node;
23-
import org.xml.sax.InputSource;
2424

2525
import javax.xml.bind.JAXBContext;
2626
import javax.xml.bind.Marshaller;
@@ -194,7 +194,7 @@ private <T> void doWithMarshaller(T object, ThrowingBiConsumer<? super T, ? supe
194194
}
195195

196196
public <T> T unmarshal(InputStream inputStream, Class<T> type) {
197-
Source xmlSource = saxParserProvider.createSource(new InputSource(inputStream));
197+
Source xmlSource = saxParserProvider.createSource(SaxInputSources.fromInputStreamPreventClose(inputStream));
198198
return unmarshal(unmarshaller -> unmarshaller.unmarshal(xmlSource), type);
199199
}
200200

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (C) Posten Norge AS
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package no.digipost.xml.transform.sax;
17+
18+
import org.xml.sax.InputSource;
19+
20+
import java.io.FilterInputStream;
21+
import java.io.InputStream;
22+
import java.io.Reader;
23+
import java.nio.charset.Charset;
24+
25+
26+
class ClosePreventingInputSource extends InputSource {
27+
28+
private boolean initialized;
29+
30+
public ClosePreventingInputSource(InputStream inputStream, Charset encoding) {
31+
super(new ClosePreventingInputStream(inputStream));
32+
if (encoding != null) {
33+
this.setEncoding(encoding.name());
34+
}
35+
this.initialized = true;
36+
}
37+
38+
private static final class ClosePreventingInputStream extends FilterInputStream {
39+
public ClosePreventingInputStream(InputStream in) {
40+
super(in);
41+
}
42+
@Override
43+
public void close() {
44+
// prevent closing
45+
}
46+
}
47+
48+
49+
50+
@Override
51+
public final Reader getCharacterStream() {
52+
return null;
53+
}
54+
55+
@Override
56+
public final void setCharacterStream(Reader characterStream) {
57+
throw new UnsupportedOperationException("Setting a character stream is not supported");
58+
}
59+
60+
@Override
61+
public final void setByteStream(InputStream byteStream) {
62+
if (initialized) {
63+
throw new UnsupportedOperationException("Setting a byte stream is not supported");
64+
}
65+
super.setByteStream(byteStream);
66+
}
67+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (C) Posten Norge AS
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package no.digipost.xml.transform.sax;
17+
18+
import org.xml.sax.InputSource;
19+
20+
import java.io.InputStream;
21+
import java.net.URL;
22+
import java.nio.charset.Charset;
23+
24+
import static java.util.Objects.requireNonNull;
25+
26+
public final class SaxInputSources {
27+
28+
/**
29+
* Create a SAX {@link InputSource} wrapping the given existing InputStream
30+
* which will not be closed by anything consuming the InputSource.
31+
* The wrapped InputStream, which will be returned by {@link InputSource#getByteStream()},
32+
* will be ensured cannot be closed, even if attempted. The responsibility to
33+
* properly manage an existing InputStream (i.e. closing)
34+
* is outside the InputSource, and it cannot assume that the wrapped stream
35+
* can be closed after done consuming it.
36+
*/
37+
public static InputSource fromInputStreamPreventClose(InputStream inputStream) {
38+
return fromInputStreamPreventClose(inputStream, null);
39+
}
40+
41+
/**
42+
* Create a SAX {@link InputSource} wrapping the given existing InputStream
43+
* which will not be closed by anything consuming the InputSource.
44+
*
45+
* @see #fromInputStreamPreventClose(InputStream)
46+
*/
47+
public static InputSource fromInputStreamPreventClose(InputStream inputStream, Charset encoding) {
48+
return new ClosePreventingInputSource(inputStream, encoding);
49+
}
50+
51+
52+
public static InputSource fromClasspath(String resourceName) {
53+
return fromClasspath(resourceName, null);
54+
}
55+
56+
public static InputSource fromClasspath(String resourceName, Charset encoding) {
57+
return fromClasspath(resourceName, encoding, UrlInputSource.class.getClassLoader());
58+
}
59+
60+
public static InputSource fromClasspath(String resourceName, Charset encoding, ClassLoader classLoader) {
61+
URL location = classLoader.getResource(resourceName.startsWith("/") ? resourceName.substring(1) : resourceName);
62+
requireNonNull(location, resourceName + " not found on classpath");
63+
return new UrlInputSource(location, encoding);
64+
}
65+
66+
private SaxInputSources() {
67+
}
68+
}

jaxb/src/main/java/no/digipost/xml/transform/sax/UrlInputSource.java

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,18 @@
2626

2727
import static java.util.Objects.requireNonNull;
2828

29-
public class UrlInputSource extends InputSource {
29+
class UrlInputSource extends InputSource {
3030

31-
public static UrlInputSource fromClasspath(String resourceName, Charset encoding) {
32-
return fromClasspath(resourceName, encoding, UrlInputSource.class.getClassLoader());
33-
}
34-
35-
public static UrlInputSource fromClasspath(String resourceName, Charset encoding, ClassLoader classLoader) {
36-
URL location = classLoader.getResource(resourceName.startsWith("/") ? resourceName.substring(1) : resourceName);
37-
requireNonNull(location, resourceName + " not found on classpath");
38-
return new UrlInputSource(location, encoding);
39-
}
40-
41-
private URL location;
31+
private final URL location;
32+
private boolean initialized;
4233

4334
public UrlInputSource(URL location, Charset encoding) {
44-
this.location = location;
45-
this.setEncoding(encoding.name());
35+
this.location = requireNonNull(location, "location URL");
4636
this.setSystemId(location.toString());
37+
if (encoding != null) {
38+
this.setEncoding(encoding.name());
39+
}
40+
this.initialized = true;
4741
}
4842

4943
@Override
@@ -58,13 +52,20 @@ public final InputStream getByteStream() {
5852
}
5953

6054
@Override
61-
public final String getEncoding() {
62-
return super.getEncoding();
55+
public final Reader getCharacterStream() {
56+
return null;
6357
}
6458

6559
@Override
66-
public final Reader getCharacterStream() {
67-
return null;
60+
public final void setCharacterStream(Reader characterStream) {
61+
throw new UnsupportedOperationException("Setting a character stream is not supported");
6862
}
6963

64+
@Override
65+
public final void setByteStream(InputStream byteStream) {
66+
if (initialized) {
67+
throw new UnsupportedOperationException("Setting a byte stream is not supported");
68+
}
69+
super.setByteStream(byteStream);
70+
}
7071
}

jaxb/src/main/java/no/digipost/xml/validation/SchemaHelper.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package no.digipost.xml.validation;
1717

1818
import no.digipost.xml.parsers.SaxParserProvider;
19-
import no.digipost.xml.transform.sax.UrlInputSource;
19+
import no.digipost.xml.transform.sax.SaxInputSources;
2020

2121
import javax.xml.XMLConstants;
2222
import javax.xml.transform.Source;
@@ -25,8 +25,6 @@
2525

2626
import java.util.Collection;
2727

28-
import static java.nio.charset.StandardCharsets.UTF_8;
29-
3028
public final class SchemaHelper {
3129

3230
public static Schema createW3cXmlSchema(Collection<String> schemaResourceNames) {
@@ -35,7 +33,7 @@ public static Schema createW3cXmlSchema(Collection<String> schemaResourceNames)
3533
}
3634
try {
3735
Source[] schemaSources = schemaResourceNames.stream()
38-
.map(resourceName -> UrlInputSource.fromClasspath(resourceName, UTF_8))
36+
.map(SaxInputSources::fromClasspath)
3937
.map(SaxParserProvider.createSecuredProvider()::createSource)
4038
.toArray(Source[]::new);
4139

0 commit comments

Comments
 (0)