blob: 28dbd86407ec337a0a52c6e42346211cea7d9ce0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.harmony.luni.internal.net.www.protocol.jar;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ContentHandler;
import java.net.ContentHandlerFactory;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipFile;
import org.apache.harmony.luni.internal.nls.Messages;
import org.apache.harmony.luni.util.Util;
/**
* This subclass extends {@code URLConnection}.
* <p>
*
* This class is responsible for connecting and retrieving resources from a Jar
* file which can be anywhere that can be referred to by an URL.
*/
public class JarURLConnectionImpl extends JarURLConnection {
static HashMap<URL, JarFile> jarCache = new HashMap<URL, JarFile>();
private URL jarFileURL;
private InputStream jarInput;
private JarFile jarFile;
private JarEntry jarEntry;
private boolean closed;
/**
* @param url
* the URL of the JAR
* @throws MalformedURLException
* if the URL is malformed
* @throws IOException
* if there is a problem opening the connection.
*/
public JarURLConnectionImpl(URL url) throws MalformedURLException,
IOException {
super(url);
jarFileURL = getJarFileURL();
jarFileURLConnection = jarFileURL.openConnection();
}
/**
* @see java.net.URLConnection#connect()
*/
@Override
public void connect() throws IOException {
if (!connected) {
findJarFile(); // ensure the file can be found
findJarEntry(); // ensure the entry, if any, can be found
connected = true;
}
}
/**
* Answers the Jar file referred by this {@code URLConnection}.
*
* @return the JAR file referenced by this connection
*
* @throws IOException
* thrown if an IO error occurs while connecting to the
* resource.
*/
@Override
public JarFile getJarFile() throws IOException {
connect();
return jarFile;
}
/**
* Answers the Jar file referred by this {@code URLConnection}
*
* @throws IOException
* if an IO error occurs while connecting to the resource.
*/
private void findJarFile() throws IOException {
JarFile jar = null;
if (getUseCaches()) {
synchronized (jarCache) {
jarFile = jarCache.get(jarFileURL);
}
if (jarFile == null) {
jar = openJarFile();
synchronized (jarCache) {
jarFile = jarCache.get(jarFileURL);
if (jarFile == null) {
jarCache.put(jarFileURL, jar);
jarFile = jar;
} else {
jar.close();
}
}
}
} else {
jarFile = openJarFile();
}
if (jarFile == null) {
throw new IOException();
}
}
@SuppressWarnings("nls")
JarFile openJarFile() throws IOException {
JarFile jar = null;
if (jarFileURL.getProtocol().equals("file")) {
jar = new JarFile(new File(Util.decode(jarFileURL.getFile(), false,
"UTF-8")), true, ZipFile.OPEN_READ);
} else {
final InputStream is = jarFileURL.openConnection().getInputStream();
try {
jar = AccessController
.doPrivileged(new PrivilegedAction<JarFile>() {
public JarFile run() {
try {
File tempJar = File.createTempFile(
"hyjar_", ".tmp", null);
tempJar.deleteOnExit();
FileOutputStream fos = new FileOutputStream(
tempJar);
byte[] buf = new byte[4096];
int nbytes = 0;
while ((nbytes = is.read(buf)) > -1) {
fos.write(buf, 0, nbytes);
}
fos.close();
return new JarFile(tempJar, true,
ZipFile.OPEN_READ
| ZipFile.OPEN_DELETE);
} catch (IOException e) {
return null;
}
}
});
} finally {
if (is != null) {
is.close();
}
}
}
return jar;
}
/**
* Answers the JarEntry of the entry referenced by this {@code
* URLConnection}.
*
* @return the JarEntry referenced
*
* @throws IOException
* if an IO error occurs while getting the entry
*/
@Override
public JarEntry getJarEntry() throws IOException {
connect();
return jarEntry;
}
/**
* Look up the JarEntry of the entry referenced by this {@code
* URLConnection}.
*/
private void findJarEntry() throws IOException {
if (getEntryName() == null) {
return;
}
jarEntry = jarFile.getJarEntry(getEntryName());
if (jarEntry == null) {
throw new FileNotFoundException(getEntryName());
}
}
/**
* Creates an input stream for reading from this URL Connection.
*
* @return the input stream
*
* @throws IOException
* if an IO error occurs while connecting to the resource.
*/
@Override
public InputStream getInputStream() throws IOException {
if (closed) {
// luni.33=Inputstream of the JarURLConnection has been closed
throw new IllegalStateException(Messages.getString("luni.33")); //$NON-NLS-1$
}
connect();
if (jarInput != null) {
return jarInput;
}
if (jarEntry == null) {
// luni.34=Jar entry not specified
throw new IOException(Messages.getString("luni.34")); //$NON-NLS-1$
}
return jarInput = new JarURLConnectionInputStream(jarFile
.getInputStream(jarEntry), jarFile);
}
/**
* Answers the content type of the resource. For jar file itself
* "x-java/jar" should be returned, for jar entries the content type of the
* entry should be returned. Returns non-null results ("content/unknown" for
* unknown types).
*
* @return the content type
*/
@Override
public String getContentType() {
if (url.getFile().endsWith("!/")) { //$NON-NLS-1$
// the type for jar file itself is always "x-java/jar"
return "x-java/jar"; //$NON-NLS-1$
}
String cType = null;
String entryName = getEntryName();
if (entryName != null) {
// if there is an Jar Entry, get the content type from the name
cType = guessContentTypeFromName(entryName);
} else {
try {
connect();
cType = jarFileURLConnection.getContentType();
} catch (IOException ioe) {
// Ignore
}
}
if (cType == null) {
cType = "content/unknown"; //$NON-NLS-1$
}
return cType;
}
/**
* Answers the content length of the resource. Test cases reveal that if the
* URL is referring to a Jar file, this method answers a content-length
* returned by URLConnection. For jar entry it should return it's size.
* Otherwise, it will return -1.
*
* @return the content length
*/
@Override
public int getContentLength() {
try {
connect();
if (jarEntry == null) {
return jarFileURLConnection.getContentLength();
}
return (int) getJarEntry().getSize();
} catch (IOException e) {
// Ignored
}
return -1;
}
/**
* Answers the object pointed by this {@code URL}. If this URLConnection is
* pointing to a Jar File (no Jar Entry), this method will return a {@code
* JarFile} If there is a Jar Entry, it will return the object corresponding
* to the Jar entry content type.
*
* @return a non-null object
*
* @throws IOException
* if an IO error occurred
*
* @see ContentHandler
* @see ContentHandlerFactory
* @see java.io.IOException
* @see #setContentHandlerFactory(ContentHandlerFactory)
*/
@Override
public Object getContent() throws IOException {
connect();
// if there is no Jar Entry, return a JarFile
if (jarEntry == null) {
return jarFile;
}
return super.getContent();
}
/**
* Answers the permission, in this case the subclass, FilePermission object
* which represents the permission necessary for this URLConnection to
* establish the connection.
*
* @return the permission required for this URLConnection.
*
* @throws IOException
* thrown when an IO exception occurs while creating the
* permission.
*/
@Override
public Permission getPermission() throws IOException {
return jarFileURLConnection.getPermission();
}
@Override
public boolean getUseCaches() {
return jarFileURLConnection.getUseCaches();
}
@Override
public void setUseCaches(boolean usecaches) {
jarFileURLConnection.setUseCaches(usecaches);
}
@Override
public boolean getDefaultUseCaches() {
return jarFileURLConnection.getDefaultUseCaches();
}
@Override
public void setDefaultUseCaches(boolean defaultusecaches) {
jarFileURLConnection.setDefaultUseCaches(defaultusecaches);
}
/**
* Closes the cached files.
*/
public static void closeCachedFiles() {
Set<Map.Entry<URL, JarFile>> s = jarCache.entrySet();
synchronized (jarCache) {
Iterator<Map.Entry<URL, JarFile>> i = s.iterator();
while (i.hasNext()) {
try {
ZipFile zip = i.next().getValue();
if (zip != null) {
zip.close();
}
} catch (IOException e) {
// Ignored
}
}
}
}
private class JarURLConnectionInputStream extends FilterInputStream {
InputStream inputStream;
JarFile jarFile;
protected JarURLConnectionInputStream(InputStream in, JarFile file) {
super(in);
inputStream = in;
jarFile = file;
}
@Override
public void close() throws IOException {
super.close();
if (!getUseCaches()) {
closed = true;
jarFile.close();
}
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public int read(byte[] buf, int off, int nbytes) throws IOException {
return inputStream.read(buf, off, nbytes);
}
@Override
public long skip(long nbytes) throws IOException {
return inputStream.skip(nbytes);
}
}
}