/* * eXist Open Source Native XML Database * Copyright (C) 2011 The eXist Project * http://exist.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.exist.util; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.evolvedbinary.j8fu.Either; import com.evolvedbinary.j8fu.function.SupplierE; import org.xml.sax.InputSource; import java.io.BufferedInputStream; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.Reader; import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; /** * This class extends {@link org.xml.sax.InputSource}, so * it also manages {@link java.io.File} and * {@link org.exist.util.VirtualTempFile} as input sources. * * @author jmfernandez * */ public class VirtualTempFileInputSource extends EXistInputSource { private final static Logger LOG = LogManager.getLogger(VirtualTempFileInputSource.class); private Optional<Either<Path, VirtualTempFile>> file = Optional.empty(); public VirtualTempFileInputSource(final VirtualTempFile vtempFile) throws IOException { this(vtempFile, null); } public VirtualTempFileInputSource(final VirtualTempFile vtempFile, final String encoding) throws IOException { // Temp file must be immutable from this point vtempFile.close(); this.file = Optional.of(Either.Right(vtempFile)); if(encoding != null) { super.setEncoding(encoding); } if(vtempFile.tempFile != null) { super.setSystemId(vtempFile.tempFile.toURI().toASCIIString()); } } public VirtualTempFileInputSource(final Path file) { this(file,null); } public VirtualTempFileInputSource(final Path file, final String encoding) { this.file = Optional.ofNullable(file).map(Either::Left); if(encoding != null) { super.setEncoding(encoding); } if(file != null) { super.setSystemId(file.toUri().toASCIIString()); } } /** * @see InputSource#getByteStream() * * @throws IllegalStateException if the InputSource was previously closed */ @Override public InputStream getByteStream() { assertOpen(); return file .flatMap(f -> f.fold(this::newInputStream, this::vtfByteStream)) .orElse(null); } private Optional<Reader> inputStreamReader(final InputStream is, final String encoding) { return Optional .ofNullable(encoding) .flatMap(e -> Optional.ofNullable(is).flatMap(i -> { try { return Optional.of(new InputStreamReader(i, e)); } catch(final IOException ioe) { LOG.error(ioe); return Optional.empty(); } })); } /** * @see InputSource#getCharacterStream() * * @throws IllegalStateException if the InputSource was previously closed */ @Override public Reader getCharacterStream() { assertOpen(); return inputStreamReader(getByteStream(), getEncoding()).orElse(null); } /** * This method now does nothing, so collateral * effects from superclass with this one are avoided * * @throws IllegalStateException if the InputSource was previously closed */ @Override public void setByteStream(final InputStream is) { assertOpen(); // Nothing, so collateral effects are avoided! } /** * This method now does nothing, so collateral * effects from superclass with this one are avoided * * @throws IllegalStateException if the InputSource was previously closed */ @Override public void setCharacterStream(final Reader r) { assertOpen(); // Nothing, so collateral effects are avoided! } /** * This method now does nothing, so collateral * effects from superclass with this one are avoided * * @throws IllegalStateException if the InputSource was previously closed */ @Override public void setSystemId(final String systemId) { assertOpen(); // Nothing, so collateral effects are avoided! } /** * @see EXistInputSource#getSymbolicPath() * * @throws IllegalStateException if the InputSource was previously closed */ @Override public String getSymbolicPath() { assertOpen(); return file .flatMap(f -> f.fold(l -> Optional.of(l.toAbsolutePath().toString()), r -> Optional.ofNullable(r.tempFile).map(File::getAbsolutePath))) .orElse(null); } /** * @see EXistInputSource#getByteStreamLength() * * @throws IllegalStateException if the InputSource was previously closed */ @Override public long getByteStreamLength() { assertOpen(); return file.flatMap(f -> f.fold(this::fileSize, this::vtfSize)).orElse(-1l); } @Override protected void finalize() throws Throwable { try { close(); } finally { super.finalize(); } } @Override public void close() { if(!isClosed()) { try { file.ifPresent(f -> f.fold(l -> true, VirtualTempFile::delete)); file = Optional.empty(); } finally { super.close(); } } } private Optional<InputStream> newInputStream(final Path path) { return safeIO(() -> new BufferedInputStream(Files.newInputStream(path))); } private Optional<InputStream> vtfByteStream(final VirtualTempFile vtf) { return safeIO(() -> vtf.getByteStream()); } private Optional<Long> fileSize(final Path path) { return safeIO(() -> Files.size(path)); } private <T> Optional<T> safeIO(final SupplierE<T, IOException> isSource) { try { return Optional.of(isSource.get()); } catch(final IOException e) { LOG.error(e); return Optional.empty(); } } private Optional<Long> vtfSize(final VirtualTempFile vtf) { return Optional.of(vtf.length()); } }