Line data Source code
1 : // Copyright (C) 2012 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.httpd; 16 : 17 : import com.google.gerrit.extensions.registration.DynamicSet; 18 : import com.google.gerrit.server.plugins.Plugin; 19 : import com.google.gerrit.server.plugins.StopPluginListener; 20 : import com.google.inject.Inject; 21 : import com.google.inject.Module; 22 : import com.google.inject.Scopes; 23 : import com.google.inject.Singleton; 24 : import com.google.inject.internal.UniqueAnnotations; 25 : import com.google.inject.servlet.ServletModule; 26 : import java.io.IOException; 27 : import java.util.Iterator; 28 : import javax.servlet.Filter; 29 : import javax.servlet.FilterChain; 30 : import javax.servlet.FilterConfig; 31 : import javax.servlet.ServletException; 32 : import javax.servlet.ServletRequest; 33 : import javax.servlet.ServletResponse; 34 : 35 : /** Filters all HTTP requests passing through the server. */ 36 100 : public abstract class AllRequestFilter implements Filter { 37 : public static Module module() { 38 99 : return new ServletModule() { 39 : @Override 40 : protected void configureServlets() { 41 99 : DynamicSet.setOf(binder(), AllRequestFilter.class); 42 99 : DynamicSet.bind(binder(), AllRequestFilter.class) 43 99 : .to(AllowRenderInFrameFilter.class) 44 99 : .in(Scopes.SINGLETON); 45 : 46 99 : filter("/*").through(FilterProxy.class); 47 : 48 99 : bind(StopPluginListener.class) 49 99 : .annotatedWith(UniqueAnnotations.create()) 50 99 : .to(FilterProxy.class); 51 99 : } 52 : }; 53 : } 54 : 55 : @Singleton 56 : static class FilterProxy implements Filter, StopPluginListener { 57 : private final DynamicSet<AllRequestFilter> filters; 58 : 59 : private DynamicSet<AllRequestFilter> initializedFilters; 60 : private FilterConfig filterConfig; 61 : 62 : @Inject 63 100 : FilterProxy(DynamicSet<AllRequestFilter> filters) { 64 100 : this.filters = filters; 65 100 : this.initializedFilters = new DynamicSet<>(); 66 100 : this.filterConfig = null; 67 100 : } 68 : 69 : /** 70 : * Initializes a filter if needed 71 : * 72 : * @param filter The filter that should get initialized 73 : * @return {@code true} iff filter is now initialized 74 : * @throws ServletException if filter itself fails to init 75 : */ 76 : private synchronized boolean initFilterIfNeeded(AllRequestFilter filter) 77 : throws ServletException { 78 100 : boolean ret = true; 79 100 : if (filters.contains(filter)) { 80 : // Regardless of whether or not the caller checked filter's 81 : // containment in initializedFilters, we better re-check as we're now 82 : // synchronized. 83 100 : if (!initializedFilters.contains(filter)) { 84 100 : filter.init(filterConfig); 85 100 : initializedFilters.add("gerrit", filter); 86 : } 87 : } else { 88 0 : ret = false; 89 : } 90 100 : return ret; 91 : } 92 : 93 : private synchronized void cleanUpInitializedFilters() { 94 10 : Iterable<AllRequestFilter> filtersToCleanUp = initializedFilters; 95 10 : initializedFilters = new DynamicSet<>(); 96 10 : for (AllRequestFilter filter : filtersToCleanUp) { 97 10 : if (filters.contains(filter)) { 98 10 : initializedFilters.add("gerrit", filter); 99 : } else { 100 1 : filter.destroy(); 101 : } 102 10 : } 103 10 : } 104 : 105 : @Override 106 : public void doFilter(ServletRequest req, ServletResponse res, FilterChain last) 107 : throws IOException, ServletException { 108 39 : final Iterator<AllRequestFilter> itr = filters.iterator(); 109 39 : new FilterChain() { 110 : @Override 111 : public void doFilter(ServletRequest req, ServletResponse res) 112 : throws IOException, ServletException { 113 39 : while (itr.hasNext()) { 114 39 : AllRequestFilter filter = itr.next(); 115 : // To avoid {@code synchronized} on the whole filtering (and 116 : // thereby killing concurrency), we start the below disjunction 117 : // with an unsynchronized check for containment. This 118 : // unsynchronized check is always correct if no filters got 119 : // initialized/cleaned concurrently behind our back. 120 : // The case of concurrently initialized filters is saved by the 121 : // call to initFilterIfNeeded. So that's fine too. 122 : // The case of concurrently cleaned filters between the {@code if} 123 : // condition and the call to {@code doFilter} is not saved by 124 : // anything. If a filter is getting removed concurrently while 125 : // another thread is in those two lines, doFilter might (but need 126 : // not) fail. 127 : // 128 : // Since this failure only occurs if a filter is deleted 129 : // (e.g.: a plugin reloaded) exactly when a thread is in those 130 : // two lines, and it only breaks a single request, we're ok with 131 : // it, given that this is really both really improbable and also 132 : // the "proper" fix for it would basically kill concurrency of 133 : // webrequests. 134 39 : if (initializedFilters.contains(filter) || initFilterIfNeeded(filter)) { 135 39 : filter.doFilter(req, res, this); 136 39 : return; 137 : } 138 0 : } 139 39 : last.doFilter(req, res); 140 39 : } 141 39 : }.doFilter(req, res); 142 39 : } 143 : 144 : @Override 145 : public void init(FilterConfig config) throws ServletException { 146 : // Plugins that provide AllRequestFilters might get loaded later at 147 : // runtime, long after this init method had been called. To allow to 148 : // correctly init such plugins' AllRequestFilters, we keep the 149 : // FilterConfig around, and reuse it to lazy init the AllRequestFilters. 150 100 : filterConfig = config; 151 : 152 100 : for (AllRequestFilter f : filters) { 153 100 : initFilterIfNeeded(f); 154 100 : } 155 100 : } 156 : 157 : @Override 158 : public synchronized void destroy() { 159 100 : Iterable<AllRequestFilter> filtersToDestroy = initializedFilters; 160 100 : initializedFilters = new DynamicSet<>(); 161 100 : for (AllRequestFilter filter : filtersToDestroy) { 162 100 : filter.destroy(); 163 100 : } 164 100 : } 165 : 166 : @Override 167 : public void onStopPlugin(Plugin plugin) { 168 : // In order to allow properly garbage collection, we need to scrub 169 : // initializedFilters clean of filters stemming from plugins as they 170 : // get unloaded. 171 10 : cleanUpInitializedFilters(); 172 10 : } 173 : } 174 : 175 : @Override 176 99 : public void init(FilterConfig config) throws ServletException {} 177 : 178 : @Override 179 99 : public void destroy() {} 180 : }