org.getobjects.appserver.publisher.JoDefaultRenderer.java Source code

Java tutorial

Introduction

Here is the source code for org.getobjects.appserver.publisher.JoDefaultRenderer.java

Source

/*
  Copyright (C) 2007-2008 Helge Hess
    
  This file is part of JOPE.
    
  JOPE 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 2, or (at your option) any
  later version.
    
  JOPE 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 Lesser General Public
  License for more details.
    
  You should have received a copy of the GNU Lesser General Public
  License along with JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/
package org.getobjects.appserver.publisher;

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.getobjects.appserver.core.IWOComponentDefinition;
import org.getobjects.appserver.core.WOActionResults;
import org.getobjects.appserver.core.WOApplication;
import org.getobjects.appserver.core.WOComponent;
import org.getobjects.appserver.core.WOContext;
import org.getobjects.appserver.core.WOElement;
import org.getobjects.appserver.core.WOMessage;
import org.getobjects.appserver.core.WORequest;
import org.getobjects.appserver.core.WOResourceManager;
import org.getobjects.appserver.core.WOResponse;
import org.getobjects.foundation.NSException;
import org.getobjects.foundation.NSJavaRuntime;
import org.getobjects.foundation.NSObject;
import org.getobjects.foundation.UString;

/**
 * JoDefaultRenderer
 * <p>
 * This is the default renderer which can deal with all the regular call
 * results. That is WOResponse, WOComponent, String, etc.
 * <p>
 * @see IJoObjectRenderer
 */
public class JoDefaultRenderer extends NSObject implements IJoObjectRenderer {
    protected static final Log log = LogFactory.getLog("JoDefaultRenderer");

    public static final JoDefaultRenderer sharedRenderer = new JoDefaultRenderer();

    protected IJoObjectRenderer jsonRenderer;

    public JoDefaultRenderer() {
        this.jsonRenderer = new JoSimpleJSONRenderer();
    }

    /* control rendering */

    /**
     * Checks whether the JoDefaultRenderer can render the given object. This
     * renderer can render JSON stuff plus:
     * <ul>
     *   <li>WOComponent
     *   <li>WOResponse
     *   <li>WOActionResults
     *   <li>WOElement
     *   <li>String
     *   <li>Exception
     *   <li>WOApplication (render a HTTP redirect to the entry URL)
     * </ul>
     */
    public boolean canRenderObjectInContext(Object _object, WOContext _ctx) {
        if (this.jsonRenderer.canRenderObjectInContext(_object, _ctx))
            return true;

        if (_object instanceof WOComponent || _object instanceof WOResponse || _object instanceof WOActionResults
                || _object instanceof WOElement || _object instanceof String || _object instanceof Exception
                || _object instanceof WOApplication || _object instanceof BufferedImage || _object instanceof Image)
            return true;

        return false;
    }

    /* rendering */

    public Exception renderObjectInContext(Object _object, WOContext _ctx) {
        if (this.jsonRenderer.canRenderObjectInContext(_object, _ctx))
            return this.jsonRenderer.renderObjectInContext(_object, _ctx);

        if (_object instanceof WOComponent)
            return this.renderComponent((WOComponent) _object, _ctx);

        if (_object instanceof WOResponse)
            return this.renderResponse((WOResponse) _object, _ctx);

        if (_object instanceof WOActionResults)
            return this.renderActionResults((WOActionResults) _object, _ctx);

        if (_object instanceof Exception)
            return this.renderException((Exception) _object, _ctx);

        if (_object instanceof WOElement)
            return this.renderElement((WOElement) _object, _ctx);

        if (_object instanceof String)
            return this.renderString(_object, _ctx);

        if (_object instanceof BufferedImage)
            return this.renderBufferedImage((BufferedImage) _object, _ctx);

        if (_object instanceof Image)
            return this.renderImage((Image) _object, _ctx);

        /* standard fallback for WOApplication */

        if (_object instanceof WOApplication) {
            /* This is if someone enters the root URL, per default we either redirect
             * to the DirectAction or to the Main page.
             */
            WOResponse r = ((WOApplication) _object).redirectToApplicationEntry(_ctx);
            return this.renderResponse(r, _ctx);
        }

        /* failed rendering, should not happen with proper call to canRender.. */

        log.error("cannot render object: " + _object);
        return new JoInternalErrorException("cannot render given object");
    }

    /* specific renderers */

    public Exception renderResponse(WOResponse _response, WOContext _ctx) {
        if (_response == null)
            return new JoInternalErrorException("got no response to render");

        if (_ctx.response() == _response) /* response is already active */
            return null; /* everything OK */

        // TODO: copy status, headers, content
        log.error("custom WOResponse'es not yet supported: " + _response);
        return new NSException("unimplemented: cannot render response");
    }

    public Exception renderException(Exception _exception, WOContext _ctx) {
        if (_exception == null)
            return new JoInternalErrorException("got no exception to render");

        /* determine status */

        int httpStatus = NSJavaRuntime.intValueForKey(_exception, "httpStatus");
        if (httpStatus < 100 || httpStatus > 999) {
            if (httpStatus != 0) {
                log.warn("got invalid httpStatus " + httpStatus + " for exception: " + _exception);
            }
            httpStatus = WOMessage.HTTP_STATUS_INTERNAL_ERROR;
        }
        if (httpStatus == 500)
            log.warn("delivering internal error exception", _exception);

        /* render exception */

        WOResponse r = _ctx.response();
        r.setStatus(httpStatus);

        // TODO: we should check the request 'accept' header and then decide
        r.setHeaderForKey("text/html", "content-type");

        // TODO: we might want to render more

        JoTraversalPath tp = _ctx.joTraversalPath();
        if (tp != null) {
            r.appendContentString("Path: ");
            r.appendContentHTMLString(UString.componentsJoinedByString(tp.path(), " => "));
            r.appendContentString("<br />");
        }

        r.appendContentHTMLString("Exception: " + _exception.getClass().getCanonicalName());
        r.appendContentString("<br />");

        r.appendContentHTMLString("Cause: " + _exception.getCause());
        r.appendContentString("<br />");

        r.appendContentHTMLString("Message: " + _exception.getMessage());
        r.appendContentString("<br />");

        return null /* everything is great */;
    }

    public Exception renderComponent(WOComponent _page, WOContext _ctx) {
        /* reuse context response for WOComponent */
        WOResponse r = _ctx.response();
        if (_page == null)
            return new JoInternalErrorException("got no page to render");

        if (log.isDebugEnabled())
            log.debug("delivering page: " + _page);

        _ctx.setPage(_page);
        _page.ensureAwakeInContext(_ctx);
        _ctx.enterComponent(_page, null /* component-content */);
        try {
            // TBD: shouldn't we call WOApplication appendToResponse?!
            _page.appendToResponse(r, _ctx);
        } finally { /* ensure that the component stack is OK */
            _ctx.leaveComponent(_page);
        }

        return null /* everything OK */;
    }

    public Exception renderElement(WOElement _e, WOContext _ctx) {
        if (_e == null)
            return new JoInternalErrorException("got no element to render");

        WOResponse r = _ctx.response();
        _e.appendToResponse(r, _ctx);
        return null /* everything OK */;
    }

    public Exception renderActionResults(WOActionResults _r, WOContext _ctx) {
        if (_r == null)
            return new JoInternalErrorException("got no actionresults to render");

        WOResponse r = _r.generateResponse();
        if (r == null) {
            return new JoInternalErrorException("got no actionresults response to render");
        }

        return this.renderResponse(r, _ctx);
    }

    public Exception renderString(Object _o, WOContext _ctx) {
        String s = _o.toString();
        if (s == null)
            return new JoInternalErrorException("got no string to render");

        WOResponse r = _ctx.response();
        r.setStatus(WOMessage.HTTP_STATUS_OK);
        // TODO: we should check the request 'accept' header and then decide
        r.setHeaderForKey("text/html", "content-type");
        r.appendContentHTMLString(s);

        return null; /* everything is awesome O */
    }

    /**
     * Renders a java.awt.BufferedImage to the WOResponse of the given context.
     * Remember to configure:<pre>
     *   -Djava.awt.headless=true</pre>
     * (thats the VM arguments of the run panel in Eclipse) 
     * 
     * @param _img   - the BufferedImage object to render
     * @param _ctx - the WOContext to render the image in
     * @return null if everything went fine, an Exception otherwise
     */
    public Exception renderBufferedImage(BufferedImage _img, WOContext _ctx) {
        // TBD: this method could be improved a lot, but it works well enough for
        //      simple cases

        if (_img == null)
            return new JoInternalErrorException("got no image to render");

        /* find a proper image writer */

        String usedType = null;
        Iterator<ImageWriter> writers;
        ImageWriter writer = null;
        WORequest rq = _ctx.request();
        if (rq != null) {
            // TBD: just iterate over the accepted (image/) types (considering
            //      the value quality) and check each

            if (rq.acceptsContentType("image/png", false /* direct match */)) {
                if ((writers = ImageIO.getImageWritersByMIMEType("image/png")) != null)
                    writer = writers.next();
                if (writer != null)
                    usedType = "image/png";
            }
            if (writer == null && rq.acceptsContentType("image/gif", false)) {
                if ((writers = ImageIO.getImageWritersByMIMEType("image/gif")) != null)
                    writer = writers.next();
                if (writer != null)
                    usedType = "image/gif";
            }
            if (writer == null && rq.acceptsContentType("image/jpeg", false)) {
                if ((writers = ImageIO.getImageWritersByMIMEType("image/jpeg")) != null)
                    writer = writers.next();
                if (writer != null)
                    usedType = "image/jpeg";
            }
        }
        if (writer == null) {
            if ((writers = ImageIO.getImageWritersByMIMEType("image/png")) != null)
                writer = writers.next();
            if (writer != null)
                usedType = "image/png";
        }
        if (writer == null)
            return new JoInternalErrorException("found no writer for image: " + _img);

        /* prepare WOResponse */

        WOResponse r = _ctx.response();
        r.setStatus(WOMessage.HTTP_STATUS_OK);
        r.setHeaderForKey("inline", "content-disposition");
        if (usedType != null)
            r.setHeaderForKey(usedType, "content-type");
        // TBD: do we know the content-length? If not, should we generate to a
        //      buffer to avoid confusing the browser (IE ...)
        r.enableStreaming();

        /* write */

        ImageOutputStream ios = null;
        try {
            ios = ImageIO.createImageOutputStream(rq.outputStream());
        } catch (IOException e) {
            log.warn("could not create image output stream: " + _img);
            return e;
        }

        writer.setOutput(ios);

        try {
            writer.write(null, new IIOImage(_img, null, null), null);
            ios.flush();
            writer.dispose();
            ios.close();
        } catch (IOException e) {
            log.warn("failed to write image to stream", e);
            return e;
        }

        return null; /* everything is awesome O */
    }

    /**
     * Renders an arbitary java.awt.Image object by converting it to a 
     * BufferedImage and then calling renderBufferedImage.
     * 
     * @param _img - the java.awt.Image to be rendered
     * @param _ctx - the context to render the Image in
     * @return null if everything went fine, an exception otherwise
     */
    public Exception renderImage(Image _img, WOContext _ctx) {
        /* Not sure whether thats the best way to accomplish this :-) */
        if (_img == null)
            return new JoInternalErrorException("got no image to render");

        if (_img instanceof BufferedImage)
            return this.renderBufferedImage((BufferedImage) _img, _ctx);

        int width = _img.getWidth(null);
        int height = _img.getHeight(null);

        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2d = bi.createGraphics();
        g2d.drawImage(_img, 0, 0, width, height, null);

        return this.renderBufferedImage(bi, _ctx);
    }

    /* rendering with frames */

    /**
     * Renders an object using another 'Frame' object. Usually the object will be
     * a WOComponent and the 'Frame' will be an OFS object representing a
     * WOComponent.
     * <p>
     * If this is the case the method will trigger the renderComponentWithFrame()
     * method.
     * 
     * @param _object - the object to render with the frame
     * @param _frame  - the frame used to render the _object
     * @param _ctx    - the context in which the rendering happens
     * @return null if everything went fine, an Exception otherwise
     */
    public Exception renderObjectWithFrame(Object _object, IJoComponentDefinition _frame, WOContext _ctx) {
        if (_frame == null)
            return this.renderObjectInContext(_object, _ctx);

        /* instantiate frame */

        // TBD: maybe we should just use app.pageWithName()? This does not guarantee
        //      that our cdef is triggered, but the RMs will cache our cdef
        WOResourceManager rm = null;

        if (_object instanceof WOComponent)
            rm = ((WOComponent) _object).resourceManager();

        if (rm == null) {
            // TBD: this is flaky, can get confused wrt JoLookupRM ...
            WOComponent cursor = null;
            if (_ctx != null)
                cursor = _ctx.component();
            if (cursor == null && _ctx != null)
                cursor = _ctx.page();
            if (cursor != null)
                rm = cursor.resourceManager();
        }
        if (rm == null && _ctx != null)
            rm = _ctx.application().resourceManager();

        if (rm == null) {
            log.warn("found no resource manager in context: " + _ctx);
            return new JoInternalErrorException("Frame: missing resource manager");
        }

        IWOComponentDefinition cdef = _frame.definitionForComponent(null /* name */, null /* langs */, rm);
        if (cdef == null) {
            log.warn("got no cdef for Frame: " + _frame);
            return new JoInternalErrorException("Frame: missing cdef");
        }

        //System.err.println("GOT FRAME CDEF:" + cdef);

        WOComponent frame = cdef.instantiateComponent(rm, _ctx);
        if (frame == null) {
            log.warn("could not instantiate Frame: " + _frame);
            return new JoInternalErrorException("Frame: could not instantiate");
        }

        /* Check whether we embed a component or whether we render an arbitary
         * object.
         */
        if (!(_object instanceof WOComponent)) {
            /* WOComponent itself is also an IJoRenderer :-) */
            // Note: do we first need to check whether the component CAN render the
            //       object?
            return frame.renderObjectInContext(_object, _ctx);
        }

        /* OK, embed component */
        WOComponent page = (WOComponent) _object;
        return this.renderComponentWithFrame(page, frame, _ctx);
    }

    public Exception renderObjectWithFrame(Object _object, IWOComponentDefinition _frame, WOContext _ctx) {
        if (_frame == null)
            return this.renderObjectInContext(_object, _ctx);

        /* instantiate frame */

        // TBD: maybe we should just use app.pageWithName()? This does not guarantee
        //      that our cdef is triggered, but the RMs will cache our cdef
        WOResourceManager rm = null;

        if (_object instanceof WOComponent)
            rm = ((WOComponent) _object).resourceManager();

        if (rm == null) {
            // TBD: this is flaky, can get confused wrt JoLookupRM ...
            WOComponent cursor = null;
            if (_ctx != null)
                cursor = _ctx.component();
            if (cursor == null && _ctx != null)
                cursor = _ctx.page();
            if (cursor != null)
                rm = cursor.resourceManager();
        }
        if (rm == null && _ctx != null)
            rm = _ctx.application().resourceManager();

        if (rm == null) {
            log.warn("found no resource manager in context: " + _ctx);
            return new JoInternalErrorException("Frame: missing resource manager");
        }

        //System.err.println("GOT FRAME CDEF:" + cdef);

        WOComponent frame = _frame.instantiateComponent(rm, _ctx);
        if (frame == null) {
            log.warn("could not instantiate Frame: " + _frame);
            return new JoInternalErrorException("Frame: could not instantiate");
        }

        /* Check whether we embed a component or whether we render an arbitary
         * object.
         */
        if (!(_object instanceof WOComponent)) {
            /* WOComponent itself is also an IJoRenderer :-) */
            // Note: do we first need to check whether the component CAN render the
            //       object?
            return frame.renderObjectInContext(_object, _ctx);
        }

        /* OK, embed component */
        WOComponent page = (WOComponent) _object;
        return this.renderComponentWithFrame(page, frame, _ctx);
    }

    /**
     * Renders a component within another 'Frame' component. On the page stack
     * the Frame is pushed a subcomponent of the component. It gets the template
     * of the component as the 'component content', which it then can embed using
     * the &lt;#WOComponentContent/&gt; element.
     * 
     * @param _page  - the page to embed in a frame
     * @param _frame - the frame to put around the page
     * @param _ctx   - the context in which the rendering happens
     * @return null if everything went fine, an Exception otherwise
     */
    public Exception renderComponentWithFrame(WOComponent _page, WOComponent _frame, WOContext _ctx) {
        if (_frame == null)
            return this.renderObjectInContext(_page, _ctx);

        /* First push page to stack, then push the Frame, then call appendToResponse
         * on the frame.
         * Note that we never call appendToResponse() on the page in this setup!
         */
        if (log.isDebugEnabled())
            log.debug("delivering page: " + _page);

        _ctx.setPage(_page);
        _page.ensureAwakeInContext(_ctx);
        _ctx.enterComponent(_page, null /* component-content */);
        try {
            _frame.ensureAwakeInContext(_ctx);
            _ctx.enterComponent(_frame, _page.template());
            try {
                // Note: we do not call the page appendToResponse()
                _frame.appendToResponse(_ctx.response(), _ctx);
            } finally {
                _ctx.leaveComponent(_frame);
            }
        } finally {
            _ctx.leaveComponent(_page);
        }

        return null /* everything OK */;
    }
}