
cat.inspiracio.html.HTMLServlet Maven / Gradle / Ivy
/*
Copyright 20ileNotFound15 Alexander Bunkenburg
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 cat.inspiracio.html;
import static javax.script.ScriptEngine.FILENAME;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import cat.inspiracio.servlet.http.BufferedResponse;
import cat.inspiracio.servlet.http.HTTPException;
import cat.inspiracio.servlet.jsp.ServletPageContext;
/** Receives GET requests for html files.
*
* Parses the file, renders it (executing the data-attributes, the $-expressions,
* and the server-side script elements, and writes the resulting html document to
* the response.
*
* For parsing, using Java's built-in XML-DOM parser, therefore the html file must be valid XML.
* For parsing real HTML5 (rather than XML), use subclass HTML5Servlet.
*
* The output is html5 and cannot be parsed as XML. */
public class HTMLServlet extends HttpServlet{
private static final long serialVersionUID = -2680536696225745452L;
// construction --------------------------------------------------
public HTMLServlet(){}
// business methods ----------------------------------------------
/** Serves a request of method GET or POST.
* @throws IOException Couldn't write to response
* @throws ServletException Something else, to show in error page
* */
private void doIt(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
//Repairs reset()
if(gae())
response=new BufferedResponse(response);
Reader reader;
try{
reader=getReader(request);
}
catch(FileNotFoundException e){
response.sendError(404);
return;
}
try{
//sendError() abort rendering
response=new HttpServletResponseWrapper(response){
@Override public void sendError(int sc, String msg) throws IOException{
super.sendError(sc, msg);
throw new HTTPException(sc);
}
@Override public void sendError(int sc) throws IOException {
super.sendError(sc);
throw new HTTPException(sc);
}
@Override public void sendRedirect(String location) throws IOException {
super.sendRedirect(location);
throw new HTTPException(302);
}
};
template(request, response, reader);
}
//html parsing failed
catch (SAXException e){
throw new ServletException(e);
}
//Javascript execution threw an exception
catch (ScriptException e){
//Thrown by response.sendError() to abort rendering
HTTPException h=unwrapHTTPException(e);
if(h!=null){
response.resetBuffer();
}
else
//no unwrapping, so that error page can show line number
throw new ServletException(e);
}
if(gae())
response.flushBuffer();//BufferedResponse needs flush
}
/** Given an exception thrown by javascript execution,
* unwrap a HTTPException from it, or null. */
private HTTPException unwrapHTTPException(ScriptException e){
Throwable t=e;
while(true){
//discriminate on t
if(t==null)return null;
if(t instanceof HTTPException)return (HTTPException)t;
Throwable cause=t.getCause();
if(t==cause)return null;
t=cause;//go one deeper
}
}
/** Sends a template.
*
* If the template throws exception, sends corresponding error page.
* If the template sets error code, sends corresponding error page.
*
* @param request
* @param response Assume that reset() works.
* @param reader Open reader to the template
*
* @throws IOException Couldn't write to response
* @throws SAXException Couldn't parse html
* @throws ScriptException Javascript execution threw an exception
* */
private void template(HttpServletRequest request, HttpServletResponse response, Reader reader)throws IOException, SAXException, ScriptException{
if(request.getAttribute("javax.servlet.error.status_code")==null)
response.setStatus(200);//initialise, maybe script will change it
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
Document d=parse(reader);//Reading and parsing is fast. SAXException
d.setDocumentURI(request.getRequestURI());
Bindings bindings=initialise(request, response);
PrintWriter w=response.getWriter();
render(bindings, d, w);//ScriptException
}
/** Try to unwrap an exception thrown by the javascript engine.
*
* This method just returns the same exception,
* respecting all abstract boundaries.
*
* Subclasses that know the javascript engine that is being used,
* can really unwrap the exception.
* */
protected Throwable unwrap(Throwable e){return e;}
/** Forwards the request.
* @throws IOException
* @throws ServletException */
@SuppressWarnings("unused")
private void forward(HttpServletRequest request, HttpServletResponse response, String uri) throws ServletException, IOException{
RequestDispatcher dispatcher=request.getRequestDispatcher(uri);
dispatcher.forward(request, response);
}
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
long start=System.currentTimeMillis();
String uri=request.getRequestURI();
log("GET " + uri);
doIt(request, response);
long end=System.currentTimeMillis();
long delta=end-start;
log("HTMLServlet.doGet(" + uri + "): " + delta);
}
/** Receives a POST request and renders the html file
* which may send a redirect. */
@Override protected void doPost(HttpServletRequest rq, HttpServletResponse rs)throws ServletException, IOException {
String uri=rq.getRequestURI();
log("POST " + uri);
doIt(rq, rs);
}
// javascript -------------------------------------------
/** Prepares the initial scripting state.
* This implementation puts request, response, and session into it.
* */
protected Bindings initialise(HttpServletRequest request, HttpServletResponse response){
Bindings bindings=new SimpleBindings();
HttpSession session=request.getSession();
ServletPageContext context=new ServletPageContext(this, request, response);
ServletContext servletContext=getServletContext();
bindings.put("request", request);
bindings.put("response", response);
bindings.put("session", session);
bindings.put("pageContext", context);
bindings.put("servletContext", servletContext);
//If this is an error-page, there are request attributes
bind(request, bindings, "status_code");
bind(request, bindings, "exception_type");
bind(request, bindings, "message");
bind(request, bindings, "exception");//maybe this one is enough
bind(request, bindings, "request_uri");
bind(request, bindings, "servlet_name");
return bindings;
}
private void bind(HttpServletRequest request, Bindings bindings, String name){
Object o=request.getAttribute("javax.servlet.error." + name);
if(o!=null)
bindings.put(name, o);
}
/** Evaluate the prelude
* @param engine The script engine to use. In Java 8 (Nashorn) we must
* use the same engine instance for the whole page.
* @throws ScriptException
* @throws FileNotFoundException The prelude file is not there. */
private void prelude(ScriptEngine engine, Bindings bindings) throws ScriptException, FileNotFoundException{
//prelude-file, file name relative to /WEB-INF/web.xml
String preludeFile=getInitParameter("prelude-file");
if(preludeFile!=null && 0
© 2015 - 2025 Weber Informatics LLC | Privacy Policy