blob: 677d96ddfb8eb200572ba494c33e8c59254c2f3b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.url;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandlerFactory;
import java.util.Collections;
import java.util.Hashtable;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
public class EquinoxFactoryManager {
private final EquinoxContainer container;
// we need to hold these so that we can unregister them at shutdown
private volatile URLStreamHandlerFactoryImpl urlStreamHandlerFactory;
private volatile ContentHandlerFactoryImpl contentHandlerFactory;
public EquinoxFactoryManager(EquinoxContainer container) {
this.container = container;
}
public void installHandlerFactories(BundleContext context) {
installURLStreamHandlerFactory(context);
installContentHandlerFactory(context);
}
private void installURLStreamHandlerFactory(BundleContext context) {
URLStreamHandlerFactoryImpl shf = new URLStreamHandlerFactoryImpl(context, container);
try {
// first try the standard way
URL.setURLStreamHandlerFactory(shf);
} catch (Error err) {
try {
// ok we failed now use more drastic means to set the factory
forceURLStreamHandlerFactory(shf);
} catch (Throwable ex) {
container.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, ex.getMessage(), ex);
urlStreamHandlerFactory = null;
return;
}
}
urlStreamHandlerFactory = shf;
}
private static void forceURLStreamHandlerFactory(URLStreamHandlerFactoryImpl shf) throws Exception {
// force our FrameworkUtil to initialize before we register to avoid calling
// getBundle before we register this as a multiplexing handler
FrameworkUtil.asDictionary(Collections.emptyMap());
Field factoryField = getField(URL.class, URLStreamHandlerFactory.class, false);
if (factoryField == null)
throw new Exception("Could not find URLStreamHandlerFactory field"); //$NON-NLS-1$
// look for a lock to synchronize on
Object lock = getURLStreamHandlerFactoryLock();
synchronized (lock) {
URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null);
// doing a null check here just in case, but it would be really strange if it was null,
// because we failed to set the factory normally!!
if (factory != null) {
try {
factory.getClass().getMethod("isMultiplexing", (Class[]) null); //$NON-NLS-1$
Method register = factory.getClass().getMethod("register", new Class[] {Object.class}); //$NON-NLS-1$
register.invoke(factory, new Object[] {shf});
} catch (NoSuchMethodException e) {
// current factory does not support multiplexing, ok we'll wrap it
shf.setParentFactory(factory);
factory = shf;
}
}
factoryField.set(null, null);
// always attempt to clear the handlers cache
// This allows an optimization for the single framework use-case
resetURLStreamHandlers();
URL.setURLStreamHandlerFactory(factory);
}
}
private static void resetURLStreamHandlers() throws IllegalAccessException {
Field handlersField = getField(URL.class, Hashtable.class, false);
if (handlersField != null) {
@SuppressWarnings("rawtypes")
Hashtable<?, ?> handlers = (Hashtable) handlersField.get(null);
if (handlers != null)
handlers.clear();
}
}
private static Object getURLStreamHandlerFactoryLock() throws IllegalAccessException {
Object lock;
try {
Field streamHandlerLockField = URL.class.getDeclaredField("streamHandlerLock"); //$NON-NLS-1$
MultiplexingFactory.setAccessible(streamHandlerLockField);
lock = streamHandlerLockField.get(null);
} catch (NoSuchFieldException noField) {
// could not find the lock, lets sync on the class object
lock = URL.class;
}
return lock;
}
private void installContentHandlerFactory(BundleContext context) {
ContentHandlerFactoryImpl chf = new ContentHandlerFactoryImpl(context, container);
try {
// first try the standard way
URLConnection.setContentHandlerFactory(chf);
} catch (Error err) {
// ok we failed now use more drastic means to set the factory
try {
forceContentHandlerFactory(chf);
} catch (Throwable ex) {
// this is unexpected, log the exception and throw the original error
container.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, ex.getMessage(), ex);
contentHandlerFactory = null;
return;
}
}
contentHandlerFactory = chf;
}
private static void forceContentHandlerFactory(ContentHandlerFactoryImpl chf) throws Exception {
Field factoryField = getField(URLConnection.class, java.net.ContentHandlerFactory.class, false);
if (factoryField == null)
throw new Exception("Could not find ContentHandlerFactory field"); //$NON-NLS-1$
synchronized (URLConnection.class) {
java.net.ContentHandlerFactory factory = (java.net.ContentHandlerFactory) factoryField.get(null);
// doing a null check here just in case, but it would be really strange if it was null,
// because we failed to set the factory normally!!
if (factory != null) {
try {
factory.getClass().getMethod("isMultiplexing", (Class[]) null); //$NON-NLS-1$
Method register = factory.getClass().getMethod("register", new Class[] {Object.class}); //$NON-NLS-1$
register.invoke(factory, new Object[] {chf});
} catch (NoSuchMethodException e) {
// current factory does not support multiplexing, ok we'll wrap it
chf.setParentFactory(factory);
factory = chf;
}
}
// null out the field so that we can successfully call setContentHandlerFactory
factoryField.set(null, null);
// always attempt to clear the handlers cache
// This allows an optimization for the single framework use-case
resetContentHandlers();
URLConnection.setContentHandlerFactory(factory);
}
}
private static void resetContentHandlers() throws IllegalAccessException {
Field handlersField = getField(URLConnection.class, Hashtable.class, false);
if (handlersField != null) {
@SuppressWarnings("rawtypes")
Hashtable<?, ?> handlers = (Hashtable) handlersField.get(null);
if (handlers != null)
handlers.clear();
}
}
public void uninstallHandlerFactories() {
uninstallURLStreamHandlerFactory();
uninstallContentHandlerFactory();
}
private void uninstallURLStreamHandlerFactory() {
if (urlStreamHandlerFactory == null) {
return; // didn't succeed in setting the factory at launch
}
try {
Field factoryField = getField(URL.class, URLStreamHandlerFactory.class, false);
if (factoryField == null)
return; // oh well, we tried
Object lock = getURLStreamHandlerFactoryLock();
synchronized (lock) {
URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null);
if (factory == urlStreamHandlerFactory) {
factory = (URLStreamHandlerFactory) urlStreamHandlerFactory.designateSuccessor();
} else {
Method unregister = factory.getClass().getMethod("unregister", new Class[] {Object.class}); //$NON-NLS-1$
unregister.invoke(factory, new Object[] {urlStreamHandlerFactory});
}
factoryField.set(null, null);
// always attempt to clear the handlers cache
// This allows an optimization for the single framework use-case
// Note that the call to setURLStreamHandlerFactory below may clear this cache
// but we want to be sure to clear it here just in case the parent is null.
// In this case the call below would not occur.
resetURLStreamHandlers();
if (factory != null)
URL.setURLStreamHandlerFactory(factory);
}
} catch (Throwable e) {
// ignore and continue closing the framework
}
}
private void uninstallContentHandlerFactory() {
if (contentHandlerFactory == null) {
return; // didn't succeed in setting the factory at launch
}
try {
Field factoryField = getField(URLConnection.class, java.net.ContentHandlerFactory.class, false);
if (factoryField == null)
return; // oh well, we tried.
synchronized (URLConnection.class) {
java.net.ContentHandlerFactory factory = (java.net.ContentHandlerFactory) factoryField.get(null);
if (factory == contentHandlerFactory) {
factory = (java.net.ContentHandlerFactory) contentHandlerFactory.designateSuccessor();
} else {
Method unregister = factory.getClass().getMethod("unregister", new Class[] {Object.class}); //$NON-NLS-1$
unregister.invoke(factory, new Object[] {contentHandlerFactory});
}
// null out the field so that we can successfully call setContentHandlerFactory
factoryField.set(null, null);
// always attempt to clear the handlers cache
// This allows an optomization for the single framework use-case
// Note that the call to setContentHandlerFactory below may clear this cache
// but we want to be sure to clear it here just incase the parent is null.
// In this case the call below would not occur.
// Also it appears most java libraries actually do not clear the cache
// when setContentHandlerFactory is called, go figure!!
resetContentHandlers();
if (factory != null)
URLConnection.setContentHandlerFactory(factory);
}
} catch (Throwable e) {
// ignore and continue closing the framework
}
}
public static Field getField(Class<?> clazz, Class<?> type, boolean instance) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
boolean isStatic = Modifier.isStatic(field.getModifiers());
if (instance != isStatic && field.getType().equals(type)) {
MultiplexingFactory.setAccessible(field);
return field;
}
}
return null;
}
}