/*
* Copyright 2011- Per Wendel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package spark.webserver;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import com.cinchapi.common.reflect.Reflection;
import com.cinchapi.concourse.util.Logger;
import spark.Access;
import spark.HaltException;
import spark.Request;
import spark.RequestResponseFactory;
import spark.Response;
import spark.Route;
import spark.route.HttpMethod;
import spark.route.RouteMatch;
import spark.route.RouteMatcher;
import spark.webserver.MatcherFilter;
import spark.webserver.NotConsumedException;
import spark.webserver.RequestWrapper;
import spark.webserver.ResponseWrapper;
/**
* Filter for matching of filters and routes.
*
* @author Per Wendel
*/
public class MatcherFilter implements Filter {
/**
* Check the path of the {@code servletRequest} and return {@code true} of
* that path is for a static file.
*
* @param servletRequest
* @param servletResponse
* @param chain
* @return {@code true} if the request is for a static file instead of a
* route.
*/
private static boolean isStaticFileRequest(ServletRequest servletRequest,
ServletResponse servletResponse, FilterChain chain) {
String path = Reflection.get("_pathInfo", servletRequest);
for (String ext : STATIC_FILE_EXTENSIONS) {
if(path.endsWith(ext)) {
return true;
}
}
return false;
}
private static final String ACCEPT_TYPE_REQUEST_MIME_HEADER = "Accept";
private static final String INTERNAL_ERROR = "<html><body><h2>500 Internal Error</h2></body></html>";
private static final String NOT_FOUND = "<html><body><h2>404 Not found</h2>The requested route [%s] has not been mapped in Spark</body></html>";
/**
* Valid static file extensions.
*/
private static final String[] STATIC_FILE_EXTENSIONS = { "jpg", "jpeg",
"png", "css", "ico", "gif", "js" };
private boolean hasOtherHandlers;
private boolean isServletContext;
private RouteMatcher routeMatcher;
/**
* Constructor
*
* @param routeMatcher The route matcher
* @param isServletContext If true, chain.doFilter will be invoked if
* request is not consumed by Spark.
* @param hasOtherHandlers If true, do nothing if request is not consumed by
* Spark in order to let others handlers process the request.
*/
public MatcherFilter(RouteMatcher routeMatcher, boolean isServletContext,
boolean hasOtherHandlers) {
this.routeMatcher = routeMatcher;
this.isServletContext = isServletContext;
this.hasOtherHandlers = hasOtherHandlers;
}
public void destroy() {
// TODO Auto-generated method stub
}
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse, // NOSONAR
FilterChain chain) throws IOException, ServletException { // NOSONAR
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; // NOSONAR
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
String httpMethodStr = httpRequest.getMethod().toLowerCase(); // NOSONAR
String uri = httpRequest.getRequestURI(); // NOSONAR
String acceptType = httpRequest
.getHeader(ACCEPT_TYPE_REQUEST_MIME_HEADER);
String bodyContent = null;
if(!isStaticFileRequest(servletRequest, servletResponse, chain)) {
RequestWrapper req = new RequestWrapper();
ResponseWrapper res = new ResponseWrapper();
try {
// BEFORE filters
List<RouteMatch> matchSet = routeMatcher
.findTargetsForRequestedRoute(HttpMethod.before, uri,
acceptType);
for (RouteMatch filterMatch : matchSet) {
Object filterTarget = filterMatch.getTarget();
if(filterTarget instanceof spark.Filter) {
Request request = RequestResponseFactory.create(
filterMatch, httpRequest);
Response response = RequestResponseFactory
.create(httpResponse);
spark.Filter filter = (spark.Filter) filterTarget;
req.setDelegate(request);
res.setDelegate(response);
filter.handle(req, res);
String bodyAfterFilter = Access.getBody(response);
if(bodyAfterFilter != null) {
bodyContent = bodyAfterFilter;
}
}
}
// BEFORE filters, END
HttpMethod httpMethod = HttpMethod.valueOf(httpMethodStr);
RouteMatch match = null;
match = routeMatcher.findTargetForRequestedRoute(httpMethod,
uri, acceptType);
Object target = null;
if(match != null) {
target = match.getTarget();
}
else if(httpMethod == HttpMethod.head && bodyContent == null) {
// See if get is mapped to provide default head mapping
bodyContent = routeMatcher.findTargetForRequestedRoute(
HttpMethod.get, uri, acceptType) != null ? ""
: null;
}
else if(httpMethod == HttpMethod.options && bodyContent == null) {
// CON-476: For an OPTIONS request, attempt to get all the
// targets for the route and specify those in the response
Set<HttpMethod> methods = routeMatcher
.findMethodsForRequestedPath(uri, acceptType);
if(!methods.isEmpty()) {
httpResponse.setHeader("Allow",
StringUtils.join(methods, ','));
bodyContent = "";
}
}
if(target != null) {
try {
String result = null;
if(target instanceof Route) {
Route route = ((Route) target);
Request request = RequestResponseFactory.create(
match, httpRequest);
Response response = RequestResponseFactory
.create(httpResponse);
req.setDelegate(request);
res.setDelegate(response);
Object element = route.handle(req, res);
result = route.render(element);
}
if(result != null) {
bodyContent = result;
}
}
catch (HaltException hEx) { // NOSONAR
throw hEx; // NOSONAR
}
catch (Exception e) {
Logger.error("", e);
httpResponse
.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
bodyContent = INTERNAL_ERROR;
}
}
// AFTER filters
matchSet = routeMatcher.findTargetsForRequestedRoute(
HttpMethod.after, uri, acceptType);
for (RouteMatch filterMatch : matchSet) {
Object filterTarget = filterMatch.getTarget();
if(filterTarget instanceof spark.Filter) {
Request request = RequestResponseFactory.create(
filterMatch, httpRequest);
Response response = RequestResponseFactory
.create(httpResponse);
req.setDelegate(request);
res.setDelegate(response);
spark.Filter filter = (spark.Filter) filterTarget;
filter.handle(req, res);
String bodyAfterFilter = Access.getBody(response);
if(bodyAfterFilter != null) {
bodyContent = bodyAfterFilter;
}
}
}
// AFTER filters, END
}
catch (HaltException hEx) {
httpResponse.setStatus(hEx.getStatusCode());
if(hEx.getBody() != null) {
bodyContent = hEx.getBody();
}
else {
bodyContent = "";
}
}
}
boolean consumed = bodyContent != null;
if(!consumed && hasOtherHandlers) {
throw new NotConsumedException();
}
if(!consumed && !isServletContext) {
httpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
bodyContent = String.format(NOT_FOUND, uri);
consumed = true;
}
if(consumed) {
// Write body content
if(!httpResponse.isCommitted()) {
if(httpResponse.getContentType() == null) {
httpResponse.setContentType("text/html; charset=utf-8");
}
httpResponse.getOutputStream().write(
bodyContent.getBytes("utf-8"));
}
}
else if(chain != null) {
chain.doFilter(httpRequest, httpResponse);
}
}
public void init(FilterConfig filterConfig) {
//
}
}