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        for (String aDirList : dirList) {
194            File f = new File(dir, aDirList);
195            if (!f.isHidden()) {
196                if (f.isDirectory()) {
197                    if (!start) {
198                        ZipEntry dirEntry = new ZipEntry(relativePath + f.getName() + "/");
199                        zos.putNextEntry(dirEntry);
200                        zos.closeEntry();
201                    }
202                    String filePath = f.getPath();
203                    File file = new File(filePath);
204                    zipDir(file, relativePath + f.getName() + "/", zos, false);
205                }
206                else {
207                    ZipEntry anEntry = new ZipEntry(relativePath + f.getName());
208                    zos.putNextEntry(anEntry);
209                    InputStream is = new FileInputStream(f);
210                    byte[] arr = new byte[4096];
211                    int read = is.read(arr);
212                    while (read > -1) {
213                        zos.write(arr, 0, read);
214                        read = is.read(arr);
215                    }
216                    is.close();
217                    zos.closeEntry();
218                }
219            }
220        }
221    }
222
223    /**
224     * Creates a JAR file with the specified classes.
225     *
226     * @param baseDir local directory to create the JAR file, the staging 'classes' directory is created in there.
227     * @param jarName JAR file name, including extesion.
228     * @param classes classes to add to the JAR.
229     * @return an absolute File to the created JAR file.
230     * @throws java.io.IOException thrown if the JAR file could not be created.
231     */
232    public static File createJar(File baseDir, String jarName, Class... classes) throws IOException {
233        File classesDir = new File(baseDir, "classes");
234        for (Class clazz : classes) {
235            String classPath = clazz.getName().replace(".", "/") + ".class";
236            String classFileName = classPath;
237            if (classPath.lastIndexOf("/") > -1) {
238                classFileName = classPath.substring(classPath.lastIndexOf("/") + 1);
239            }
240            String packagePath = new File(classPath).getParent();
241            File dir = new File(classesDir, packagePath);
242            if (!dir.exists()) {
243                if (!dir.mkdirs()) {
244                    throw new IOException(XLog.format("could not create dir [{0}]", dir));
245                }
246            }
247            InputStream is = getResourceAsStream(classPath, -1);
248            OutputStream os = new FileOutputStream(new File(dir, classFileName));
249            copyStream(is, os);
250        }
251        File jar = new File(baseDir, jarName);
252        File jarDir = jar.getParentFile();
253        if (!jarDir.exists()) {
254            if (!jarDir.mkdirs()) {
255                throw new IOException(XLog.format("could not create dir [{0}]", jarDir));
256            }
257        }
258        JarOutputStream zos = new JarOutputStream(new FileOutputStream(jar), new Manifest());
259        zipDir(classesDir, "", zos);
260        return jar;
261    }
262
263    /**
264     * Close a list of resources. <p> Any thrown exceptions are suppressed.
265     * @param objects list of objects to close
266     */
267    public static void closeSafely(Closeable... objects) {
268        for (Closeable object : objects) {
269            try {
270                if (null != object) {
271                    object.close();
272                }
273            } catch (Throwable th) {
274                // ignore
275            }
276        }
277    }
278}