001/*
002 * #%L
003 * Netarchivesuite - common
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.common.utils;
025
026import java.io.FileInputStream;
027import java.io.FileOutputStream;
028import java.io.IOException;
029import java.io.InputStream;
030import java.io.OutputStream;
031import java.nio.channels.FileChannel;
032
033import javax.servlet.jsp.JspWriter;
034
035import org.dom4j.Document;
036import org.dom4j.io.OutputFormat;
037import org.dom4j.io.XMLWriter;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041import dk.netarkivet.common.Constants;
042import dk.netarkivet.common.exceptions.ArgumentNotValid;
043import dk.netarkivet.common.exceptions.IOFailure;
044
045/**
046 * Utilities for handling streams.
047 */
048public class StreamUtils {
049
050    /** logger for this class. */
051    private static final Logger log = LoggerFactory.getLogger(StreamUtils.class);
052
053    /** Constant for UTF-8. */
054    private static final String UTF8_CHARSET = "UTF-8";
055
056    /**
057     * Will copy everything from input stream to jsp writer, closing input stream afterwards. Charset UTF-8 is assumed.
058     *
059     * @param in Inputstream to copy from
060     * @param out JspWriter to copy to
061     * @throws ArgumentNotValid if either parameter is null
062     * @throws IOFailure if a read or write error happens during copy
063     */
064    public static void copyInputStreamToJspWriter(InputStream in, JspWriter out) {
065        ArgumentNotValid.checkNotNull(in, "InputStream in");
066        ArgumentNotValid.checkNotNull(out, "JspWriter out");
067
068        byte[] buf = new byte[Constants.IO_BUFFER_SIZE];
069        int read = 0;
070        try {
071            try {
072                while ((read = in.read(buf)) != -1) {
073                    out.write(new String(buf, UTF8_CHARSET), 0, read);
074                }
075            } finally {
076                in.close();
077            }
078        } catch (IOException e) {
079            String errMsg = "Trouble copying inputstream " + in + " to JspWriter " + out;
080            log.warn(errMsg, e);
081            throw new IOFailure(errMsg, e);
082        }
083    }
084
085    /**
086     * Will copy everything from input stream to output stream, closing input stream afterwards.
087     *
088     * @param in Inputstream to copy from
089     * @param out Outputstream to copy to
090     * @throws ArgumentNotValid if either parameter is null
091     * @throws IOFailure if a read or write error happens during copy
092     */
093    public static void copyInputStreamToOutputStream(InputStream in, OutputStream out) {
094        ArgumentNotValid.checkNotNull(in, "InputStream in");
095        ArgumentNotValid.checkNotNull(out, "OutputStream out");
096
097        try {
098            try {
099                if (in instanceof FileInputStream && out instanceof FileOutputStream) {
100                    FileChannel inChannel = ((FileInputStream) in).getChannel();
101                    FileChannel outChannel = ((FileOutputStream) out).getChannel();
102                    long transferred = 0;
103                    final long fileLength = inChannel.size();
104                    do {
105                        transferred += inChannel.transferTo(transferred,
106                                Math.min(Constants.IO_CHUNK_SIZE, fileLength - transferred), outChannel);
107                    } while (transferred < fileLength);
108                } else {
109                    byte[] buf = new byte[Constants.IO_BUFFER_SIZE];
110                    int bytesRead;
111                    while ((bytesRead = in.read(buf)) != -1) {
112                        out.write(buf, 0, bytesRead);
113                    }
114                }
115                out.flush();
116            } finally {
117                in.close();
118            }
119        } catch (IOException e) {
120            String errMsg = "Trouble copying inputstream " + in + " to outputstream " + out;
121            log.warn(errMsg, e);
122            throw new IOFailure(errMsg, e);
123        }
124    }
125
126    /**
127     * Write document tree to stream. Note, the stream is flushed, but not closed.
128     *
129     * @param doc the document tree to save.
130     * @param os the stream to write xml to
131     * @throws IOFailure On trouble writing XML to stream.
132     */
133    public static void writeXmlToStream(Document doc, OutputStream os) {
134        ArgumentNotValid.checkNotNull(doc, "Document doc");
135        ArgumentNotValid.checkNotNull(doc, "OutputStream os");
136        XMLWriter xwriter = null;
137        try {
138            try {
139                OutputFormat format = OutputFormat.createPrettyPrint();
140                format.setEncoding(UTF8_CHARSET);
141                xwriter = new XMLWriter(os, format);
142                xwriter.write(doc);
143            } finally {
144                if (xwriter != null) {
145                    xwriter.close();
146                }
147                os.flush();
148            }
149        } catch (IOException e) {
150            String errMsg = "Unable to write XML to stream";
151            log.warn(errMsg, e);
152            throw new IOFailure(errMsg, e);
153        }
154    }
155
156    /**
157     * Reads an input stream and returns it as a string.
158     *
159     * @param in The input stream.
160     * @return The string content of the input stream in the UTF8-charset.
161     * @throws ArgumentNotValid If the input stream is null.
162     * @throws IOFailure If an IOException is caught while reading the inputstream.
163     */
164    public static String getInputStreamAsString(InputStream in) throws ArgumentNotValid, IOFailure {
165        ArgumentNotValid.checkNotNull(in, "InputStream in");
166
167        StringBuilder res = new StringBuilder();
168        byte[] buf = new byte[Constants.IO_BUFFER_SIZE];
169        int read = 0;
170        try {
171            try {
172                while ((read = in.read(buf)) != -1) {
173                    res.append(new String(buf, UTF8_CHARSET), 0, read);
174                }
175            } finally {
176                in.close();
177            }
178        } catch (IOException e) {
179            String errMsg = "Trouble reading inputstream '" + in + "'";
180            log.warn(errMsg, e);
181            throw new IOFailure(errMsg, e);
182        }
183
184        return res.toString();
185    }
186
187    /**
188     * Convert inputStream to byte array.
189     *
190     * @param data inputstream
191     * @param dataLength length of inputstream (must be larger than 0)
192     * @return byte[] containing data in inputstream
193     */
194    public static byte[] inputStreamToBytes(InputStream data, int dataLength) {
195        ArgumentNotValid.checkNotNull(data, "data");
196        ArgumentNotValid.checkNotNegative(dataLength, "dataLength");
197        byte[] contents = new byte[dataLength];
198        try {
199            int read = data.read(contents, 0, dataLength);
200            if (dataLength != read) {
201                log.debug("Only read {} bytes out of the {} bytes requested", read, dataLength);
202            }
203        } catch (IOException e) {
204            throw new IOFailure("Unable to convert inputstream to byte array", e);
205        }
206        return contents;
207    }
208
209}