ManagedServletPipeline.java revision b4b7f7209570bd75352eb322825ae79392f03978
1/** 2 * Copyright (C) 2008 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.google.inject.servlet; 17 18import com.google.inject.Binding; 19import com.google.inject.Inject; 20import com.google.inject.Injector; 21import com.google.inject.Singleton; 22import com.google.inject.TypeLiteral; 23import com.google.inject.internal.util.Lists; 24import com.google.inject.internal.util.Maps; 25import com.google.inject.internal.util.Preconditions; 26import com.google.inject.internal.util.Sets; 27import java.io.IOException; 28import java.util.List; 29import java.util.Set; 30import javax.servlet.RequestDispatcher; 31import javax.servlet.ServletContext; 32import javax.servlet.ServletException; 33import javax.servlet.ServletRequest; 34import javax.servlet.ServletResponse; 35import javax.servlet.http.HttpServlet; 36import javax.servlet.http.HttpServletRequest; 37import javax.servlet.http.HttpServletRequestWrapper; 38 39/** 40 * A wrapping dispatcher for servlets, in much the same way as {@link ManagedFilterPipeline} is for 41 * filters. 42 * 43 * @author dhanji@gmail.com (Dhanji R. Prasanna) 44 */ 45@Singleton 46class ManagedServletPipeline { 47 private final ServletDefinition[] servletDefinitions; 48 private static final TypeLiteral<ServletDefinition> SERVLET_DEFS = 49 TypeLiteral.get(ServletDefinition.class); 50 51 @Inject 52 public ManagedServletPipeline(Injector injector) { 53 this.servletDefinitions = collectServletDefinitions(injector); 54 } 55 56 boolean hasServletsMapped() { 57 return servletDefinitions.length > 0; 58 } 59 60 /** 61 * Introspects the injector and collects all instances of bound {@code List<ServletDefinition>} 62 * into a master list. 63 * 64 * We have a guarantee that {@link com.google.inject.Injector#getBindings()} returns a map 65 * that preserves insertion order in entry-set iterators. 66 */ 67 private ServletDefinition[] collectServletDefinitions(Injector injector) { 68 List<ServletDefinition> servletDefinitions = Lists.newArrayList(); 69 for (Binding<ServletDefinition> entry : injector.findBindingsByType(SERVLET_DEFS)) { 70 servletDefinitions.add(entry.getProvider().get()); 71 } 72 73 // Copy to a fixed size array for speed. 74 return servletDefinitions.toArray(new ServletDefinition[servletDefinitions.size()]); 75 } 76 77 public void init(ServletContext servletContext, Injector injector) throws ServletException { 78 Set<HttpServlet> initializedSoFar 79 = Sets.newSetFromMap(Maps.<HttpServlet, Boolean>newIdentityHashMap()); 80 81 for (ServletDefinition servletDefinition : servletDefinitions) { 82 servletDefinition.init(servletContext, injector, initializedSoFar); 83 } 84 } 85 86 public boolean service(ServletRequest request, ServletResponse response) 87 throws IOException, ServletException { 88 89 //stop at the first matching servlet and service 90 for (ServletDefinition servletDefinition : servletDefinitions) { 91 if (servletDefinition.service(request, response)) { 92 return true; 93 } 94 } 95 96 //there was no match... 97 return false; 98 } 99 100 public void destroy() { 101 Set<HttpServlet> destroyedSoFar 102 = Sets.newSetFromMap(Maps.<HttpServlet, Boolean>newIdentityHashMap()); 103 for (ServletDefinition servletDefinition : servletDefinitions) { 104 servletDefinition.destroy(destroyedSoFar); 105 } 106 } 107 108 /** 109 * @return Returns a request dispatcher wrapped with a servlet mapped to 110 * the given path or null if no mapping was found. 111 */ 112 RequestDispatcher getRequestDispatcher(String path) { 113 final String newRequestUri = path; 114 115 // TODO(dhanji): check servlet spec to see if the following is legal or not. 116 // Need to strip query string if requested... 117 118 for (final ServletDefinition servletDefinition : servletDefinitions) { 119 if (servletDefinition.shouldServe(path)) { 120 return new RequestDispatcher() { 121 public void forward(ServletRequest servletRequest, ServletResponse servletResponse) 122 throws ServletException, IOException { 123 Preconditions.checkState(!servletResponse.isCommitted(), 124 "Response has been committed--you can only call forward before" 125 + " committing the response (hint: don't flush buffers)"); 126 127 // clear buffer before forwarding 128 servletResponse.resetBuffer(); 129 130 ServletRequest requestToProcess; 131 if (servletRequest instanceof HttpServletRequest) { 132 requestToProcess = new RequestDispatcherRequestWrapper(servletRequest, newRequestUri); 133 } else { 134 // This should never happen, but instead of throwing an exception 135 // we will allow a happy case pass thru for maximum tolerance to 136 // legacy (and internal) code. 137 requestToProcess = servletRequest; 138 } 139 140 servletRequest.setAttribute(REQUEST_DISPATCHER_REQUEST, Boolean.TRUE); 141 142 // now dispatch to the servlet 143 try { 144 servletDefinition.doService(requestToProcess, servletResponse); 145 } finally { 146 servletRequest.removeAttribute(REQUEST_DISPATCHER_REQUEST); 147 } 148 } 149 150 public void include(ServletRequest servletRequest, ServletResponse servletResponse) 151 throws ServletException, IOException { 152 servletRequest.setAttribute(REQUEST_DISPATCHER_REQUEST, Boolean.TRUE); 153 154 // route to the target servlet 155 try { 156 servletDefinition.doService(servletRequest, servletResponse); 157 } finally { 158 servletRequest.removeAttribute(REQUEST_DISPATCHER_REQUEST); 159 } 160 } 161 }; 162 } 163 } 164 165 //otherwise, can't process 166 return null; 167 } 168 169 /** 170 * A Marker constant attribute that when present in the request indicates to Guice servlet that 171 * this request has been generated by a request dispatcher rather than the servlet pipeline. 172 * In accordance with section 8.4.2 of the Servlet 2.4 specification. 173 */ 174 public static final String REQUEST_DISPATCHER_REQUEST = "javax.servlet.forward.servlet_path"; 175 176 private static class RequestDispatcherRequestWrapper extends HttpServletRequestWrapper { 177 private final String newRequestUri; 178 179 public RequestDispatcherRequestWrapper(ServletRequest servletRequest, String newRequestUri) { 180 super((HttpServletRequest) servletRequest); 181 this.newRequestUri = newRequestUri; 182 } 183 184 @Override 185 public String getRequestURI() { 186 return newRequestUri; 187 } 188 } 189} 190