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