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
18 package org.apache.solr.servlet;
19
20 import java.io.IOException;
21 import java.io.PrintWriter;
22 import java.io.StringWriter;
23 import java.util.logging.Logger;
24
25 import javax.servlet.Filter;
26 import javax.servlet.FilterChain;
27 import javax.servlet.FilterConfig;
28 import javax.servlet.ServletException;
29 import javax.servlet.ServletRequest;
30 import javax.servlet.ServletResponse;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33
34 import org.apache.solr.core.SolrConfig;
35 import org.apache.solr.core.SolrCore;
36 import org.apache.solr.core.SolrException;
37 import org.apache.solr.request.QueryResponseWriter;
38 import org.apache.solr.request.SolrParams;
39 import org.apache.solr.request.SolrQueryRequest;
40 import org.apache.solr.request.SolrQueryResponse;
41 import org.apache.solr.request.SolrRequestHandler;
42
43 /**
44 * This filter looks at the incoming URL maps them to handlers defined in solrconfig.xml
45 */
46 public class SolrDispatchFilter implements Filter
47 {
48 final Logger log = Logger.getLogger(SolrDispatchFilter.class.getName());
49
50 protected SolrCore core;
51 protected SolrRequestParsers parsers;
52 protected boolean handleSelect = false;
53 protected String pathPrefix = null; // strip this from the begging of a path
54 protected String abortErrorMessage = null;
55
56 public void init(FilterConfig config) throws ServletException
57 {
58 log.info("SolrDispatchFilter.init()");
59
60 try {
61 // web.xml configuration
62 this.pathPrefix = config.getInitParameter( "path-prefix" );
63
64 // Let this filter take care of /select?xxx format
65 this.handleSelect =
66 SolrConfig.config.getBool( "requestDispatcher/@handleSelect", false );
67
68 log.info("user.dir=" + System.getProperty("user.dir"));
69 core = SolrCore.getSolrCore();
70 parsers = new SolrRequestParsers( core, SolrConfig.config );
71 }
72 catch( Throwable t ) {
73 // catch this so our filter still works
74 SolrConfig.severeErrors.add( t );
75 SolrCore.log( t );
76 }
77
78 // Optionally abort if we found a sever error
79 boolean abortOnConfigurationError = SolrConfig.config.getBool("abortOnConfigurationError",true);
80 if( abortOnConfigurationError && SolrConfig.severeErrors.size() > 0 ) {
81 StringWriter sw = new StringWriter();
82 PrintWriter out = new PrintWriter( sw );
83 out.println( "Severe errors in solr configuration.\n" );
84 out.println( "Check your log files for more detailed infomation on what may be wrong.\n" );
85 out.println( "If you want solr to continue after configuration errors, change: \n");
86 out.println( " <abortOnConfigurationError>false</abortOnConfigurationError>\n" );
87 out.println( "in solrconfig.xml\n" );
88
89 for( Throwable t : SolrConfig.severeErrors ) {
90 out.println( "-------------------------------------------------------------" );
91 t.printStackTrace( out );
92 }
93 out.flush();
94
95 // Servlet containers behave slightly differntly if you throw an exception durring
96 // initalization. Resin will display that error for every page, jetty prints it in
97 // the logs, but continues normally. (We will see a 404 rather then the real error)
98 // rather then leave the behavior undefined, lets cache the error and spit it out
99 // for every request.
100 abortErrorMessage = sw.toString();
101 //throw new ServletException( abortErrorMessage );
102 }
103
104 log.info("SolrDispatchFilter.init() done");
105 }
106
107 public void destroy() {
108 core.close();
109 }
110
111 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
112 {
113 if( abortErrorMessage != null ) {
114 ((HttpServletResponse)response).sendError( 500, abortErrorMessage );
115 return;
116 }
117
118 if( request instanceof HttpServletRequest) {
119 SolrQueryRequest solrReq = null;
120 HttpServletRequest req = (HttpServletRequest)request;
121 try {
122 String path = req.getServletPath();
123 if( req.getPathInfo() != null ) {
124 // this lets you handle /update/commit when /update is a servlet
125 path += req.getPathInfo();
126 }
127 if( pathPrefix != null && path.startsWith( pathPrefix ) ) {
128 path = path.substring( pathPrefix.length() );
129 }
130
131 int idx = path.indexOf( ':' );
132 if( idx > 0 ) {
133 // save the portion after the ':' for a 'handler' path parameter
134 path = path.substring( 0, idx );
135 }
136
137 SolrRequestHandler handler = null;
138 if( path.length() > 1 ) { // don't match "" or "/" as valid path
139 handler = core.getRequestHandler( path );
140 }
141 if( handler == null && handleSelect ) {
142 if( "/select".equals( path ) || "/select/".equals( path ) ) {
143 solrReq = parsers.parse( path, req );
144 String qt = solrReq.getParams().get( SolrParams.QT );
145 if( qt != null && qt.startsWith( "/" ) ) {
146 throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Invalid query type. Do not use /select to access: "+qt);
147 }
148 handler = core.getRequestHandler( qt );
149 if( handler == null ) {
150 throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "unknown handler: "+qt);
151 }
152 }
153 }
154 if( handler != null ) {
155 if( solrReq == null ) {
156 solrReq = parsers.parse( path, req );
157 }
158 SolrQueryResponse solrRsp = new SolrQueryResponse();
159 this.execute( req, handler, solrReq, solrRsp );
160 if( solrRsp.getException() != null ) {
161 sendError( (HttpServletResponse)response, solrRsp.getException() );
162 return;
163 }
164
165 // Now write it out
166 QueryResponseWriter responseWriter = core.getQueryResponseWriter(solrReq);
167 response.setContentType(responseWriter.getContentType(solrReq, solrRsp));
168 PrintWriter out = response.getWriter();
169 responseWriter.write(out, solrReq, solrRsp);
170 return;
171 }
172 }
173 catch( Throwable ex ) {
174 sendError( (HttpServletResponse)response, ex );
175 return;
176 }
177 finally {
178 if( solrReq != null ) {
179 solrReq.close();
180 }
181 }
182 }
183
184 // Otherwise let the webapp handle the request
185 chain.doFilter(request, response);
186 }
187
188 protected void execute( HttpServletRequest req, SolrRequestHandler handler, SolrQueryRequest sreq, SolrQueryResponse rsp) {
189 // a custom filter could add more stuff to the request before passing it on.
190 // for example: sreq.getContext().put( "HttpServletRequest", req );
191 core.execute( handler, sreq, rsp );
192 }
193
194 protected void sendError(HttpServletResponse res, Throwable ex) throws IOException
195 {
196 int code=500;
197 String trace = "";
198 if( ex instanceof SolrException ) {
199 code = ((SolrException)ex).code();
200 }
201
202 // For any regular code, don't include the stack trace
203 if( code == 500 || code < 100 ) {
204 StringWriter sw = new StringWriter();
205 ex.printStackTrace(new PrintWriter(sw));
206 trace = "\n\n"+sw.toString();
207
208 SolrException.logOnce(log,null,ex );
209
210 // non standard codes have undefined results with various servers
211 if( code < 100 ) {
212 log.warning( "invalid return code: "+code );
213 code = 500;
214 }
215 }
216 res.sendError( code, ex.getMessage() + trace );
217 }
218
219 //---------------------------------------------------------------------
220 //---------------------------------------------------------------------
221
222 /**
223 * Should the filter handle /select even if it is not mapped in solrconfig.xml
224 *
225 * This will use consistent error handling for /select?qt=xxx and /update/xml
226 *
227 */
228 public boolean isHandleSelect() {
229 return handleSelect;
230 }
231
232 public void setHandleSelect(boolean handleSelect) {
233 this.handleSelect = handleSelect;
234 }
235
236 /**
237 * set the prefix for all paths. This is useful if you want to apply the
238 * filter to something other then *.
239 *
240 * For example, if web.xml specifies:
241 *
242 * <filter-mapping>
243 * <filter-name>SolrRequestFilter</filter-name>
244 * <url-pattern>/xxx/*</url-pattern>
245 * </filter-mapping>
246 *
247 * Make sure to set the PathPrefix to "/xxx" either with this function
248 * or in web.xml
249 *
250 * <init-param>
251 * <param-name>path-prefix</param-name>
252 * <param-value>/xxx</param-value>
253 * </init-param>
254 *
255 */
256 public void setPathPrefix(String pathPrefix) {
257 this.pathPrefix = pathPrefix;
258 }
259
260 public String getPathPrefix() {
261 return pathPrefix;
262 }
263 }