/* * Copyright (C) 2013 eXo Platform SAS. * * This 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.1 of * the License, or (at your option) any later version. * * This software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.exoplatform.container.ar; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; /** * This implementation of an {@link URLConnection} allows to get a content from an archive even if * the archive is not a zip file but simply a folder. * * <p>The syntax of a AR URL is: * * <pre> * ar:<path>(!/{entry}(/!{sub-entry})) * </pre> * * <p>for example: * * <p><code> * ar:/path/to/my/archive.zip<br> * </code> * * <p><code> * ar:/path/to/my/archive.zip!/path/to/my/file<br> * </code> * * <p><code> * ar:/path/to/my/archive.zip!/path/to/my/archive2.zip!/path/to/my/file<br> * </code> * * @author <a href="mailto:nfilotto@exoplatform.com">Nicolas Filotto</a> * @version $Id$ * */ class ArchiveURLConnection extends URLConnection { private static final Log LOG = ExoLogger.getLogger("exo.kernel.container.ArchiveURLConnection"); /** * In case it is simply a direct file access */ private File directAccess; /** * The zip file corresponding to the root archive */ private ZipFile zipFile; /** * The list containing all the name entries */ private List<String> nameEntries; /** * @param url */ ArchiveURLConnection(URL url) { super(url); } /** * @see java.net.URLConnection#connect() */ @Override public void connect() throws IOException { this.connected = true; String file = url.getFile(); int index = file.indexOf("!/"); if (index == -1) { // It is a direct file access this.directAccess = new File(file); if (!directAccess.exists()) throw new FileNotFoundException("Cannot find the file at " + file); if (directAccess.isDirectory()) throw new IOException("A file was expected at " + file); return; } File f = new File(file.substring(0, index)); if (!f.exists()) throw new FileNotFoundException("Cannot find the file at " + f.getAbsolutePath()); if (f.isDirectory()) throw new IOException("A zip file was expected at " + f.getAbsolutePath()); this.zipFile = new ZipFile(f); try { int fromIndex = index + 2; index = file.indexOf("!/", fromIndex); String nameEntry; if (index == -1) nameEntry = file.substring(fromIndex); else nameEntry = file.substring(fromIndex, index); ZipEntry entry = zipFile.getEntry(nameEntry); if (entry == null) throw new FileNotFoundException("Cannot find the file at " + file.substring(0, index == -1 ? file.length() : index)); if (zipFile.getEntry(nameEntry + "/") != null) throw new IOException("A " + (index == -1 ? "" : "zip") + " file was expected at " + file.substring(0, index == -1 ? file.length() : index)); nameEntries = new ArrayList<String>(); nameEntries.add(nameEntry); if (index == -1) { // there is no more entries return; } nameEntry = file.substring(index + 2); // We add it without checking if it exists to avoid reading twice the ZipInputStream nameEntries.add(nameEntry); } catch (IOException e) { try { zipFile.close(); } catch (IOException e2) { LOG.debug("Could not close the zip file"); } throw e; } catch (RuntimeException e) { try { zipFile.close(); } catch (IOException e2) { LOG.debug("Could not close the zip file"); } throw e; } } /** * @see java.net.URLConnection#getContentLength() */ @Override public int getContentLength() { if (connected) { if (directAccess != null) return (int)directAccess.length(); if (zipFile != null && nameEntries != null && nameEntries.size() == 1) { ZipEntry entry = zipFile.getEntry(nameEntries.get(0)); if (entry != null) return (int)entry.getSize(); } } return -1; } /** * @see java.net.URLConnection#getInputStream() */ @Override public InputStream getInputStream() throws IOException { if (!connected) connect(); if (directAccess != null) return new FileInputStream(directAccess); if (zipFile == null) throw new IOException("no zip file defined"); try { if (nameEntries == null || nameEntries.isEmpty()) throw new IOException("no entry name specified"); ZipEntry entry = zipFile.getEntry(nameEntries.get(0)); if (entry == null) throw new FileNotFoundException("The entry " + nameEntries.get(0) + " could not be found"); if (nameEntries.size() == 1) { return new ArchiveInputStream(zipFile.getInputStream(entry)); } String nameEntry = nameEntries.get(1); ZipInputStream zis = new ZipInputStream(zipFile.getInputStream(entry)); boolean closeZis = true; try { ZipEntry subZP; ZipEntry found = null; while ((subZP = zis.getNextEntry()) != null) { if (subZP.getName().equals(nameEntry) || subZP.getName().equals(nameEntry + "/")) { found = subZP; break; } } if (found == null) throw new FileNotFoundException("Cannot find the file at " + url.getFile()); if (found.isDirectory()) throw new IOException("A file was expected at " + url.getFile()); closeZis = false; return new ArchiveInputStream(zis); } finally { if (closeZis) { try { zis.close(); } catch (IOException e) { LOG.debug("Could not close the zip input stream"); } } } } catch (IOException e) { try { zipFile.close(); } catch (IOException e2) { LOG.debug("Could not close the zip file"); } throw e; } catch (RuntimeException e) { try { zipFile.close(); } catch (IOException e2) { LOG.debug("Could not close the zip file"); } throw e; } } /** * This class is needed to properly close the {@link ZipFile} when we close the stream */ private class ArchiveInputStream extends FilterInputStream { protected ArchiveInputStream(InputStream in) { super(in); } @Override public void close() throws IOException { try { super.close(); } finally { try { zipFile.close(); } catch (IOException e2) { LOG.debug("Could not close the zip file"); } } } } }