001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.util;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.InputStreamReader;
024import java.io.OutputStream;
025import java.io.Reader;
026import java.io.Writer;
027import java.io.File;
028import java.io.FileInputStream;
029import java.io.FileOutputStream;
030import java.io.Closeable;
031import java.util.zip.ZipOutputStream;
032import java.util.zip.ZipEntry;
033import java.util.jar.JarOutputStream;
034import java.util.jar.Manifest;
035
036/**
037 * IO Utility methods.
038 */
039public abstract class IOUtils {
040
041    /**
042     * Delete recursively a local directory.
043     *
044     * @param file directory to delete.
045     * @throws IOException thrown if the directory could not be deleted.
046     */
047    public static void delete(File file) throws IOException {
048        ParamChecker.notNull(file, "file");
049        if (file.getAbsolutePath().length() < 5) {
050            throw new RuntimeException(XLog.format("Path[{0}] is too short, not deleting", file.getAbsolutePath()));
051        }
052        if (file.exists()) {
053            if (file.isDirectory()) {
054                File[] children = file.listFiles();
055                if (children != null) {
056                    for (File child : children) {
057                        delete(child);
058                    }
059                }
060            }
061            if (!file.delete()) {
062                throw new RuntimeException(XLog.format("Could not delete path[{0}]", file.getAbsolutePath()));
063            }
064        }
065    }
066
067    /**
068     * Return a reader as string. <p>
069     *
070     * @param reader reader to read into a string.
071     * @param maxLen max content length allowed, if -1 there is no limit.
072     * @return the reader content.
073     * @throws IOException thrown if the resource could not be read.
074     */
075    public static String getReaderAsString(Reader reader, int maxLen) throws IOException {
076        ParamChecker.notNull(reader, "reader");
077        StringBuffer sb = new StringBuffer();
078        char[] buffer = new char[2048];
079        int read;
080        int count = 0;
081        while ((read = reader.read(buffer)) > -1) {
082            count += read;
083            if (maxLen > -1 && count > maxLen) {
084                throw new IllegalArgumentException(XLog.format("stream exceeds limit [{0}]", maxLen));
085            }
086            sb.append(buffer, 0, read);
087        }
088        reader.close();
089        return sb.toString();
090    }
091
092
093    /**
094     * Return a classpath resource as a stream. <p>
095     *
096     * @param path classpath for the resource.
097     * @param maxLen max content length allowed.
098     * @return the stream for the resource.
099     * @throws IOException thrown if the resource could not be read.
100     */
101    public static InputStream getResourceAsStream(String path, int maxLen) throws IOException {
102        ParamChecker.notEmpty(path, "path");
103        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
104        if (is == null) {
105            throw new IllegalArgumentException(XLog.format("resource [{0}] not found", path));
106        }
107        return is;
108    }
109
110    /**
111     * Return a classpath resource as a reader. <p> It is assumed that the resource is a text resource.
112     *
113     * @param path classpath for the resource.
114     * @param maxLen max content length allowed.
115     * @return the reader for the resource.
116     * @throws IOException thrown if the resource could not be read.
117     */
118    public static Reader getResourceAsReader(String path, int maxLen) throws IOException {
119        return new InputStreamReader(getResourceAsStream(path, maxLen));
120    }
121
122    /**
123     * Return a classpath resource as string. <p> It is assumed that the resource is a text resource.
124     *
125     * @param path classpath for the resource.
126     * @param maxLen max content length allowed.
127     * @return the resource content.
128     * @throws IOException thrown if the resource could not be read.
129     */
130    public static String getResourceAsString(String path, int maxLen) throws IOException {
131        ParamChecker.notEmpty(path, "path");
132        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
133        if (is == null) {
134            throw new IllegalArgumentException(XLog.format("resource [{0}] not found", path));
135        }
136        Reader reader = new InputStreamReader(is);
137        return getReaderAsString(reader, maxLen);
138    }
139
140    /**
141     * Copies an inputstream into an output stream.
142     *
143     * @param is inputstream to copy from.
144     * @param os outputstream to  copy to.
145     * @throws IOException thrown if the copy failed.
146     */
147    public static void copyStream(InputStream is, OutputStream os) throws IOException {
148        ParamChecker.notNull(is, "is");
149        ParamChecker.notNull(os, "os");
150        byte[] buffer = new byte[4096];
151        int read;
152        while ((read = is.read(buffer)) > -1) {
153            os.write(buffer, 0, read);
154        }
155        os.close();
156        is.close();
157    }
158
159    /**
160     * Copies an char input stream into an char output stream.
161     *
162     * @param reader reader to copy from.
163     * @param writer writer to  copy to.
164     * @throws IOException thrown if the copy failed.
165     */
166    public static void copyCharStream(Reader reader, Writer writer) throws IOException {
167        ParamChecker.notNull(reader, "reader");
168        ParamChecker.notNull(writer, "writer");
169        char[] buffer = new char[4096];
170        int read;
171        while ((read = reader.read(buffer)) > -1) {
172            writer.write(buffer, 0, read);
173        }
174        writer.close();
175        reader.close();
176    }
177
178    /**
179     * Zips a local directory, recursively, into a ZIP stream.
180     *
181     * @param dir directory to ZIP.
182     * @param relativePath basePath in the ZIP for the files, normally "/".
183     * @param zos the ZIP output stream to ZIP the directory.
184     * @throws java.io.IOException thrown if the directory could not be zipped.
185     */
186    public static void zipDir(File dir, String relativePath, ZipOutputStream zos) throws IOException {
187        zipDir(dir, relativePath, zos, true);
188        zos.close();
189    }
190
191    private static void zipDir(File dir, String relativePath, ZipOutputStream zos, boolean start) throws IOException {
192        String[] dirList = dir.list();
193
194        if (dirList != null) {
195            for (String aDirList : dirList) {
196                File f = new File(dir, aDirList);
197                if (!f.isHidden()) {
198                    if (f.isDirectory()) {
199                        if (!start) {
200                            ZipEntry dirEntry = new ZipEntry(relativePath + f.getName() + "/");
201                            zos.putNextEntry(dirEntry);
202                            zos.closeEntry();
203                        }
204                        String filePath = f.getPath();
205                        File file = new File(filePath);
206                        zipDir(file, relativePath + f.getName() + "/", zos, false);
207                    }
208                    else {
209                        ZipEntry anEntry = new ZipEntry(relativePath + f.getName());
210                        zos.putNextEntry(anEntry);
211                        InputStream is = new FileInputStream(f);
212                        byte[] arr = new byte[4096];
213                        int read = is.read(arr);
214                        while (read > -1) {
215                            zos.write(arr, 0, read);
216                            read = is.read(arr);
217                        }
218                        is.close();
219                        zos.closeEntry();
220                    }
221                }
222            }
223        }
224    }
225
226    /**
227     * Creates a JAR file with the specified classes.
228     *
229     * @param baseDir local directory to create the JAR file, the staging 'classes' directory is created in there.
230     * @param jarName JAR file name, including extesion.
231     * @param classes classes to add to the JAR.
232     * @return an absolute File to the created JAR file.
233     * @throws java.io.IOException thrown if the JAR file could not be created.
234     */
235    public static File createJar(File baseDir, String jarName, Class... classes) throws IOException {
236        File classesDir = new File(baseDir, "classes");
237        for (Class clazz : classes) {
238            String classPath = clazz.getName().replace(".", "/") + ".class";
239            String classFileName = classPath;
240            if (classPath.lastIndexOf("/") > -1) {
241                classFileName = classPath.substring(classPath.lastIndexOf("/") + 1);
242            }
243            String packagePath = new File(classPath).getParent();
244            File dir = new File(classesDir, packagePath);
245            if (!dir.exists()) {
246                if (!dir.mkdirs()) {
247                    throw new IOException(XLog.format("could not create dir [{0}]", dir));
248                }
249            }
250            InputStream is = getResourceAsStream(classPath, -1);
251            OutputStream os = new FileOutputStream(new File(dir, classFileName));
252            copyStream(is, os);
253        }
254        File jar = new File(baseDir, jarName);
255        File jarDir = jar.getParentFile();
256        if (!jarDir.exists()) {
257            if (!jarDir.mkdirs()) {
258                throw new IOException(XLog.format("could not create dir [{0}]", jarDir));
259            }
260        }
261        JarOutputStream zos = new JarOutputStream(new FileOutputStream(jar), new Manifest());
262        zipDir(classesDir, "", zos);
263        return jar;
264    }
265
266    /**
267     * Close a list of resources. <p> Any thrown exceptions are suppressed.
268     * @param objects list of objects to close
269     */
270    public static void closeSafely(Closeable... objects) {
271        for (Closeable object : objects) {
272            try {
273                if (null != object) {
274                    object.close();
275                }
276            } catch (Throwable th) {
277                // ignore
278            }
279        }
280    }
281}