001/*
002 * Copyright (c) 2006-2012 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     IBM Corporation - initial API and implementation
011 */
012package org.nuxeo.common.utils;
013
014import java.lang.reflect.Field;
015import java.lang.reflect.Modifier;
016import java.net.URL;
017import java.net.URLStreamHandler;
018import java.net.URLStreamHandlerFactory;
019import java.util.ArrayList;
020import java.util.Hashtable;
021
022/**
023 * Used to force installation of URLStreamHandlerFactory as the default mechanism in Java is failing to set a new
024 * factory if one was already set.
025 * <p>
026 * This class provides the capability to stack any number of factories - each factory having precedence over the last
027 * one.
028 * <p>
029 * Thus, when querying for a URL protocol handler all factories will be asked in turn (from the newest one to the older
030 * one) until a stream handler is obtained.
031 * <p>
032 * Contains some code from Eclipse Framework class.
033 *
034 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
035 */
036public class URLStreamHandlerFactoryInstaller {
037
038    private static final FactoryStackHolder factoryStackHolder = new FactoryStackHolder();
039
040    private static final FactoryStack stack = new FactoryStack();
041
042    private static class FactoryStackHolder extends InheritableThreadLocal<FactoryStack> implements
043            URLStreamHandlerFactory {
044
045        @Override
046        public FactoryStack initialValue() {
047            return stack;
048        }
049
050        @Override
051        public void remove() {
052            super.remove();
053        }
054
055        @Override
056        public URLStreamHandler createURLStreamHandler(String protocol) {
057            return get().createURLStreamHandler(protocol);
058        }
059    }
060
061    private URLStreamHandlerFactoryInstaller() {
062    }
063
064    public static void installURLStreamHandlerFactory(URLStreamHandlerFactory shf) {
065        FactoryStack factoryStack = factoryStackHolder.get();
066        Field factoryField = getStaticField(URL.class, URLStreamHandlerFactory.class);
067        if (factoryField == null) {
068            throw new IllegalArgumentException("Could not find URLStreamHandlerFactory field");
069        }
070        // look for a lock to synchronize on
071        Object lock = getURLStreamHandlerFactoryLock();
072        synchronized (lock) {
073            try {
074                URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null);
075                if (factory == null) { // not installed - install it
076                    factoryStack.push(shf); // push the new factory
077                } else if (factory != factoryStackHolder) { // another factory is
078                    // installed
079                    factoryStack.push(factory);
080                    factoryStack.push(shf); // push the new factory
081                } else { // already installed
082                    factoryStack.push(shf); // push the new factory
083                }
084                flush(factoryField);
085            } catch (IllegalAccessException e) {
086                throw new IllegalArgumentException(e);
087            }
088        }
089    }
090
091    public static void uninstallURLStreamHandlerFactory() {
092        factoryStackHolder.remove();
093        try {
094            Field factoryField = getStaticField(URL.class, URLStreamHandlerFactory.class);
095            if (factoryField == null) {
096                return; // oh well, we tried
097            }
098            Object lock = getURLStreamHandlerFactoryLock();
099            synchronized (lock) {
100                URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null);
101                if (factory == null) {
102                    return;
103                }
104                if (factory != factoryStackHolder) {
105                    return;
106                }
107                factoryField.set(null, null);
108                resetURLStreamHandlers();
109            }
110        } catch (IllegalArgumentException e) {
111            // ignore and continue closing the framework
112        } catch (IllegalAccessException e) {
113            // ignore and continue closing the framework
114        }
115    }
116
117    public static void uninstallURLStreamHandlerFactory(URLStreamHandlerFactory shf) {
118        try {
119            Field factoryField = getStaticField(URL.class, URLStreamHandlerFactory.class);
120            if (factoryField == null) {
121                return; // oh well, we tried
122            }
123            Object lock = getURLStreamHandlerFactoryLock();
124            synchronized (lock) {
125                URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null);
126                if (factory == null) {
127                    return;
128                }
129                if (factory != factoryStackHolder) {
130                    return;
131                }
132                FactoryStack factoryStack = factoryStackHolder.get();
133                if (shf == null) {
134                    factoryStack.pop();
135                } else {
136                    factoryStack.remove(shf);
137                }
138                // reinstall factory (to flush cache)
139                flush(factoryField);
140            }
141        } catch (IllegalArgumentException e) {
142            // ignore and continue closing the framework
143        } catch (IllegalAccessException e) {
144            // ignore and continue closing the framework
145        }
146
147    }
148
149    protected static void flush(Field factoryField) throws IllegalArgumentException, IllegalAccessException {
150        factoryField.set(null, null);
151        resetURLStreamHandlers();
152        URL.setURLStreamHandlerFactory(factoryStackHolder);
153    }
154
155    private static Field getStaticField(Class<?> clazz, Class<?> type) {
156        Field[] fields = clazz.getDeclaredFields();
157        for (Field field : fields) {
158            if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(type)) {
159                field.setAccessible(true);
160                return field;
161            }
162        }
163        return null;
164    }
165
166    public static void resetURLStreamHandlers() {
167        Field handlersField = getStaticField(URL.class, Hashtable.class);
168        if (handlersField != null) {
169            Hashtable<?, ?> handlers;
170            try {
171                handlers = (Hashtable<?, ?>) handlersField.get(null);
172            } catch (IllegalAccessException e) {
173                throw new IllegalArgumentException("Cannot clear URL handlers cache", e);
174            }
175            if (handlers != null) {
176                handlers.clear();
177            }
178        }
179    }
180
181    private static Object getURLStreamHandlerFactoryLock() {
182        Object lock;
183        try {
184            Field streamHandlerLockField = URL.class.getDeclaredField("streamHandlerLock");
185            streamHandlerLockField.setAccessible(true);
186            lock = streamHandlerLockField.get(null);
187        } catch (NoSuchFieldException noField) {
188            // could not find the lock, lets sync on the class object
189            lock = URL.class;
190        } catch (IllegalAccessException e) {
191            throw new IllegalArgumentException(e);
192        }
193        return lock;
194    }
195
196    /**
197     * Get the underlying stack.
198     * <p>
199     * This should not be used to register/unregister factories (since it is not synchronized). To install / uninstall
200     * factories use the static method of that class.
201     */
202    public static FactoryStack getStack() {
203        return factoryStackHolder.get();
204    }
205
206    public static class FactoryStack implements URLStreamHandlerFactory {
207
208        final ArrayList<URLStreamHandlerFactory> factories = new ArrayList<URLStreamHandlerFactory>();
209
210        @Override
211        public URLStreamHandler createURLStreamHandler(String protocol) {
212            for (int i = factories.size() - 1; i >= 0; i--) {
213                URLStreamHandler h = factories.get(i).createURLStreamHandler(protocol);
214                if (h != null) {
215                    return h;
216                }
217            }
218            return null;
219        }
220
221        public void push(URLStreamHandlerFactory factory) {
222            factories.add(factory);
223        }
224
225        public URLStreamHandlerFactory pop() {
226            if (factories.isEmpty()) {
227                return null;
228            }
229            return factories.remove(factories.size() - 1);
230        }
231
232        URLStreamHandlerFactory remove(URLStreamHandlerFactory shf) {
233            return factories.remove(factories.indexOf(shf));
234        }
235
236        public URLStreamHandlerFactory peek() {
237            if (factories.isEmpty()) {
238                return null;
239            }
240            return factories.get(factories.size() - 1);
241        }
242
243        public boolean isEmpty() {
244            return factories.isEmpty();
245        }
246
247        public int size() {
248            return factories.size();
249        }
250
251        public void clear() {
252            factories.clear();
253        }
254
255    }
256
257}