org.omnifaces.resourcehandler.CombinedResourceInputStream Maven / Gradle / Ivy
/*
* 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.FINE;
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 javax.faces.application.Resource;
import org.omnifaces.util.Utils;
/**
*
* 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 streams;
private Iterator 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 resources) throws IOException {
streams = new ArrayList<>();
String domainURL = getRequestDomainURL();
for (Resource resource : resources) {
InputStream stream;
try {
stream = resource.getInputStream();
}
catch (Exception richFacesDoesNotSupportThis) {
logger.log(FINE, "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 -1
and then iterate to the
* {@link InputStream} of the next resource, if any available, else return -1
.
*/
@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 -1
and then iterate to the
* {@link InputStream} of the next resource, if any available, else return -1
.
*/
@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;
}
}
}