/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 groovy.servlet;
import groovy.util.
ResourceConnector;
import groovy.util.
ResourceException;
import javax.servlet.
ServletConfig;
import javax.servlet.
ServletContext;
import javax.servlet.
ServletException;
import javax.servlet.http.
HttpServlet;
import javax.servlet.http.
HttpServletRequest;
import java.io.
File;
import java.io.
IOException;
import java.net.
MalformedURLException;
import java.net.
URI;
import java.net.
URISyntaxException;
import java.net.
URL;
import java.net.
URLConnection;
import java.util.regex.
Matcher;
import java.util.regex.
Pattern;
/**
* A base class dealing with common HTTP servlet API housekeeping aspects.
* <p>
* <h4>Resource name mangling (pattern replacement)</h4>
* <p>
* Also implements Groovy's {@link groovy.util.ResourceConnector} in a dynamic
* manner. It allows you to modify the resource name that is searched for with a
* <i>replace all</i> operation. See {@link java.util.regex.Pattern} and
* {@link java.util.regex.Matcher} for details.
* The servlet init parameter names are:
* <pre>
* {@value #INIT_PARAM_RESOURCE_NAME_REGEX} = empty - defaults to null
* resource.name.replacement = empty - defaults to null
* resource.name.replace.all = true (default) | false means replaceFirst()
* </pre>
* Note: If you specify a regex, you have to specify a replacement string too!
* Otherwise an exception gets raised.
* <p>
* <h4>Logging and bug-hunting options</h4>
* <p>
* This implementation provides a verbosity flag switching log statements.
* The servlet init parameter name is:
* <pre>
* verbose = false(default) | true
* </pre>
* <p>
* In order to support class-loading-troubles-debugging with Tomcat 4 or
* higher, you can log the class loader responsible for loading some classes.
* See <a href="https://issues.apache.org/jira/browse/GROOVY-861">GROOVY-861</a> for details.
* The servlet init parameter name is:
* <pre>
* log.GROOVY861 = false(default) | true
* </pre>
* <p>
* If you experience class-loading-troubles with Tomcat 4 (or higher) or any
* other servlet container using custom class loader setups, you can fallback
* to use (slower) reflection in Groovy's MetaClass implementation. Please
* contact the dev team with your problem! Thanks.
* The servlet init parameter name is:
* <pre>
* reflection = false(default) | true
* </pre>
*
* @author Christian Stein
* @author Roshan Dawrani (roshandawrani)
*/
public abstract class
AbstractHttpServlet extends
HttpServlet implements
ResourceConnector {
public static final
String INIT_PARAM_RESOURCE_NAME_REGEX = "resource.name.regex";
public static final
String INIT_PARAM_RESOURCE_NAME_REGEX_FLAGS = "resource.name.regex.flags";
/**
* Content type of the HTTP response.
*/
public static final
String CONTENT_TYPE_TEXT_HTML = "text/html";
/**
* Servlet API include key name: path_info
*/
public static final
String INC_PATH_INFO = "javax.servlet.include.path_info";
/* *** Not used, yet. See comments in getScriptUri(HttpServletRequest). ***
* Servlet API include key name: request_uri
*/
public static final
String INC_REQUEST_URI = "javax.servlet.include.request_uri";
/**
* Servlet API include key name: servlet_path
*/
public static final
String INC_SERVLET_PATH = "javax.servlet.include.servlet_path";
/**
* Servlet (or the web application) context.
*/
protected
ServletContext servletContext;
/**
* Either <code>null</code> or a compiled pattern read from "{@value #INIT_PARAM_RESOURCE_NAME_REGEX}"
* and used in {@link AbstractHttpServlet#getScriptUri(HttpServletRequest)}.
*/
protected
Pattern resourceNamePattern;
/**
* The replacement used by the resource name matcher.
*/
protected
String resourceNameReplacement;
/**
* The replace method to use on the matcher.
* <pre>
* true - replaceAll(resourceNameReplacement); (default)
* false - replaceFirst(resourceNameReplacement);
* </pre>
*/
protected boolean
resourceNameReplaceAll;
/**
* Controls almost all log output.
*/
protected boolean
verbose;
/**
* Encoding to use, becomes charset part of contentType.
*/
protected
String encoding = "UTF-8";
/**
* Mirrors the static value of the reflection flag in MetaClass.
* See AbstractHttpServlet#logGROOVY861
*/
protected boolean
reflection;
/**
* Debug flag logging the class the class loader of the request.
*/
private boolean
logGROOVY861;
/** a.fink: it was in {@link #removeNamePrefix}, but was extracted to var for optimization*/
protected
String namePrefix;
/**
* Initializes all fields with default values.
*/
public
AbstractHttpServlet () {
this.
servletContext = null;
this.
resourceNameReplacement = null;
this.
resourceNameReplaceAll = true;
this.
verbose = false;
this.
reflection = false;
this.
logGROOVY861 = false;
}
protected void
generateNamePrefixOnce () {
URI uri = null;
String realPath =
servletContext.
getRealPath("/");
if (
realPath != null) {
uri = new
File(
realPath).
toURI();}//prevent NPE if in .war
try {
URL res =
servletContext.
getResource("/");
if (
res != null) {
uri =
res.
toURI(); }
} catch (
MalformedURLException ignore) {
} catch (
URISyntaxException ignore) {
}
if (
uri != null) {
try {
namePrefix =
uri.
toURL().
toExternalForm();
return;
} catch (
MalformedURLException e) {
log("generateNamePrefixOnce [ERROR] Malformed URL for base path / == '"+
uri +'\'',
e);
}
}
namePrefix = "";
}
protected
String removeNamePrefix(
String name) throws
ResourceException {
if (
namePrefix == null) {
generateNamePrefixOnce();
}
if (
name.
startsWith(
namePrefix)) {//usualy name has text
return
name.
substring(
namePrefix.
length());
}
return
name;
}
/**
* Interface method for ResourceContainer. This is used by the GroovyScriptEngine.
*/
public
URLConnection getResourceConnection (
String name) throws
ResourceException {
name =
removeNamePrefix(
name).
replace('\\', '/');
//remove the leading / as we are trying with a leading / now
if (
name.
startsWith("WEB-INF/groovy/")) {
name =
name.
substring(15);//just for uniformity
} else if (
name.
startsWith("/")) {
name =
name.
substring(1);
}
/*
* Try to locate the resource and return an opened connection to it.
*/
try {
URL url =
servletContext.
getResource('/' +
name);
if (
url == null) {
url =
servletContext.
getResource("/WEB-INF/groovy/" +
name);
}
if (
url == null) {
throw new
ResourceException("Resource \"" +
name + "\" not found!");
}
return
url.
openConnection();
} catch (
IOException e) {
throw new
ResourceException("Problems getting resource named \"" +
name + "\"!",
e);
}
}
/**
* Returns the include-aware uri of the script or template file.
*
* @param request the http request to analyze
* @return the include-aware uri either parsed from request attributes or
* hints provided by the servlet container
*/
protected
String getScriptUri(
HttpServletRequest request) {
/*
* Log some debug information for https://issues.apache.org/jira/browse/GROOVY-861
*/
if (
logGROOVY861) {
log("Logging request class and its class loader:");
log(" c = request.getClass() :\"" +
request.
getClass() + "\"");
log(" l = c.getClassLoader() :\"" +
request.
getClass().
getClassLoader() + "\"");
log(" l.getClass() :\"" +
request.
getClass().
getClassLoader().
getClass() + "\"");
/*
* Keep logging, if we're verbose. Else turn it off.
*/
logGROOVY861 =
verbose;
}
//
// NOTE: This piece of code is heavily inspired by Apaches Jasper2!
//
// http://cvs.apache.org/viewcvs.cgi/jakarta-tomcat-jasper/jasper2/ \
// src/share/org/apache/jasper/servlet/JspServlet.java?view=markup
//
// Why doesn't it use request.getRequestURI() or INC_REQUEST_URI?
//
String uri = null;
String info = null;
//
// Check to see if the requested script/template source file has been the
// target of a RequestDispatcher.include().
//
uri = (
String)
request.
getAttribute(
INC_SERVLET_PATH);
if (
uri != null) {
//
// Requested script/template file has been target of
// RequestDispatcher.include(). Its path is assembled from the relevant
// javax.servlet.include.* request attributes and returned!
//
info = (
String)
request.
getAttribute(
INC_PATH_INFO);
if (
info != null) {
uri +=
info;
}
return
applyResourceNameMatcher(
uri);
}
//
// Requested script/template file has not been the target of a
// RequestDispatcher.include(). Reconstruct its path from the request's
// getServletPath() and getPathInfo() results.
//
uri =
request.
getServletPath();
info =
request.
getPathInfo();
if (
info != null) {
uri +=
info;
}
/*
* TODO : Enable auto ".groovy" extension replacing here!
* http://cvs.groovy.codehaus.org/viewrep/groovy/groovy/groovy-core/src/main/groovy/servlet/GroovyServlet.java?r=1.10#l259
*/
return
applyResourceNameMatcher(
uri);
}
protected
String applyResourceNameMatcher (
String uri) {
if (
resourceNamePattern != null) {// mangle resource name with the compiled pattern.
Matcher matcher =
resourceNamePattern.
matcher(
uri);
String replaced;
if (
resourceNameReplaceAll) {
replaced =
matcher.
replaceAll(
resourceNameReplacement);
} else {
replaced =
matcher.
replaceFirst(
resourceNameReplacement);
}
if (!
uri.
equals(
replaced)) {
if (
verbose) {
log("Replaced resource name \"" +
uri + "\" with \"" +
replaced + "\".");
}
return
replaced;
}
}
return
uri;
}
/**
* Parses the http request for the real script or template source file.
*
* @param request
* the http request to analyze
* @return a file object using an absolute file path name, or <code>null</code> if the
* servlet container cannot translate the virtual path to a real
* path for any reason (such as when the content is being made
* available from a .war archive).
*/
protected
File getScriptUriAsFile(
HttpServletRequest request) {
String uri =
getScriptUri(
request);
String real =
servletContext.
getRealPath(
uri);
if (
real == null) {
return null;
}
return new
File(
real).
getAbsoluteFile();
}
/**
* Overrides the generic init method to set some debug flags.
*
* @param config the servlet configuration provided by the container
* @throws ServletException if init() method defined in super class
* javax.servlet.GenericServlet throws it
*/
public void
init(
ServletConfig config) throws
ServletException {
/*
* Never forget super.init()!
*/
super.init(
config);
/*
* Grab the servlet context.
*/
this.
servletContext =
config.
getServletContext();
// Get verbosity hint.
String value =
config.
getInitParameter("verbose");
if (
value != null) {
this.
verbose =
Boolean.
valueOf(
value);
}
// get encoding
value =
config.
getInitParameter("encoding");
if (
value != null) {
this.
encoding =
value;
}
// And now the real init work...
if (
verbose) {
log("Parsing init parameters...");
}
String regex =
config.
getInitParameter(
INIT_PARAM_RESOURCE_NAME_REGEX);
if (
regex != null) {
String replacement =
config.
getInitParameter("resource.name.replacement");
if (
replacement == null) {
Exception npex = new
NullPointerException("resource.name.replacement");
String message = "Init-param 'resource.name.replacement' not specified!";
log(
message,
npex);
throw new
ServletException(
message,
npex);
} else if ("EMPTY_STRING".
equals(
replacement)) {//<param-value></param-value> is prohibited
replacement = "";
}
int
flags = 0; // TODO : Parse pattern compile flags (literal names).
String flagsStr =
config.
getInitParameter(
INIT_PARAM_RESOURCE_NAME_REGEX_FLAGS);
if (
flagsStr != null &&
flagsStr.
length() > 0) {
flags =
Integer.
decode(
flagsStr.
trim());//throws NumberFormatException
}
resourceNamePattern =
Pattern.
compile(
regex,
flags);
this.
resourceNameReplacement =
replacement;
String all =
config.
getInitParameter("resource.name.replace.all");
if (
all != null) {
this.
resourceNameReplaceAll =
Boolean.
valueOf(
all.
trim());
}
}
value =
config.
getInitParameter("logGROOVY861");
if (
value != null) {
this.
logGROOVY861 =
Boolean.
valueOf(
value);
// nothing else to do here
}
/*
* If verbose, log the parameter values.
*/
if (
verbose) {
log("(Abstract) init done. Listing some parameter name/value pairs:");
log("verbose = " +
verbose); // this *is* verbose! ;)
log("reflection = " +
reflection);
log("logGROOVY861 = " +
logGROOVY861);
log(
INIT_PARAM_RESOURCE_NAME_REGEX + " = " +
resourceNamePattern);//toString == pattern
log("resource.name.replacement = " +
resourceNameReplacement);
}
}
/**
* Override this method to set your variables to the Groovy binding.
* <p>
* All variables bound the binding are passed to the template source text,
* e.g. the HTML file, when the template is merged.
* <p>
* The binding provided by TemplateServlet does already include some default
* variables. As of this writing, they are (copied from
* {@link groovy.servlet.ServletBinding}):
* <ul>
* <li><tt>"request"</tt> : HttpServletRequest </li>
* <li><tt>"response"</tt> : HttpServletResponse </li>
* <li><tt>"context"</tt> : ServletContext </li>
* <li><tt>"application"</tt> : ServletContext </li>
* <li><tt>"session"</tt> : request.getSession(<b>false</b>) </li>
* </ul>
* <p>
* And via implicit hard-coded keywords:
* <ul>
* <li><tt>"out"</tt> : response.getWriter() </li>
* <li><tt>"sout"</tt> : response.getOutputStream() </li>
* <li><tt>"html"</tt> : new MarkupBuilder(response.getWriter()) </li>
* </ul>
* <p>
* The binding also provides convenient methods:
* <ul>
* <li><tt>"forward(String path)"</tt> : request.getRequestDispatcher(path).forward(request, response);</li>
* <li><tt>"include(String path)"</tt> : request.getRequestDispatcher(path).include(request, response);</li>
* <li><tt>"redirect(String location)"</tt> : response.sendRedirect(location);</li>
* </ul>
* <p>
* <p>Example binding all servlet context variables:
* <pre><code>
* class MyServlet extends TemplateServlet {
*
* protected void setVariables(ServletBinding binding) {
* // Bind a simple variable
* binding.setVariable("answer", new Long(42));
*
* // Bind all servlet context attributes...
* ServletContext context = (ServletContext) binding.getVariable("context");
* Enumeration enumeration = context.getAttributeNames();
* while (enumeration.hasMoreElements()) {
* String name = (String) enumeration.nextElement();
* binding.setVariable(name, context.getAttribute(name));
* }
* }
* }
* <code></pre>
*
* @param binding to be modified
*/
protected void
setVariables(
ServletBinding binding) {
// empty
}
}