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 | IllegalAccessException e) {
118            // ignore and continue closing the framework
119        }
120    }
121
122    public static void uninstallURLStreamHandlerFactory(URLStreamHandlerFactory shf) {
123        try {
124            Field factoryField = getStaticField(URL.class, URLStreamHandlerFactory.class);
125            if (factoryField == null) {
126                return; // oh well, we tried
127            }
128            Object lock = getURLStreamHandlerFactoryLock();
129            synchronized (lock) {
130                URLStreamHandlerFactory factory = (URLStreamHandlerFactory) factoryField.get(null);
131                if (factory == null) {
132                    return;
133                }
134                if (factory != factoryStackHolder) {
135                    return;
136                }
137                FactoryStack factoryStack = factoryStackHolder.get();
138                if (shf == null) {
139                    factoryStack.pop();
140                } else {
141                    factoryStack.remove(shf);
142                }
143                // reinstall factory (to flush cache)
144                flush(factoryField);
145            }
146        } catch (IllegalArgumentException | IllegalAccessException e) {
147            // ignore and continue closing the framework
148        }
149
150    }
151
152    protected static void flush(Field factoryField) throws IllegalArgumentException, IllegalAccessException {
153        factoryField.set(null, null);
154        resetURLStreamHandlers();
155        URL.setURLStreamHandlerFactory(factoryStackHolder);
156    }
157
158    private static Field getStaticField(Class<?> clazz, Class<?> type) {
159        Field[] fields = clazz.getDeclaredFields();
160        for (Field field : fields) {
161            if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(type)) {
162                field.setAccessible(true);
163                return field;
164            }
165        }
166        return null;
167    }
168
169    public static void resetURLStreamHandlers() {
170        Field handlersField = getStaticField(URL.class, Hashtable.class);
171        if (handlersField != null) {
172            try {
173                Hashtable<?, ?> handlers = (Hashtable<?, ?>) handlersField.get(null);
174                if (handlers != null) {
175                    handlers.clear();
176                }
177            } catch (IllegalAccessException e) {
178                throw new IllegalArgumentException("Cannot clear URL handlers cache", e);
179            }
180        }
181    }
182
183    private static Object getURLStreamHandlerFactoryLock() {
184        Object lock;
185        try {
186            Field streamHandlerLockField = URL.class.getDeclaredField("streamHandlerLock");
187            streamHandlerLockField.setAccessible(true);
188            lock = streamHandlerLockField.get(null);
189        } catch (NoSuchFieldException noField) {
190            // could not find the lock, lets sync on the class object
191            lock = URL.class;
192        } catch (IllegalAccessException e) {
193            throw new IllegalArgumentException(e);
194        }
195        return lock;
196    }
197
198    /**
199     * Get the underlying stack.
200     * <p>
201     * This should not be used to register/unregister factories (since it is not synchronized). To install / uninstall
202     * factories use the static method of that class.
203     */
204    public static FactoryStack getStack() {
205        return factoryStackHolder.get();
206    }
207
208    public static class FactoryStack implements URLStreamHandlerFactory {
209
210        final ArrayList<URLStreamHandlerFactory> factories = new ArrayList<>();
211
212        @Override
213        public URLStreamHandler createURLStreamHandler(String protocol) {
214            for (int i = factories.size() - 1; i >= 0; i--) {
215                URLStreamHandler h = factories.get(i).createURLStreamHandler(protocol);
216                if (h != null) {
217                    return h;
218                }
219            }
220            return null;
221        }
222
223        public void push(URLStreamHandlerFactory factory) {
224            factories.add(factory);
225        }
226
227        public URLStreamHandlerFactory pop() {
228            if (factories.isEmpty()) {
229                return null;
230            }
231            return factories.remove(factories.size() - 1);
232        }
233
234        URLStreamHandlerFactory remove(URLStreamHandlerFactory shf) {
235            return factories.remove(factories.indexOf(shf));
236        }
237
238        public URLStreamHandlerFactory peek() {
239            if (factories.isEmpty()) {
240                return null;
241            }
242            return factories.get(factories.size() - 1);
243        }
244
245        public boolean isEmpty() {
246            return factories.isEmpty();
247        }
248
249        public int size() {
250            return factories.size();
251        }
252
253        public void clear() {
254            factories.clear();
255        }
256
257    }
258
259}