/
CombinedResourceInputStream.java
151 lines (122 loc) · 4.61 KB
/
CombinedResourceInputStream.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*
* Copyright OmniFaces
*
* 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
*
* https://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.omnifaces.resourcehandler;
import static java.util.logging.Level.FINEST;
import static org.omnifaces.util.Faces.getRequestDomainURL;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import jakarta.faces.application.Resource;
import org.omnifaces.util.Utils;
/**
* <p>
* This {@link InputStream} implementation takes care that all in the constructor given resources are been read in
* sequence.
*
* @author Bauke Scholtz
*/
public final class CombinedResourceInputStream extends InputStream {
// Constants ------------------------------------------------------------------------------------------------------
private static final Logger logger = Logger.getLogger(CombinedResourceInputStream.class.getName());
private static final byte[] CRLF = { '\r', '\n' };
// Properties -----------------------------------------------------------------------------------------------------
private List<InputStream> streams;
private Iterator<InputStream> streamIterator;
private InputStream currentStream;
// Constructors ---------------------------------------------------------------------------------------------------
/**
* Creates an instance of {@link CombinedResourceInputStream} based on the given resources. For each resource, the
* {@link InputStream} will be obtained and hold in an iterable collection.
* @param resources The resources to be read.
* @throws IOException If something fails at I/O level.
*/
public CombinedResourceInputStream(Set<Resource> resources) throws IOException {
streams = new ArrayList<>();
String domainURL = getRequestDomainURL();
for (Resource resource : resources) {
InputStream stream;
try {
stream = resource.getInputStream();
}
catch (Exception richFacesDoesNotSupportThis) {
logger.log(FINEST, "Ignoring thrown exception; this can only be caused by a buggy component library.", richFacesDoesNotSupportThis);
stream = new URL(domainURL + resource.getRequestPath()).openStream();
}
streams.add(stream);
streams.add(new ByteArrayInputStream(CRLF));
}
streamIterator = streams.iterator();
if (streamIterator.hasNext()) {
currentStream = streamIterator.next();
}
}
// Actions --------------------------------------------------------------------------------------------------------
/**
* For each resource, read until its {@link InputStream#read()} returns <code>-1</code> and then iterate to the
* {@link InputStream} of the next resource, if any available, else return <code>-1</code>.
*/
@Override
public int read() throws IOException {
int read;
while ((read = currentStream.read()) == -1) {
if (streamIterator.hasNext()) {
currentStream = streamIterator.next();
}
else {
break;
}
}
return read;
}
/**
* For each resource, read until its {@link InputStream#read()} returns <code>-1</code> and then iterate to the
* {@link InputStream} of the next resource, if any available, else return <code>-1</code>.
*/
@Override
public int read(byte[] b, int offset, int length) throws IOException {
int read;
while ((read = currentStream.read(b, offset, length)) == -1) {
if (streamIterator.hasNext()) {
currentStream = streamIterator.next();
}
else {
break;
}
}
return read;
}
/**
* Closes the {@link InputStream} of each resource. Whenever the {@link InputStream#close()} throws an
* {@link IOException} for the first time, it will be caught and be thrown after all resources have been closed.
* Any {@link IOException} which is thrown by a subsequent close will be ignored by design.
*/
@Override
public void close() throws IOException {
IOException caught = null;
for (InputStream stream : streams) {
IOException e = Utils.close(stream);
if (caught == null) {
caught = e; // Don't throw it yet. We have to continue closing all other streams.
}
}
if (caught != null) {
throw caught;
}
}
}