/* * Copyright (C) 2011 Laurent Caillette * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either * version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.novelang.daemon; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.time.StopWatch; import org.eclipse.jetty.server.Request; import org.novelang.common.Problem; import org.novelang.common.Renderable; import org.novelang.configuration.ProducerConfiguration; import org.novelang.logger.Logger; import org.novelang.logger.LoggerFactory; import org.novelang.produce.AnyRequest; import org.novelang.produce.DocumentProducer; import org.novelang.produce.DocumentRequest; import org.novelang.produce.GenericRequest; import org.novelang.produce.MalformedRequestException; import org.novelang.produce.StreamDirector; import org.novelang.rendering.HtmlProblemPrinter; /** * Serves rendered content. * * By now, it re-creates a whole document when an error report is requested. * This is because it is not possible to change the type of a requested document without * an HTTP redirect. * <p> * Solutions: * <ol> * <li> Cache the Problems in the session. * <li> Cache all the Parts and Books and whatever. * </ol> * * Solution 1 sounds better as it keeps error display away from complex caching stuff. * * @author Laurent Caillette */ public class DocumentHandler extends GenericHandler { private static final Logger LOGGER = LoggerFactory.getLogger( DocumentHandler.class ); private final DocumentProducer documentProducer ; private final Charset renderingCharset ; public DocumentHandler( final ProducerConfiguration serverConfiguration ) { documentProducer = new DocumentProducer( serverConfiguration ) ; renderingCharset = serverConfiguration.getRenderingConfiguration().getDefaultCharset() ; } @Override protected void doHandle( final String target, final HttpServletRequest request, final HttpServletResponse response ) throws IOException, ServletException { handle( request, response ) ; } private void handle( final HttpServletRequest request, final HttpServletResponse response ) throws IOException, ServletException { LOGGER.info( "Handling request ", request.getRequestURI() ) ; final String rawRequest = request.getPathInfo() + ( StringUtils.isBlank( request.getQueryString() ) ? "" : "?" + request.getQueryString() ) ; final AnyRequest someRequest; try { someRequest = GenericRequest.parse( rawRequest ); } catch( MalformedRequestException e ) { throw new ServletException( e ); } if( null == someRequest ) { return ; } else { final ServletOutputStream outputStream = response.getOutputStream(); if( someRequest.isRendered() ) { final StopWatch stopWatch = new StopWatch() ; stopWatch.start() ; final DocumentRequest documentRequest = ( DocumentRequest ) someRequest ; final Renderable rendered ; try { rendered = documentProducer.createRenderable( documentRequest ) ; } catch( IOException e ) { renderProblems( Lists.newArrayList( Problem.createProblem( e ) ), someRequest.getOriginalTarget(), outputStream ) ; throw e ; } if( documentRequest.getDisplayProblems() ) { if( rendered.hasProblem() ) { renderProblemsAsRequested( documentRequest, rendered, outputStream ) ; } else { redirectToOriginalTarget( documentRequest, response ) ; } } else if( rendered.hasProblem() ) { LOGGER.warn( "Document had following problems: \n ", Joiner.on( "\n " ).join( rendered.getProblems() ) ) ; redirectToProblemPage( documentRequest, response ) ; } else { // Correct methods don't seem to work. // response.setCharacterEncoding( renderingCharset.name() ) ; // response.setContentType( documentRequest.getRenditionMimeType().getMimeName() ) ; response.addHeader( "Content-type", documentRequest.getRenditionMimeType().getMimeName() ) ; response.addHeader( "Charset", renderingCharset.name() ) ; response.setStatus( HttpServletResponse.SC_OK ) ; try { documentProducer.produce( documentRequest, rendered, StreamDirector.forExistingStream( outputStream ) ) ; } catch( Exception e ) { throw new ServletException( e ) ; } // response.setContentType( documentRequest.getRenditionMimeType().getMimeName() ) ; } ( ( Request ) request ).setHandled( true ) ; LOGGER.info( "Handled request ", request.getRequestURI(), " in ", formatDuration( stopWatch.getTime() ), "." ) ; } } } private static String formatDuration( final long milliseconds ) { final long seconds = milliseconds / 1000 ; return String.format( "%d.%03d", seconds, ( milliseconds % 1000 ) ) + " second" + ( seconds > 1 ? "s" : "" ) ; } private static void redirectToProblemPage( final DocumentRequest documentRequest, final HttpServletResponse response ) throws IOException { final String redirectionTarget = GenericRequest.getRedirectionWithError( documentRequest ) ; response.sendRedirect( redirectionTarget ) ; response.setStatus( HttpServletResponse.SC_FOUND ) ; LOGGER.info( "Redirected to '", redirectionTarget, "'" ); } private static void redirectToOriginalTarget( final DocumentRequest documentRequest, final HttpServletResponse response ) throws IOException { final String redirectionTarget = documentRequest.getOriginalTarget() ; response.sendRedirect( redirectionTarget ) ; response.setStatus( HttpServletResponse.SC_FOUND ) ; response.setContentType( documentRequest.getRenditionMimeType().getMimeName() ) ; LOGGER.info( "Redirected to '", redirectionTarget, "'" ) ; } private static void renderProblemsAsRequested( final DocumentRequest documentRequest, final Renderable rendered, final ServletOutputStream outputStream ) throws IOException { renderProblems( rendered.getProblems(), documentRequest.getOriginalTarget(), outputStream ) ; } private static void renderProblems( final Iterable< Problem > problems, final String originalTarget, final OutputStream outputStream ) throws IOException { final HtmlProblemPrinter problemPrinter = new HtmlProblemPrinter() ; problemPrinter.printProblems( outputStream, problems, originalTarget ) ; LOGGER.info( "Served error request '", originalTarget, "'" ) ; } }