Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 70197bba500ea13a7a386507e8ef51952d4e41e3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/*******************************************************************************
 * 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.*;
import java.net.*;
import java.util.Hashtable;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.framework.EquinoxContainer;
import org.osgi.framework.BundleContext;

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 (Exception ex) {
				container.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, ex.getMessage(), ex);
				throw err;
			}
		}
		urlStreamHandlerFactory = shf;
	}

	private static void forceURLStreamHandlerFactory(URLStreamHandlerFactoryImpl shf) throws Exception {
		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 optomization 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 (Exception ex) {
				// this is unexpected, log the exception and throw the original error
				container.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.ERROR, ex.getMessage(), ex);
				throw err;
			}
		}
		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 optomization 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() {
		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 (Exception e) {
			// ignore and continue closing the framework
		}
	}

	private void uninstallContentHandlerFactory() {
		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 (Exception e) {
			// ignore and continue closing the framework
		}
	}

	public static Field getField(Class<?> clazz, Class<?> type, boolean instance) {
		Field[] fields = clazz.getDeclaredFields();
		for (int i = 0; i < fields.length; i++) {
			boolean isStatic = Modifier.isStatic(fields[i].getModifiers());
			if (instance != isStatic && fields[i].getType().equals(type)) {
				MultiplexingFactory.setAccessible(fields[i]);
				return fields[i];
			}
		}
		return null;
	}
}

Back to the top