blob: 7ad09da4f65d375f29ed4bbee0d48cb6aaefe266 [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.felix.framework;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import org.apache.felix.framework.util.SecureAction;
import org.osgi.service.url.URLStreamHandlerService;
import org.osgi.service.url.URLStreamHandlerSetter;
/**
* <p>
* This class implements a stream handler proxy. When the stream handler
* proxy instance is created, it is associated with a particular protocol
* and will answer all future requests for handling of that stream type. It
* does not directly handle the stream handler requests, but delegates the
* requests to an underlying stream handler service.
* </p>
* <p>
* The proxy instance for a particular protocol is used for all framework
* instances that may contain their own stream handler services. When
* performing a stream handler operation, the proxy retrieves the handler
* service from the framework instance associated with the current call
* stack and delegates the call to the handler service.
* </p>
* <p>
* The proxy will create simple stream handler service trackers for each
* framework instance. The trackers will listen to service events in its
* respective framework instance to maintain a reference to the "best"
* stream handler service at any given time.
* </p>
**/
public class URLHandlersStreamHandlerProxy extends URLStreamHandler
implements URLStreamHandlerSetter, InvocationHandler
{
private static final Class[] URL_PROXY_CLASS;
private static final Class[] STRING_TYPES = new Class[]{String.class};
private static final Method EQUALS;
private static final Method GET_DEFAULT_PORT;
private static final Method GET_HOST_ADDRESS;
private static final Method HASH_CODE;
private static final Method HOSTS_EQUAL;
private static final Method OPEN_CONNECTION;
private static final Method OPEN_CONNECTION_PROXY;
private static final Method SAME_FILE;
private static final Method TO_EXTERNAL_FORM;
static {
SecureAction action = new SecureAction();
try
{
EQUALS = URLStreamHandler.class.getDeclaredMethod("equals",
new Class[]{URL.class, URL.class});
action.setAccesssible(EQUALS);
GET_DEFAULT_PORT = URLStreamHandler.class.getDeclaredMethod("getDefaultPort",
(Class[]) null);
action.setAccesssible(GET_DEFAULT_PORT);
GET_HOST_ADDRESS = URLStreamHandler.class.getDeclaredMethod(
"getHostAddress", new Class[]{URL.class});
action.setAccesssible(GET_HOST_ADDRESS);
HASH_CODE = URLStreamHandler.class.getDeclaredMethod(
"hashCode", new Class[]{URL.class});
action.setAccesssible(HASH_CODE);
HOSTS_EQUAL = URLStreamHandler.class.getDeclaredMethod(
"hostsEqual", new Class[]{URL.class, URL.class});
action.setAccesssible(HOSTS_EQUAL);
OPEN_CONNECTION = URLStreamHandler.class.getDeclaredMethod(
"openConnection", new Class[]{URL.class});
action.setAccesssible(OPEN_CONNECTION);
SAME_FILE = URLStreamHandler.class.getDeclaredMethod(
"sameFile", new Class[]{URL.class, URL.class});
action.setAccesssible(SAME_FILE);
TO_EXTERNAL_FORM = URLStreamHandler.class.getDeclaredMethod(
"toExternalForm", new Class[]{URL.class});
action.setAccesssible(TO_EXTERNAL_FORM);
}
catch (Exception ex)
{
throw new RuntimeException(ex.getMessage(), ex);
}
Method open_connection_proxy = null;
Class[] url_proxy_class = null;
try
{
url_proxy_class = new Class[]{URL.class, java.net.Proxy.class};
open_connection_proxy = URLStreamHandler.class.getDeclaredMethod(
"openConnection", url_proxy_class);
action.setAccesssible(open_connection_proxy);
}
catch (Throwable ex)
{
open_connection_proxy = null;
url_proxy_class = null;
}
OPEN_CONNECTION_PROXY = open_connection_proxy;
URL_PROXY_CLASS = url_proxy_class;
}
private final Object m_service;
private final SecureAction m_action;
private final URLStreamHandler m_builtIn;
private final URL m_builtInURL;
private final String m_protocol;
public URLHandlersStreamHandlerProxy(String protocol,
SecureAction action, URLStreamHandler builtIn, URL builtInURL)
{
m_protocol = protocol;
m_service = null;
m_action = action;
m_builtIn = builtIn;
m_builtInURL = builtInURL;
}
URLHandlersStreamHandlerProxy(Object service, SecureAction action)
{
m_protocol = null;
m_service = service;
m_action = action;
m_builtIn = null;
m_builtInURL = null;
}
//
// URLStreamHandler interface methods.
//
protected boolean equals(URL url1, URL url2)
{
Object svc = getStreamHandlerService();
if (svc == null)
{
throw new IllegalStateException(
"Unknown protocol: " + url1.getProtocol());
}
if (svc instanceof URLStreamHandlerService)
{
return ((URLStreamHandlerService) svc).equals(url1, url2);
}
try
{
return ((Boolean) EQUALS.invoke(svc, new Object[]{url1, url2})).booleanValue();
}
catch (Exception ex)
{
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
protected int getDefaultPort()
{
Object svc = getStreamHandlerService();
if (svc == null)
{
throw new IllegalStateException("Stream handler unavailable.");
}
if (svc instanceof URLStreamHandlerService)
{
return ((URLStreamHandlerService) svc).getDefaultPort();
}
try
{
return ((Integer) GET_DEFAULT_PORT.invoke(svc, null)).intValue();
}
catch (Exception ex)
{
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
protected InetAddress getHostAddress(URL url)
{
Object svc = getStreamHandlerService();
if (svc == null)
{
throw new IllegalStateException(
"Unknown protocol: " + url.getProtocol());
}
if (svc instanceof URLStreamHandlerService)
{
return ((URLStreamHandlerService) svc).getHostAddress(url);
}
try
{
return (InetAddress) GET_HOST_ADDRESS.invoke(svc, new Object[]{url});
}
catch (Exception ex)
{
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
protected int hashCode(URL url)
{
Object svc = getStreamHandlerService();
if (svc == null)
{
throw new IllegalStateException(
"Unknown protocol: " + url.getProtocol());
}
if (svc instanceof URLStreamHandlerService)
{
return ((URLStreamHandlerService) svc).hashCode(url);
}
try
{
return ((Integer) HASH_CODE.invoke(svc, new Object[]{url})).intValue();
}
catch (Exception ex)
{
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
protected boolean hostsEqual(URL url1, URL url2)
{
Object svc = getStreamHandlerService();
if (svc == null)
{
throw new IllegalStateException(
"Unknown protocol: " + url1.getProtocol());
}
if (svc instanceof URLStreamHandlerService)
{
return ((URLStreamHandlerService) svc).hostsEqual(url1, url2);
}
try
{
return ((Boolean) HOSTS_EQUAL.invoke(svc, new Object[]{url1, url2})).booleanValue();
}
catch (Exception ex)
{
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
protected URLConnection openConnection(URL url) throws IOException
{
Object svc = getStreamHandlerService();
if (svc == null)
{
throw new MalformedURLException("Unknown protocol: " + url.getProtocol());
}
if (svc instanceof URLStreamHandlerService)
{
return ((URLStreamHandlerService) svc).openConnection(url);
}
try
{
if ("http".equals(url.getProtocol()) &&
"felix.extensions".equals(url.getHost()) &&
9 == url.getPort())
{
try
{
Object handler = m_action.getDeclaredField(
ExtensionManager.class, "m_extensionManager", null);
if (handler != null)
{
return (URLConnection) m_action.invoke(
m_action.getMethod(handler.getClass(),
"openConnection", new Class[]{URL.class}), handler,
new Object[]{url});
}
throw new IOException("Extensions not supported or ambiguous context.");
}
catch (IOException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new IOException(ex.getMessage());
}
}
return (URLConnection) OPEN_CONNECTION.invoke(svc, new Object[]{url});
}
catch (IOException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
protected URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException
{
Object svc = getStreamHandlerService();
if (svc == null)
{
throw new MalformedURLException("Unknown protocol: " + url.getProtocol());
}
if (svc instanceof URLStreamHandlerService)
{
Method method;
try
{
method = svc.getClass().getMethod("openConnection", URL_PROXY_CLASS);
}
catch (NoSuchMethodException e)
{
RuntimeException rte = new UnsupportedOperationException(e.getMessage());
rte.initCause(e);
throw rte;
}
try
{
m_action.setAccesssible(method);
return (URLConnection) method.invoke(svc, new Object[]{url, proxy});
}
catch (Exception e)
{
if (e instanceof IOException)
{
throw (IOException) e;
}
throw new IOException(e.getMessage(), e);
}
}
try
{
return (URLConnection) OPEN_CONNECTION_PROXY.invoke(svc, new Object[]{url, proxy});
}
catch (Exception ex)
{
if (ex instanceof IOException)
{
throw (IOException) ex;
}
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
// We use this thread local to detect whether we have a reentrant entry to the parseURL
// method. This can happen do to some difference between gnu/classpath and sun jvms
// For more see inside the method.
private static final ThreadLocal m_loopCheck = new ThreadLocal();
protected void parseURL(URL url, String spec, int start, int limit)
{
Object svc = getStreamHandlerService();
if (svc == null)
{
throw new IllegalStateException(
"Unknown protocol: " + url.getProtocol());
}
if (svc instanceof URLStreamHandlerService)
{
((URLStreamHandlerService) svc).parseURL(this, url, spec, start, limit);
}
else
{
try
{
URL test = null;
// In order to cater for built-in urls being over-writable we need to use a
// somewhat strange hack. We use a hidden feature inside the jdk which passes
// the handler of the url given as a context to a new URL to that URL as its
// handler. This way, we can create a new URL which will use the given built-in
// handler to parse the url. Subsequently, we can use the information from that
// URL to call set with the correct values.
if (m_builtInURL != null)
{
// However, if we are on gnu/classpath we have to pass the handler directly
// because the hidden feature is not there. Funnily, the workaround to pass
// pass the handler directly doesn't work on sun as their handler detects
// that it is not the same as the one inside the url and throws an exception
// Luckily it doesn't do that on gnu/classpath. We detect that we need to
// pass the handler directly by using the m_loopCheck thread local to detect
// that we parseURL has been called inside a call to parseURL.
if (m_loopCheck.get() != null)
{
test = new URL(new URL(m_builtInURL, url.toExternalForm()), spec, (URLStreamHandler) svc);
}
else
{
// Set-up the thread local as we don't expect to be called again until we are
// done. Otherwise, we are on gnu/classpath
m_loopCheck.set(Thread.currentThread());
try
{
test = new URL(new URL(m_builtInURL, url.toExternalForm()), spec);
}
finally
{
m_loopCheck.set(null);
}
}
}
else
{
// We don't have a url with a built-in handler for this but still want to create
// the url with the buil-in handler as we could find one now. This might not
// work for all handlers on sun but it is better then doing nothing.
test = m_action.createURL(url, spec, (URLStreamHandler) svc);
}
super.setURL(url, test.getProtocol(), test.getHost(), test.getPort(),test.getAuthority(),
test.getUserInfo(), test.getPath(), test.getQuery(), test.getRef());
}
catch (Exception ex)
{
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
}
protected boolean sameFile(URL url1, URL url2)
{
Object svc = getStreamHandlerService();
if (svc == null)
{
throw new IllegalStateException(
"Unknown protocol: " + url1.getProtocol());
}
if (svc instanceof URLStreamHandlerService)
{
return ((URLStreamHandlerService) svc).sameFile(url1, url2);
}
try
{
return ((Boolean) SAME_FILE.invoke(
svc, new Object[]{url1, url2})).booleanValue();
}
catch (Exception ex)
{
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
public void setURL(
URL url, String protocol, String host, int port, String authority,
String userInfo, String path, String query, String ref)
{
super.setURL(url, protocol, host, port, authority, userInfo, path, query, ref);
}
public void setURL(
URL url, String protocol, String host, int port, String file, String ref)
{
super.setURL(url, protocol, host, port, file, ref);
}
protected String toExternalForm(URL url)
{
return toExternalForm(url, getStreamHandlerService());
}
private String toExternalForm(URL url, Object svc)
{
if (svc == null)
{
throw new IllegalStateException(
"Unknown protocol: " + url.getProtocol());
}
if (svc instanceof URLStreamHandlerService)
{
return ((URLStreamHandlerService) svc).toExternalForm(url);
}
try
{
try
{
String result = (String) TO_EXTERNAL_FORM.invoke(
svc, new Object[]{url});
// mika does return an invalid format if we have a url with the
// protocol only (<proto>://null) - we catch this case now
if ((result != null) && (result.equals(url.getProtocol() + "://null")))
{
result = url.getProtocol() + ":";
}
return result;
}
catch (InvocationTargetException ex)
{
Throwable t = ex.getTargetException();
if (t instanceof Exception)
{
throw (Exception) t;
}
else if (t instanceof Error)
{
throw (Error) t;
}
else
{
throw new IllegalStateException("Unknown throwable: " + t, t);
}
}
}
catch (NullPointerException ex)
{
// workaround for harmony and possibly J9. The issue is that
// their implementation of URLStreamHandler.toExternalForm()
// assumes that URL.getFile() doesn't return null but in our
// case it can -- hence, we catch the NPE and do the work
// ourselvs. The only difference is that we check whether the
// URL.getFile() is null or not.
StringBuilder answer = new StringBuilder();
answer.append(url.getProtocol());
answer.append(':');
String authority = url.getAuthority();
if ((authority != null) && (authority.length() > 0))
{
answer.append("//"); //$NON-NLS-1$
answer.append(url.getAuthority());
}
String file = url.getFile();
String ref = url.getRef();
if (file != null)
{
answer.append(file);
}
if (ref != null)
{
answer.append('#');
answer.append(ref);
}
return answer.toString();
}
catch (Exception ex)
{
throw new IllegalStateException("Stream handler unavailable due to: " + ex.getMessage(), ex);
}
}
/**
* <p>
* Private method to retrieve the stream handler service from the
* framework instance associated with the current call stack. A
* simple service tracker is created and cached for the associated
* framework instance when this method is called.
* </p>
* @return the stream handler service from the framework instance
* associated with the current call stack or <tt>null</tt>
* is no service is available.
**/
private Object getStreamHandlerService()
{
try
{
// Get the framework instance associated with call stack.
Object framework = URLHandlers.getFrameworkFromContext();
if (framework == null)
{
return m_builtIn;
}
Object service;
if (framework instanceof Felix)
{
service = ((Felix) framework).getStreamHandlerService(m_protocol);
}
else
{
service = m_action.invoke(
m_action.getDeclaredMethod(framework.getClass(), "getStreamHandlerService", STRING_TYPES),
framework, new Object[]{m_protocol});
}
if (service == null)
{
return m_builtIn;
}
if (service instanceof URLStreamHandlerService)
{
return (URLStreamHandlerService) service;
}
return m_action.createProxy(
m_action.getClassLoader(URLStreamHandlerService.class),
new Class[]{URLStreamHandlerService.class},
new URLHandlersStreamHandlerProxy(service, m_action));
}
catch (ThreadDeath td)
{
throw td;
}
catch (Throwable t)
{
// In case that we are inside tomcat - the problem is that the webapp classloader
// creates a new url to load a class. This gets us to this method. Now, if we
// trigger a classload while executing, tomcat is creating a new url and we end-up with
// a loop which is cut short after two iterations (because of a circularclassload).
// We catch this exception (and all others) and just return the built-in handler
// (if we have any) as this way we at least eventually get started (this just means
// that we don't use the potentially provided built-in handler overwrite).
return m_builtIn;
}
}
public Object invoke(Object obj, Method method, Object[] params)
throws Throwable
{
Class[] types = method.getParameterTypes();
if (m_service == null)
{
return m_action.invoke(m_action.getMethod(this.getClass(), method.getName(), types), this, params);
}
if ("parseURL".equals(method.getName()))
{
ClassLoader loader = m_action.getClassLoader(m_service.getClass());
types[0] = loader.loadClass(URLStreamHandlerSetter.class.getName());
params[0] = m_action.createProxy(loader, new Class[]{types[0]},
(URLHandlersStreamHandlerProxy) params[0]);
}
return m_action.invokeDirect(m_action.getDeclaredMethod(m_service.getClass(),
method.getName(), types), m_service, params);
}
}