groovy.servlet.ServletBinding Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy-servlet Show documentation
Show all versions of groovy-servlet Show documentation
Groovy: A powerful multi-faceted language for the JVM
/*
* 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.lang.Binding;
import groovy.xml.MarkupBuilder;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.runtime.MethodClosure;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
/**
* Servlet-specific binding extension to lazy load the writer or the output
* stream from the response.
*
*
Eager variables
*
* - "request" : the
HttpServletRequest
object
* - "response" : the
HttpServletRequest
object
* - "context" : the
ServletContext
object
* - "application" : same as context
* - "session" : shorthand for
request.getSession(false)
- can be null!
* - "params" : map of all form parameters - can be empty
* - "headers" : map of all request header fields
*
*
*
Lazy variables
*
* - "out" :
response.getWriter()
* - "sout" :
response.getOutputStream()
* - "html" :
new MarkupBuilder(response.getWriter())
- expandEmptyElements
flag is set to true
* - "json" :
new JsonBuilder()
*
* As per the Servlet specification, a call to response.getWriter()
should not be
* done if a call to response.getOutputStream()
has already occurred or the other way
* around. You may wonder then how the above lazy variables can possibly be provided - since
* setting them up would involve calling both of the above methods. The trick is catered for
* behind the scenes using lazy variables. Lazy bound variables can be requested without side
* effects; under the covers the writer and stream are wrapped. That means
* response.getWriter()
is never directly called until some output is done using
* 'out' or 'html'. Once a write method call is done using either of these variable, then an attempt
* to write using 'sout' will cause an IllegalStateException
. Similarly, if a write method
* call on 'sout' has been done already, then any further write method call on 'out' or 'html' will cause an
* IllegalStateException
.
*
*
Reserved internal variable names (see "Methods" below)
*
* - "forward"
* - "include"
* - "redirect"
*
*
* If response.getWriter()
is called directly (without using out), then a write method
* call on 'sout' will not cause the IllegalStateException
, but it will still be invalid.
* It is the responsibility of the user of this class to not mix these different usage
* styles. The same applies to calling response.getOutputStream()
and using 'out' or 'html'.
*
* Methods
*
* - "forward(String path)" :
request.getRequestDispatcher(path).forward(request, response)
* - "include(String path)" :
request.getRequestDispatcher(path).include(request, response)
* - "redirect(String location)" :
response.sendRedirect(location)
*
*/
public class ServletBinding extends Binding {
/**
* A OutputStream dummy that will throw a GroovyBugError for any
* write method call to it.
*/
private static class InvalidOutputStream extends OutputStream {
/**
* Will always throw a GroovyBugError
* @see java.io.OutputStream#write(int)
*/
@Override
public void write(int b) {
throw new GroovyBugError("Any write calls to this stream are invalid!");
}
}
/**
* A class to manage the response output stream and writer.
* If the stream have been 'used', then using the writer will cause
* a IllegalStateException. If the writer have been 'used', then
* using the stream will cause a IllegalStateException. 'used' means
* any write method has been called. Simply requesting the objects will
* not cause an exception.
*/
private static class ServletOutput {
private final HttpServletResponse response;
private ServletOutputStream outputStream;
private PrintWriter writer;
public ServletOutput(HttpServletResponse response) {
this.response = response;
}
private ServletOutputStream getResponseStream() throws IOException {
if (writer != null) throw new IllegalStateException("The variable 'out' or 'html' have been used already. Use either out/html or sout, not both.");
if (outputStream == null) outputStream = response.getOutputStream();
return outputStream;
}
public ServletOutputStream getOutputStream() {
return new ServletOutputStream() {
@Override
public boolean isReady() {
try {
return getResponseStream().isReady();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void setWriteListener(WriteListener writeListener) {
try {
getResponseStream().setWriteListener(writeListener);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void write(int b) throws IOException {
getResponseStream().write(b);
}
@Override
public void close() throws IOException {
getResponseStream().close();
}
@Override
public void flush() throws IOException {
getResponseStream().flush();
}
@Override
public void write(byte[] b) throws IOException {
getResponseStream().write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
getResponseStream().write(b, off, len);
}
};
}
private PrintWriter getResponseWriter() {
if (outputStream != null) throw new IllegalStateException("The variable 'sout' have been used already. Use either out/html or sout, not both.");
if (writer == null) {
try {
writer = response.getWriter();
} catch (IOException ioe) {
writer = new PrintWriter(new ByteArrayOutputStream());
throw new IllegalStateException("unable to get response writer",ioe);
}
}
return writer;
}
public PrintWriter getWriter() {
return new PrintWriter(new InvalidOutputStream()) {
@Override
public boolean checkError() {
return getResponseWriter().checkError();
}
@Override
public void close() {
getResponseWriter().close();
}
@Override
public void flush() {
getResponseWriter().flush();
}
@Override
public void write(char[] buf) {
getResponseWriter().write(buf);
}
@Override
public void write(char[] buf, int off, int len) {
getResponseWriter().write(buf, off, len);
}
@Override
public void write(int c) {
getResponseWriter().write(c);
}
@Override
public void write(String s, int off, int len) {
getResponseWriter().write(s, off, len);
}
@Override
public void println() {
getResponseWriter().println();
}
@Override
public PrintWriter format(String format, Object... args) {
getResponseWriter().format(format, args);
return this;
}
@Override
public PrintWriter format(Locale l, String format, Object... args) {
getResponseWriter().format(l, format, args);
return this;
}
};
}
}
private boolean initialized;
/**
* Initializes a servlet binding.
*
* @param request the HttpServletRequest object
* @param response the HttpServletRequest object
* @param context the ServletContext object
*/
public ServletBinding(HttpServletRequest request, HttpServletResponse response, ServletContext context) {
/*
* Bind the default variables.
*/
super.setVariable("request", request);
super.setVariable("response", response);
super.setVariable("context", context);
super.setVariable("application", context);
/*
* Bind the HTTP session object - if there is one.
* Note: we don't create one here!
*/
super.setVariable("session", request.getSession(false));
/*
* Bind form parameter key-value hash map.
*
* If there are multiple, they are passed as an array.
*/
Map params = collectParams(request);
super.setVariable("params", params);
/*
* Bind request header key-value hash map.
*/
Map headers = new LinkedHashMap();
for (Enumeration names = request.getHeaderNames(); names.hasMoreElements();) {
String headerName = (String) names.nextElement();
String headerValue = request.getHeader(headerName);
headers.put(headerName, headerValue);
}
super.setVariable("headers", headers);
}
@SuppressWarnings("unchecked")
private Map collectParams(HttpServletRequest request) {
Map params = new LinkedHashMap();
for (Enumeration names = request.getParameterNames(); names.hasMoreElements();) {
String name = (String) names.nextElement();
if (!super.getVariables().containsKey(name)) {
String[] values = request.getParameterValues(name);
if (values.length == 1) {
params.put(name, values[0]);
} else {
params.put(name, values);
}
}
}
return params;
}
@Override
public void setVariable(String name, Object value) {
lazyInit();
validateArgs(name, "Can't bind variable to");
excludeReservedName(name, "out");
excludeReservedName(name, "sout");
excludeReservedName(name, "html");
excludeReservedName(name, "json");
excludeReservedName(name, "forward");
excludeReservedName(name, "include");
excludeReservedName(name, "redirect");
super.setVariable(name, value);
}
@Override
public Map getVariables() {
lazyInit();
return super.getVariables();
}
/**
* @return a writer, an output stream, a markup builder or another requested object
*/
@Override
public Object getVariable(String name) {
lazyInit();
validateArgs(name, "No variable with");
return super.getVariable(name);
}
private void lazyInit() {
if (initialized) return;
initialized = true;
HttpServletResponse response = (HttpServletResponse) super.getVariable("response");
ServletOutput output = new ServletOutput(response);
super.setVariable("out", output.getWriter());
super.setVariable("sout", output.getOutputStream());
MarkupBuilder builder = new MarkupBuilder(output.getWriter());
builder.setExpandEmptyElements(true);
super.setVariable("html", builder);
try {
// load using reflection to avoid needing a hard requirement on groovy-json for those not needing JSON support
Class jsonBuilderClass = this.getClass().getClassLoader().loadClass("groovy.json.StreamingJsonBuilder");
Constructor writerConstructor = getWriterConstructor(jsonBuilderClass);
super.setVariable("json", writerConstructor.newInstance(output.getWriter()));
} catch (Throwable t) {
t.printStackTrace();
}
// bind forward method
MethodClosure c = new MethodClosure(this, "forward");
super.setVariable("forward", c);
// bind include method
c = new MethodClosure(this, "include");
super.setVariable("include", c);
// bind redirect method
c = new MethodClosure(this, "redirect");
super.setVariable("redirect", c);
}
@SuppressWarnings("unchecked")
private Constructor getWriterConstructor(Class jsonBuilderClass) throws NoSuchMethodException {
return jsonBuilderClass.getConstructor(Writer.class);
}
private static void validateArgs(String name, String message) {
if (name == null) {
throw new IllegalArgumentException(message + " null key.");
}
if (name.length() == 0) {
throw new IllegalArgumentException(message + " blank key name. [length=0]");
}
}
private static void excludeReservedName(String name, String reservedName) {
if (reservedName.equals(name)) {
throw new IllegalArgumentException("Can't bind variable to key named '" + name + "'.");
}
}
public void forward(String path) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) super.getVariable("request");
HttpServletResponse response = (HttpServletResponse) super.getVariable("response");
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
dispatcher.forward(request, response);
}
public void include(String path) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) super.getVariable("request");
HttpServletResponse response = (HttpServletResponse) super.getVariable("response");
RequestDispatcher dispatcher = request.getRequestDispatcher(path);
dispatcher.include(request, response);
}
public void redirect(String location) throws IOException {
HttpServletResponse response = (HttpServletResponse) super.getVariable("response");
response.sendRedirect(location);
}
}