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}