1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.catalina.webresources;
18
19 import java.net.URL;
20 import java.net.URLStreamHandler;
21 import java.net.URLStreamHandlerFactory;
22 import java.util.List;
23 import java.util.concurrent.CopyOnWriteArrayList;
24
25 import org.apache.catalina.webresources.war.Handler;
26
27 public class TomcatURLStreamHandlerFactory implements URLStreamHandlerFactory {
28
29 private static final String WAR_PROTOCOL = "war";
30 private static final String CLASSPATH_PROTOCOL = "classpath";
31
32 // Singleton instance
33 private static volatile TomcatURLStreamHandlerFactory instance = null;
34
35 /**
36 * Obtain a reference to the singleton instance. It is recommended that
37 * callers check the value of {@link #isRegistered()} before using the
38 * returned instance.
39 *
40 * @return A reference to the singleton instance
41 */
42 public static TomcatURLStreamHandlerFactory getInstance() {
43 getInstanceInternal(true);
44 return instance;
45 }
46
47
48 private static TomcatURLStreamHandlerFactory getInstanceInternal(boolean register) {
49 // Double checked locking. OK because instance is volatile.
50 if (instance == null) {
51 synchronized (TomcatURLStreamHandlerFactory.class) {
52 if (instance == null) {
53 instance = new TomcatURLStreamHandlerFactory(register);
54 }
55 }
56 }
57 return instance;
58 }
59
60
61 private final boolean registered;
62
63 // List of factories for application defined stream handler factories.
64 private final List<URLStreamHandlerFactory> userFactories =
65 new CopyOnWriteArrayList<>();
66
67 /**
68 * Register this factory with the JVM. May be called more than once. The
69 * implementation ensures that registration only occurs once.
70 *
71 * @return <code>true</code> if the factory is already registered with the
72 * JVM or was successfully registered as a result of this call.
73 * <code>false</code> if the factory was disabled prior to this
74 * call.
75 */
76 public static boolean register() {
77 return getInstanceInternal(true).isRegistered();
78 }
79
80
81 /**
82 * Prevent this this factory from registering with the JVM. May be called
83 * more than once.
84 *
85 * @return <code>true</code> if the factory is already disabled or was
86 * successfully disabled as a result of this call.
87 * <code>false</code> if the factory was already registered prior
88 * to this call.
89
90 */
91 public static boolean disable() {
92 return !getInstanceInternal(false).isRegistered();
93 }
94
95
96 /**
97 * Release references to any user provided factories that have been loaded
98 * using the provided class loader. Called during web application stop to
99 * prevent memory leaks.
100 *
101 * @param classLoader The class loader to release
102 */
103 public static void release(ClassLoader classLoader) {
104 if (instance == null) {
105 return;
106 }
107 List<URLStreamHandlerFactory> factories = instance.userFactories;
108 for (URLStreamHandlerFactory factory : factories) {
109 ClassLoader factoryLoader = factory.getClass().getClassLoader();
110 while (factoryLoader != null) {
111 if (classLoader.equals(factoryLoader)) {
112 // Implementation note: userFactories is a
113 // CopyOnWriteArrayList, so items are removed with
114 // List.remove() instead of usual Iterator.remove()
115 factories.remove(factory);
116 break;
117 }
118 factoryLoader = factoryLoader.getParent();
119 }
120 }
121 }
122
123
124 private TomcatURLStreamHandlerFactory(boolean register) {
125 // Hide default constructor
126 // Singleton pattern to ensure there is only one instance of this
127 // factory
128 this.registered = register;
129 if (register) {
130 URL.setURLStreamHandlerFactory(this);
131 }
132 }
133
134
135 public boolean isRegistered() {
136 return registered;
137 }
138
139
140 /**
141 * Since the JVM only allows a single call to
142 * {@link URL#setURLStreamHandlerFactory(URLStreamHandlerFactory)} and
143 * Tomcat needs to register a handler, provide a mechanism to allow
144 * applications to register their own handlers.
145 *
146 * @param factory The user provided factory to add to the factories Tomcat
147 * has already registered
148 */
149 public void addUserFactory(URLStreamHandlerFactory factory) {
150 userFactories.add(factory);
151 }
152
153
154 @Override
155 public URLStreamHandler createURLStreamHandler(String protocol) {
156
157 // Tomcat's handler always takes priority so applications can't override
158 // it.
159 if (WAR_PROTOCOL.equals(protocol)) {
160 return new Handler();
161 } else if (CLASSPATH_PROTOCOL.equals(protocol)) {
162 return new ClasspathURLStreamHandler();
163 }
164
165 // Application handlers
166 for (URLStreamHandlerFactory factory : userFactories) {
167 URLStreamHandler handler =
168 factory.createURLStreamHandler(protocol);
169 if (handler != null) {
170 return handler;
171 }
172 }
173
174 // Unknown protocol
175 return null;
176 }
177 }
178