org.codehaus.groovy.grails.web.pages.GroovyPageWritable.java Source code

Java tutorial

Introduction

Here is the source code for org.codehaus.groovy.grails.web.pages.GroovyPageWritable.java

Source

/*
 * Copyright 2004-2005 Graeme Rocher
 *
 * 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 org.codehaus.groovy.grails.web.pages;

import grails.util.Environment;
import groovy.lang.Binding;
import groovy.lang.Writable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.text.DateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.grails.web.pages.exceptions.GroovyPagesException;
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes;
import org.codehaus.groovy.grails.web.servlet.WrappedResponseHolder;
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest;
import org.springframework.web.context.request.RequestContextHolder;

/**
 * Writes itself to the specified writer, typically the response writer.
 *
 * @author Graeme Rocher
 * @author Lari Hotari
 * @since 0.5
 */
class GroovyPageWritable implements Writable {
    private static final Log LOG = LogFactory.getLog(GroovyPageWritable.class);
    private static final String ATTRIBUTE_NAME_DEBUG_TEMPLATES_ID_COUNTER = "org.codehaus.groovy.grails.web.pages.DEBUG_TEMPLATES_COUNTER";
    private static final String GSP_NONE_CODEC_NAME = "none";
    private HttpServletResponse response;
    private HttpServletRequest request;
    private GroovyPageMetaInfo metaInfo;
    private boolean showSource;
    private boolean debugTemplates;
    private AtomicInteger debugTemplatesIdCounter;
    private GrailsWebRequest webRequest;
    private boolean allowSettingContentType;

    @SuppressWarnings("rawtypes")
    private Map additionalBinding = new HashMap();
    private static final String GROOVY_SOURCE_CONTENT_TYPE = "text/plain";

    public GroovyPageWritable(GroovyPageMetaInfo metaInfo, boolean allowSettingContentType) {
        this.metaInfo = metaInfo;
        this.allowSettingContentType = allowSettingContentType;
        webRequest = (GrailsWebRequest) RequestContextHolder.getRequestAttributes();
        if (webRequest != null) {
            request = webRequest.getCurrentRequest();
            HttpServletResponse wrapped = WrappedResponseHolder.getWrappedResponse();
            response = wrapped != null ? wrapped : webRequest.getCurrentResponse();
        }
        showSource = shouldShowGroovySource();
        debugTemplates = shouldDebugTemplates();
        if (debugTemplates) {
            debugTemplatesIdCounter = (AtomicInteger) request
                    .getAttribute(ATTRIBUTE_NAME_DEBUG_TEMPLATES_ID_COUNTER);
            if (debugTemplatesIdCounter == null) {
                debugTemplatesIdCounter = new AtomicInteger(0);
                request.setAttribute(ATTRIBUTE_NAME_DEBUG_TEMPLATES_ID_COUNTER, debugTemplatesIdCounter);
            }
        }
    }

    private boolean shouldDebugTemplates() {
        return request != null && request.getParameter("debugTemplates") != null
                && Environment.getCurrent() == Environment.DEVELOPMENT;
    }

    private boolean shouldShowGroovySource() {
        return request != null && request.getParameter("showSource") != null
                && (Environment.getCurrent() == Environment.DEVELOPMENT) && metaInfo.getGroovySource() != null;
    }

    /**
     * This sets any additional variables that need to be placed in the Binding of the GSP page.
     *
     * @param binding The additional variables
     */
    @SuppressWarnings("rawtypes")
    public void setBinding(Map binding) {
        if (binding != null) {
            additionalBinding = binding;
        }
    }

    /**
     * Set to true if the generated source should be output instead
     * @param showSource True if source output should be output
     */
    public void setShowSource(boolean showSource) {
        this.showSource = showSource;
    }

    /**
     * Writes the template to the specified Writer
     *
     * @param out The Writer to write to, normally the HttpServletResponse
     * @return Returns the passed Writer
     * @throws IOException
     */
    public Writer writeTo(Writer out) throws IOException {
        if (showSource) {
            // Set it to TEXT
            response.setContentType(GROOVY_SOURCE_CONTENT_TYPE); // must come before response.getOutputStream()
            writeGroovySourceToResponse(metaInfo, out);
        } else {
            // Set it to HTML by default
            if (metaInfo.getCompilationException() != null) {
                throw metaInfo.getCompilationException();
            }

            // Set up the script context
            GroovyPageBinding parentBinding = null;
            boolean hasRequest = request != null;
            boolean newParentCreated = false;

            if (hasRequest) {
                parentBinding = (GroovyPageBinding) request.getAttribute(GrailsApplicationAttributes.PAGE_SCOPE);
                if (parentBinding == null) {
                    if (webRequest != null) {
                        parentBinding = new GroovyPageBinding(new GroovyPageRequestBinding(webRequest));
                        parentBinding.setRoot(true);
                        newParentCreated = true;
                    }
                }
            }

            if (allowSettingContentType && response != null) {
                // only try to set content type when evaluating top level GSP
                boolean contentTypeAlreadySet = response.isCommitted() || response.getContentType() != null;
                if (!contentTypeAlreadySet) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Writing response to [" + response.getClass() + "] with content type: "
                                + metaInfo.getContentType());
                    }
                    response.setContentType(metaInfo.getContentType()); // must come before response.getWriter()
                }
            }

            GroovyPageBinding binding = createBinding(parentBinding);
            String previousGspCode = GSP_NONE_CODEC_NAME;
            if (hasRequest) {
                request.setAttribute(GrailsApplicationAttributes.PAGE_SCOPE, binding);
                previousGspCode = (String) request.getAttribute(GrailsApplicationAttributes.GSP_CODEC);
            }

            makeLegacyCodecVariablesAvailable(hasRequest, binding);

            binding.setVariableDirectly(GroovyPage.RESPONSE, response);
            binding.setVariableDirectly(GroovyPage.REQUEST, request);
            // support development mode's evaluate (so that doesn't search for missing variable in parent bindings)

            GroovyPage page = null;
            try {
                page = (GroovyPage) metaInfo.getPageClass().newInstance();
            } catch (Exception e) {
                throw new GroovyPagesException("Problem instantiating page class", e);
            }
            page.setBinding(binding);
            binding.setOwner(page);

            page.initRun(out, webRequest, metaInfo);

            int debugId = 0;
            long debugStartTimeMs = 0;
            if (debugTemplates) {
                debugId = debugTemplatesIdCounter.incrementAndGet();
                out.write("<!-- GSP #");
                out.write(String.valueOf(debugId));
                out.write(" START template: ");
                out.write(page.getGroovyPageFileName());
                out.write(" precompiled: ");
                out.write(String.valueOf(metaInfo.isPrecompiledMode()));
                out.write(" lastmodified: ");
                out.write(DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
                        .format(new Date(metaInfo.getLastModified())));
                out.write(" -->");
                debugStartTimeMs = System.currentTimeMillis();
            }
            try {
                page.run();
            } finally {
                page.cleanup();
                if (hasRequest) {
                    if (newParentCreated) {
                        request.removeAttribute(GrailsApplicationAttributes.PAGE_SCOPE);
                    } else {
                        request.setAttribute(GrailsApplicationAttributes.PAGE_SCOPE, parentBinding);
                    }
                    request.setAttribute(GrailsApplicationAttributes.GSP_CODEC,
                            previousGspCode != null ? previousGspCode : GSP_NONE_CODEC_NAME);
                }
            }
            if (debugTemplates) {
                out.write("<!-- GSP #");
                out.write(String.valueOf(debugId));
                out.write(" END template: ");
                out.write(page.getGroovyPageFileName());
                out.write(" rendering time: ");
                out.write(String.valueOf(System.currentTimeMillis() - debugStartTimeMs));
                out.write(" ms -->");
            }
        }
        return out;
    }

    private void makeLegacyCodecVariablesAvailable(boolean hasRequest, GroovyPageBinding binding) {
        if (metaInfo.getExpressionEncoder() != null) {
            if (hasRequest) {
                request.setAttribute(GrailsApplicationAttributes.GSP_CODEC,
                        metaInfo.getExpressionEncoder().getCodecIdentifier().getCodecName());
            }
            binding.setVariableDirectly(GroovyPage.CODEC_VARNAME, metaInfo.getExpressionEncoder());
        } else {
            if (hasRequest) {
                request.setAttribute(GrailsApplicationAttributes.GSP_CODEC, GSP_NONE_CODEC_NAME);
            }
            binding.setVariableDirectly(GroovyPage.CODEC_VARNAME, gspNoneCodeInstance);
        }
    }

    private static final GspNoneCodec gspNoneCodeInstance = new GspNoneCodec();

    private static final class GspNoneCodec {
        @SuppressWarnings("unused")
        public final Object encode(Object object) {
            return object;
        }
    }

    private GroovyPageBinding createBinding(Binding parent) {
        GroovyPageBinding binding = new GroovyPageBinding();
        binding.setParent(parent);
        binding.setVariableDirectly("it", null);
        if (additionalBinding != null) {
            binding.addMap(additionalBinding);
        }
        // set plugin context path for top level rendering, this means actual view + layout
        // view is top level when parent is GroovyPageRequestBinding
        // pluginContextPath is also resetted when a plugin template is overrided by an application view
        if (parent == null || (parent instanceof GroovyPageBinding && ((GroovyPageBinding) parent).isRoot())
                || "".equals(metaInfo.getPluginPath())) {
            binding.setPluginContextPath(metaInfo.getPluginPath());
        }
        binding.setPagePlugin(metaInfo.getPagePlugin());
        return binding;
    }

    /**
     * Copy all of input to output.
     * @param in The input stream to writeInputStreamToResponse from
     * @param out The output to write to
     * @throws IOException When an error occurs writing to the response Writer
     */
    protected void writeInputStreamToResponse(InputStream in, Writer out) throws IOException {
        try {
            in.reset();
            Reader reader = new InputStreamReader(in, "UTF-8");
            char[] buf = new char[8192];

            for (;;) {
                int read = reader.read(buf);
                if (read <= 0)
                    break;
                out.write(buf, 0, read);
            }
        } finally {
            out.close();
            in.close();
        }
    }

    /**
     * Writes the Groovy source code attached to the given info object
     * to the response, prefixing each line with its line number. The
     * line numbers make it easier to match line numbers in exceptions
     * to the generated source.
     * @param info The meta info for the GSP page that we want to write
     * the generated source for.
     * @param out The writer to send the source to.
     * @throws IOException If there is either a problem with the input
     * stream for the Groovy source, or the writer.
     */
    protected void writeGroovySourceToResponse(GroovyPageMetaInfo info, Writer out) throws IOException {
        InputStream in = info.getGroovySource();
        if (in == null)
            return;
        try {
            try {
                in.reset();
            } catch (IOException e) {
                // ignore
            }
            BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));

            int lineNum = 1;
            int maxPaddingSize = 3;

            // Prepare the buffer containing the whitespace padding.
            // The padding is used to right-align the line numbers.
            StringBuilder paddingBuffer = new StringBuilder(maxPaddingSize);
            for (int i = 0; i < maxPaddingSize; i++) {
                paddingBuffer.append(' ');
            }

            // Set the initial padding.
            String padding = paddingBuffer.toString();

            // Read the Groovy source line by line and write it to the
            // response, prefixing each line with the line number.
            for (String line = reader.readLine(); line != null; line = reader.readLine(), lineNum++) {
                // Get the current line number as a string.
                String numberText = String.valueOf(lineNum);

                // Decrease the padding if the current line number has
                // more digits than the previous one.
                if (padding.length() + numberText.length() > 4) {
                    paddingBuffer.deleteCharAt(padding.length() - 1);
                    padding = paddingBuffer.toString();
                }

                // Write out this line.
                out.write(padding);
                out.write(numberText);
                out.write(": ");
                out.write(line);
                out.write('\n');
            }
        } finally {
            out.close();
            in.close();
        }
    }
}