001/*
002 * #%L
003 * Netarchivesuite - common
004 * %%
005 * Copyright (C) 2005 - 2018 The Royal Danish 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.BufferedReader;
027import java.io.FileInputStream;
028import java.io.FileOutputStream;
029import java.io.FileReader;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.OutputStream;
033import java.nio.channels.FileChannel;
034
035import javax.servlet.jsp.JspWriter;
036
037import org.dom4j.Document;
038import org.dom4j.io.OutputFormat;
039import org.dom4j.io.XMLWriter;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043import dk.netarkivet.common.Constants;
044import dk.netarkivet.common.exceptions.ArgumentNotValid;
045import dk.netarkivet.common.exceptions.IOFailure;
046
047/**
048 * Utilities for handling streams.
049 */
050public class StreamUtils {
051
052    /** logger for this class. */
053    private static final Logger log = LoggerFactory.getLogger(StreamUtils.class);
054
055    /** Constant for UTF-8. */
056    private static final String UTF8_CHARSET = "UTF-8";
057
058    /**
059     * Will copy everything from input stream to jsp writer, closing input stream afterwards. Charset UTF-8 is assumed.
060     *
061     * @param in InputStream to copy from
062     * @param out JspWriter to copy to
063     * @throws ArgumentNotValid if either parameter is null
064     * @throws IOFailure if a read or write error happens during copy
065     */
066    public static void copyInputStreamToJspWriter(InputStream in, JspWriter out) {
067        ArgumentNotValid.checkNotNull(in, "InputStream in");
068        ArgumentNotValid.checkNotNull(out, "JspWriter out");
069
070        byte[] buf = new byte[Constants.IO_BUFFER_SIZE];
071        int read = 0;
072        try {
073            try {
074                while ((read = in.read(buf)) != -1) {
075                    String s = new String(buf, UTF8_CHARSET);
076                    if (s.length() < read) {
077                        log.debug("String length ({}) < bytes read({})", s.length(), read);
078                        out.write(s, 0, s.length());
079                    } else {
080                        out.write(s, 0, read);
081                    }
082                    // Reinitializing the buffer to avoid garbage in buffer
083                    buf = new byte[Constants.IO_BUFFER_SIZE];
084                }
085            } finally {
086                in.close();
087            }
088        } catch (IOException e) {
089            String errMsg = "Trouble copying inputstream " + in + " to JspWriter " + out;
090            log.warn(errMsg, e);
091            throw new IOFailure(errMsg, e);
092        }
093    }
094
095    /**
096     * Will copy everything from input stream to output stream, closing input stream afterwards.
097     *
098     * @param in Inputstream to copy from
099     * @param out Outputstream to copy to
100     * @throws ArgumentNotValid if either parameter is null
101     * @throws IOFailure if a read or write error happens during copy
102     */
103    public static void copyInputStreamToOutputStream(InputStream in, OutputStream out) {
104        ArgumentNotValid.checkNotNull(in, "InputStream in");
105        ArgumentNotValid.checkNotNull(out, "OutputStream out");
106
107        try {
108            try {
109                if (in instanceof FileInputStream && out instanceof FileOutputStream) {
110                    FileChannel inChannel = ((FileInputStream) in).getChannel();
111                    FileChannel outChannel = ((FileOutputStream) out).getChannel();
112                    long transferred = 0;
113                    final long fileLength = inChannel.size();
114                    do {
115                        transferred += inChannel.transferTo(transferred,
116                                Math.min(Constants.IO_CHUNK_SIZE, fileLength - transferred), outChannel);
117                    } while (transferred < fileLength);
118                } else {
119                    byte[] buf = new byte[Constants.IO_BUFFER_SIZE];
120                    int bytesRead;
121                    while ((bytesRead = in.read(buf)) != -1) {
122                        out.write(buf, 0, bytesRead);
123                    }
124                }
125                out.flush();
126            } finally {
127                in.close();
128            }
129        } catch (IOException e) {
130            String errMsg = "Trouble copying inputstream " + in + " to outputstream " + out;
131            log.warn(errMsg, e);
132            throw new IOFailure(errMsg, e);
133        }
134    }
135
136    /**
137     * Write document tree to stream. Note, the stream is flushed, but not closed.
138     *
139     * @param doc the document tree to save.
140     * @param os the stream to write xml to
141     * @throws IOFailure On trouble writing XML to stream.
142     */
143    public static void writeXmlToStream(Document doc, OutputStream os) {
144        ArgumentNotValid.checkNotNull(doc, "Document doc");
145        ArgumentNotValid.checkNotNull(doc, "OutputStream os");
146        XMLWriter xwriter = null;
147        try {
148            try {
149                OutputFormat format = OutputFormat.createPrettyPrint();
150                format.setEncoding(UTF8_CHARSET);
151                xwriter = new XMLWriter(os, format);
152                xwriter.write(doc);
153            } finally {
154                if (xwriter != null) {
155                    xwriter.close();
156                }
157                os.flush();
158            }
159        } catch (IOException e) {
160            String errMsg = "Unable to write XML to stream";
161            log.warn(errMsg, e);
162            throw new IOFailure(errMsg, e);
163        }
164    }
165
166    /**
167     * Reads an input stream and returns it as a string.
168     *
169     * @param in The input stream.
170     * @return The string content of the input stream in the UTF8-charset.
171     * @throws ArgumentNotValid If the input stream is null.
172     * @throws IOFailure If an IOException is caught while reading the inputstream.
173     */
174    public static String getInputStreamAsString(InputStream in) throws ArgumentNotValid, IOFailure {
175        ArgumentNotValid.checkNotNull(in, "InputStream in");
176
177        StringBuilder res = new StringBuilder();
178        byte[] buf = new byte[Constants.IO_BUFFER_SIZE];
179        int read = 0;
180        try {
181            try {
182                while ((read = in.read(buf)) != -1) {
183                    String s = new String(buf, UTF8_CHARSET);
184                    if (s.length() < read) {
185                        log.debug("String length ({}) < bytes read({})", s.length(), read);
186                        res.append(s, 0, s.length());
187                    } else {
188                        res.append(s, 0, read);
189                    }
190                    // Reinitializing the buffer to avoid garbage in buffer
191                    buf = new byte[Constants.IO_BUFFER_SIZE];
192                }
193            } finally {
194                in.close();
195            }
196        } catch (IOException e) {
197            String errMsg = "Trouble reading inputstream '" + in + "'";
198            log.warn(errMsg, e);
199            throw new IOFailure(errMsg, e);
200        }
201
202        return res.toString();
203    }
204    
205    /**
206     * Get FileReader as String.
207     * @param fr a given FileReader
208     * @return the FileReader as a String.
209     * @throws ArgumentNotValid If the FileReader is null.
210     * @throws IOFailure If an IOException is caught while reading the FileReader.
211     */
212    public static String getFileReaderAsString(FileReader fr) throws ArgumentNotValid, IOFailure {
213        ArgumentNotValid.checkNotNull(fr, "FileReader fr");
214
215        StringBuilder res = new StringBuilder();
216        String line = null;
217        try {
218            BufferedReader bufferreader = new BufferedReader(fr);
219            line = bufferreader.readLine();
220            while (line != null) {     
221                res.append(line);
222                res.append("\n");              
223                line = bufferreader.readLine();
224            }
225        } catch (IOException ex) {
226            String errMsg = "Trouble reading FileReader '" + fr + "'";
227            log.warn(errMsg, ex);
228            throw new IOFailure(errMsg, ex);
229        }
230        return res.toString();
231    }
232    
233    /**
234     * Convert inputStream to byte array.
235     *
236     * @param data a given InputStream
237     * @param dataLength length of the InputStream (must be larger than 0)
238     * @return byte[] containing the data in the given InputStream
239     */
240    public static byte[] inputStreamToBytes(InputStream data, int dataLength) {
241        ArgumentNotValid.checkNotNull(data, "data");
242        ArgumentNotValid.checkNotNegative(dataLength, "dataLength");
243        byte[] contents = new byte[dataLength];
244        try {
245            int read = data.read(contents, 0, dataLength);
246            if (dataLength != read) {
247                log.debug("Only read {} bytes out of the {} bytes requested", read, dataLength);
248            }
249        } catch (IOException e) {
250            throw new IOFailure("Unable to convert inputstream to byte array", e);
251        }
252        return contents;
253    }
254
255}